mirror of
https://github.com/dpup/meshstream.git
synced 2026-07-06 09:51:31 +02:00
Packet formatting changes
This commit is contained in:
@@ -16,9 +16,6 @@ export const PacketList: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
// A unique ID is attached to each rendered packet element
|
||||
const [packetKeys, setPacketKeys] = useState<Record<string, string>>({});
|
||||
|
||||
// Generate a reproducible hash code for a string
|
||||
const hashString = useCallback((str: string): string => {
|
||||
let hash = 0;
|
||||
@@ -31,42 +28,21 @@ export const PacketList: React.FC = () => {
|
||||
return Math.abs(hash).toString(36);
|
||||
}, []);
|
||||
|
||||
// Create a consistent, unique fingerprint for a packet
|
||||
const createPacketFingerprint = useCallback((packet: Packet): string => {
|
||||
// Combine multiple fields to maximize uniqueness
|
||||
const parts = [
|
||||
packet.data.from?.toString() ?? 'unknown',
|
||||
packet.data.id?.toString() ?? 'noid',
|
||||
packet.data.portNum?.toString() ?? 'noport',
|
||||
packet.info.channel ?? 'nochannel',
|
||||
packet.info.userId ?? 'nouser',
|
||||
// Include a hash of raw JSON data for extra uniqueness
|
||||
hashString(JSON.stringify(packet)),
|
||||
];
|
||||
return parts.join('_');
|
||||
// Create a packet key using data.id and from address
|
||||
// This should match the key generation logic in the reducer
|
||||
const createPacketKey = useCallback((packet: Packet): string => {
|
||||
if (packet.data.id !== undefined && packet.data.from !== undefined) {
|
||||
// Use Meshtastic node ID format (! followed by lowercase hex) and packet ID
|
||||
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
|
||||
return `${nodeId}_${packet.data.id}`;
|
||||
} else {
|
||||
// Fallback to hash-based key if no ID or from (should be rare)
|
||||
return `hash_${hashString(JSON.stringify(packet))}`;
|
||||
}
|
||||
}, [hashString]);
|
||||
|
||||
// Update the packet keys whenever the packets change
|
||||
useEffect(() => {
|
||||
// Store new packet keys
|
||||
const newKeys: Record<string, string> = {...packetKeys};
|
||||
|
||||
// Assign keys to any packets that don't already have them
|
||||
packets.forEach((packet) => {
|
||||
const fingerprint = createPacketFingerprint(packet);
|
||||
|
||||
// Only create new keys for packets we haven't seen before
|
||||
if (!newKeys[fingerprint]) {
|
||||
const randomPart = Math.random().toString(36).substring(2, 10);
|
||||
newKeys[fingerprint] = `${fingerprint}_${randomPart}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Only update state if we actually added new keys
|
||||
if (Object.keys(newKeys).length !== Object.keys(packetKeys).length) {
|
||||
setPacketKeys(newKeys);
|
||||
}
|
||||
}, [packets, createPacketFingerprint]);
|
||||
// We don't need to track packet keys in state anymore since we use data.id
|
||||
// and it's deterministic - removing this effect to prevent the infinite loop issue
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -103,8 +79,6 @@ export const PacketList: React.FC = () => {
|
||||
if (window.confirm("Are you sure you want to clear all packets?")) {
|
||||
dispatch(clearPackets());
|
||||
setCurrentPage(1);
|
||||
// Clear the packet keys too
|
||||
setPacketKeys({});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,11 +90,9 @@ export const PacketList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Get the key for a packet
|
||||
// Get the key for a packet - directly use createPacketKey
|
||||
const getPacketKey = (packet: Packet, index: number): string => {
|
||||
const fingerprint = createPacketFingerprint(packet);
|
||||
// Fallback to index-based key if no fingerprint key exists
|
||||
return packetKeys[fingerprint] || `packet_${index}_${Date.now()}`;
|
||||
return createPacketKey(packet) || `fallback_${index}`;
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -46,7 +46,7 @@ export const GenericPacket: React.FC<GenericPacketProps> = ({ packet }) => {
|
||||
<Package className="h-4 w-4 text-neutral-100" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-200">
|
||||
From: {data.from || "Unknown"}
|
||||
From: {data.from ? `!${data.from.toString(16).toLowerCase()}` : "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm">
|
||||
|
||||
@@ -22,7 +22,7 @@ export const NodeInfoPacket: React.FC<NodeInfoPacketProps> = ({ packet }) => {
|
||||
<User className="h-4 w-4 text-neutral-100" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-200">
|
||||
From: {data.from || "Unknown"}
|
||||
From: {data.from ? `!${data.from.toString(16).toLowerCase()}` : "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm">
|
||||
|
||||
@@ -26,7 +26,7 @@ export const PositionPacket: React.FC<PositionPacketProps> = ({ packet }) => {
|
||||
<MapPin className="h-4 w-4 text-neutral-100" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-200">
|
||||
From: {data.from || "Unknown"}
|
||||
From: {data.from ? `!${data.from.toString(16).toLowerCase()}` : "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm">
|
||||
|
||||
@@ -49,7 +49,7 @@ export const TelemetryPacket: React.FC<TelemetryPacketProps> = ({ packet }) => {
|
||||
<BarChart className="h-4 w-4 text-neutral-100" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-200">
|
||||
From: {data.from || "Unknown"}
|
||||
From: {data.from ? `!${data.from.toString(16).toLowerCase()}` : "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm">
|
||||
|
||||
@@ -17,7 +17,7 @@ export const TextMessagePacket: React.FC<TextMessagePacketProps> = ({ packet })
|
||||
<MessageSquareText className="h-4 w-4 text-neutral-100" />
|
||||
</div>
|
||||
<span className="font-medium text-neutral-200">
|
||||
From: {data.from || "Unknown"}
|
||||
From: {data.from ? `!${data.from.toString(16).toLowerCase()}` : "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-neutral-400 text-sm">
|
||||
|
||||
@@ -10,6 +10,7 @@ interface PacketState {
|
||||
error: string | null;
|
||||
streamPaused: boolean;
|
||||
bufferedPackets: Packet[]; // Holds packets received while paused
|
||||
seenPackets: Record<string, boolean>; // Tracks already seen packet IDs to prevent duplicates
|
||||
}
|
||||
|
||||
const initialState: PacketState = {
|
||||
@@ -18,6 +19,7 @@ const initialState: PacketState = {
|
||||
error: null,
|
||||
streamPaused: false,
|
||||
bufferedPackets: [],
|
||||
seenPackets: {}, // Empty object to track seen packets
|
||||
};
|
||||
|
||||
const packetSlice = createSlice({
|
||||
@@ -29,8 +31,26 @@ const packetSlice = createSlice({
|
||||
state.error = null;
|
||||
},
|
||||
fetchPacketsSuccess(state, action: PayloadAction<Packet[]>) {
|
||||
// Track unique packets and update the seen packets object
|
||||
const uniquePackets: Packet[] = [];
|
||||
|
||||
action.payload.forEach(packet => {
|
||||
if (packet.data.from !== undefined && packet.data.id !== undefined) {
|
||||
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
|
||||
const packetKey = `${nodeId}_${packet.data.id}`;
|
||||
|
||||
if (!state.seenPackets[packetKey]) {
|
||||
state.seenPackets[packetKey] = true;
|
||||
uniquePackets.push(packet);
|
||||
}
|
||||
} else {
|
||||
// If we don't have from or id (rare), just add the packet
|
||||
uniquePackets.push(packet);
|
||||
}
|
||||
});
|
||||
|
||||
// Limit initial load to MAX_PACKETS
|
||||
state.packets = action.payload.slice(-MAX_PACKETS);
|
||||
state.packets = uniquePackets.slice(-MAX_PACKETS);
|
||||
state.loading = false;
|
||||
},
|
||||
fetchPacketsFailure(state, action: PayloadAction<string>) {
|
||||
@@ -38,9 +58,29 @@ const packetSlice = createSlice({
|
||||
state.loading = false;
|
||||
},
|
||||
addPacket(state, action: PayloadAction<Packet>) {
|
||||
const packet = action.payload;
|
||||
|
||||
// Skip packets without valid from/id
|
||||
if (packet.data.from === undefined || packet.data.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a Meshtastic node ID format
|
||||
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
|
||||
const packetKey = `${nodeId}_${packet.data.id}`;
|
||||
|
||||
// Check if we've already seen this packet
|
||||
if (state.seenPackets[packetKey]) {
|
||||
// Packet is a duplicate, ignore it
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this packet as seen
|
||||
state.seenPackets[packetKey] = true;
|
||||
|
||||
if (state.streamPaused) {
|
||||
// When paused, add to buffer instead of main list
|
||||
state.bufferedPackets.unshift(action.payload);
|
||||
state.bufferedPackets.unshift(packet);
|
||||
|
||||
// Ensure buffer doesn't grow too large
|
||||
if (state.bufferedPackets.length > MAX_PACKETS) {
|
||||
@@ -48,7 +88,7 @@ const packetSlice = createSlice({
|
||||
}
|
||||
} else {
|
||||
// Normal flow - add to main list
|
||||
state.packets.unshift(action.payload);
|
||||
state.packets.unshift(packet);
|
||||
|
||||
// Remove oldest packets if we exceed the limit
|
||||
if (state.packets.length > MAX_PACKETS) {
|
||||
@@ -59,6 +99,7 @@ const packetSlice = createSlice({
|
||||
clearPackets(state) {
|
||||
state.packets = [];
|
||||
state.bufferedPackets = [];
|
||||
state.seenPackets = {}; // Reset the seen packets object
|
||||
},
|
||||
toggleStreamPause(state) {
|
||||
state.streamPaused = !state.streamPaused;
|
||||
|
||||
Reference in New Issue
Block a user