From 489f995433741d128e7b477f887f62da0ae3653a Mon Sep 17 00:00:00 2001 From: Daniel Pupius Date: Wed, 23 Apr 2025 13:59:54 -0700 Subject: [PATCH] Track rx time --- decoder/decoder.go | 6 ++- generated/meshstream/meshstream.pb.go | 16 ++++++-- proto/meshstream/meshstream.proto | 3 ++ web/src/components/PacketList.tsx | 37 ++++++++++++++++++- web/src/components/packets/PacketCard.tsx | 14 ++++++- .../components/packets/TelemetryPacket.tsx | 11 ++++-- web/src/lib/types.ts | 3 ++ 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/decoder/decoder.go b/decoder/decoder.go index 593544b..a40b1f2 100644 --- a/decoder/decoder.go +++ b/decoder/decoder.go @@ -3,6 +3,7 @@ package decoder import ( "fmt" "strings" + "time" "google.golang.org/protobuf/proto" @@ -77,7 +78,10 @@ func DecodeEncodedMessage(payload []byte) (*pb.ServiceEnvelope, error) { // DecodeMessage creates a Data object from a binary encoded message func DecodeMessage(payload []byte, topicInfo *meshtreampb.TopicInfo) *meshtreampb.Data { - data := &meshtreampb.Data{} + data := &meshtreampb.Data{ + // Add reception timestamp (Unix timestamp in seconds) + RxTime: uint64(time.Now().Unix()), + } // First decode the envelope envelope, err := DecodeEncodedMessage(payload) diff --git a/generated/meshstream/meshstream.pb.go b/generated/meshstream/meshstream.pb.go index 99a3adb..782845a 100644 --- a/generated/meshstream/meshstream.pb.go +++ b/generated/meshstream/meshstream.pb.go @@ -220,7 +220,9 @@ type Data struct { Source uint32 `protobuf:"varint,54,opt,name=source,proto3" json:"source,omitempty"` WantResponse bool `protobuf:"varint,55,opt,name=want_response,json=wantResponse,proto3" json:"want_response,omitempty"` // Error tracking - DecodeError string `protobuf:"bytes,60,opt,name=decode_error,json=decodeError,proto3" json:"decode_error,omitempty"` + DecodeError string `protobuf:"bytes,60,opt,name=decode_error,json=decodeError,proto3" json:"decode_error,omitempty"` + // Reception timestamp (added by decoder) + RxTime uint64 `protobuf:"varint,61,opt,name=rx_time,json=rxTime,proto3" json:"rx_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -654,6 +656,13 @@ func (x *Data) GetDecodeError() string { return "" } +func (x *Data) GetRxTime() uint64 { + if x != nil { + return x.RxTime + } + return 0 +} + type isData_Payload interface { isData_Payload() } @@ -843,7 +852,7 @@ const file_meshstream_meshstream_proto_rawDesc = "" + "\aversion\x18\x03 \x01(\tR\aversion\x12\x16\n" + "\x06format\x18\x04 \x01(\tR\x06format\x12\x18\n" + "\achannel\x18\x05 \x01(\tR\achannel\x12\x17\n" + - "\auser_id\x18\x06 \x01(\tR\x06userId\"\xfb\r\n" + + "\auser_id\x18\x06 \x01(\tR\x06userId\"\x94\x0e\n" + "\x04Data\x12\x1d\n" + "\n" + "channel_id\x18\x01 \x01(\tR\tchannelId\x12\x1d\n" + @@ -906,7 +915,8 @@ const file_meshstream_meshstream_proto_rawDesc = "" + "\x04dest\x185 \x01(\rR\x04dest\x12\x16\n" + "\x06source\x186 \x01(\rR\x06source\x12#\n" + "\rwant_response\x187 \x01(\bR\fwantResponse\x12!\n" + - "\fdecode_error\x18< \x01(\tR\vdecodeErrorB\t\n" + + "\fdecode_error\x18< \x01(\tR\vdecodeError\x12\x17\n" + + "\arx_time\x18= \x01(\x04R\x06rxTimeB\t\n" + "\apayloadB(Z&proto/generated/meshstream;meshtreampbb\x06proto3" var ( diff --git a/proto/meshstream/meshstream.proto b/proto/meshstream/meshstream.proto index 8d7cc9e..9259a95 100644 --- a/proto/meshstream/meshstream.proto +++ b/proto/meshstream/meshstream.proto @@ -91,4 +91,7 @@ message Data { // Error tracking string decode_error = 60; + + // Reception timestamp (added by decoder) + uint64 rx_time = 61; } \ No newline at end of file diff --git a/web/src/components/PacketList.tsx b/web/src/components/PacketList.tsx index cd7ceaa..9a5c5b9 100644 --- a/web/src/components/PacketList.tsx +++ b/web/src/components/PacketList.tsx @@ -43,6 +43,36 @@ export const PacketList: React.FC = () => { }, [hashString] ); + + // Get the earliest reception time from the packets + const getEarliestTime = useCallback((): string => { + if (packets.length === 0) return ""; + + // Find the packet with the earliest rxTime or time + let earliestTime: number | undefined; + + packets.forEach(packet => { + // Check for rxTime first, then fall back to other timestamp fields + const packetTime = packet.data.rxTime || + (packet.data.telemetry?.time) || + undefined; + + if (packetTime && (!earliestTime || packetTime < earliestTime)) { + earliestTime = packetTime; + } + }); + + if (!earliestTime) { + return "unknown time"; + } + + // Format the time in a nice way + const date = new Date(earliestTime * 1000); + return date.toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit' + }); + }, [packets]); // 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 @@ -102,7 +132,12 @@ export const PacketList: React.FC = () => {
- {packets.length} packets received, since 6:00am + {packets.length} packets received + {packets.length > 0 && ( + <> + , since {getEarliestTime()} + + )}
{/* Show buffered count when paused */} diff --git a/web/src/components/packets/PacketCard.tsx b/web/src/components/packets/PacketCard.tsx index 218e321..a5dc5d1 100644 --- a/web/src/components/packets/PacketCard.tsx +++ b/web/src/components/packets/PacketCard.tsx @@ -53,9 +53,19 @@ export const PacketCard: React.FC = ({ )}
- {/* Right side: ID and Type */} + {/* Right side: ID, Time, and Type */}
- {data.id || "None"} +
+ {data.id || "None"} + {data.rxTime && ( + + {new Date(data.rxTime * 1000).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit' + })} + + )} +
{label} diff --git a/web/src/components/packets/TelemetryPacket.tsx b/web/src/components/packets/TelemetryPacket.tsx index aec4013..404fb67 100644 --- a/web/src/components/packets/TelemetryPacket.tsx +++ b/web/src/components/packets/TelemetryPacket.tsx @@ -26,13 +26,16 @@ export const TelemetryPacket: React.FC = ({ packet }) => { key => telemetry.environmentMetrics![key as keyof typeof telemetry.environmentMetrics] !== undefined ); + // Use the reception timestamp if available, otherwise fall back to the telemetry time + const timestamp = data.rxTime || telemetry.time; + // Return the appropriate component based on telemetry type if (hasDeviceMetrics && telemetry.deviceMetrics) { return ( ); } @@ -42,7 +45,7 @@ export const TelemetryPacket: React.FC = ({ packet }) => { ); } @@ -58,8 +61,8 @@ export const TelemetryPacket: React.FC = ({ packet }) => { >
Unknown telemetry data received at{' '} - {telemetry.time - ? new Date(telemetry.time * 1000).toLocaleTimeString() + {timestamp + ? new Date(timestamp * 1000).toLocaleTimeString() : 'unknown time' }
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 1b4c51b..c4f82a0 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -306,6 +306,9 @@ export interface Data { // Error tracking decodeError?: string; + + // Reception timestamp (added by decoder) + rxTime?: number; } // Packet represents a complete decoded MQTT message