refactor: implement packet parsing heuristics and introduce device-level state caching to throttle database writes and vitals updates
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Findings summary and analysis artifact
|
||||
|
||||
"""
|
||||
=== COMPLETE PACKET STRUCTURE DECODED ===
|
||||
|
||||
From monitor image: ECG=88, SpO2=98, NIBP=110/67 (MAP=82), TEMP=disconnected
|
||||
|
||||
--- 45-BYTE PACKET (Subtype 22) = REAL-TIME VITALS + NIBP ---
|
||||
The 45-byte packet uses BIG-ENDIAN u16 format!
|
||||
Bytes 14-19 contain the key vitals as individual bytes:
|
||||
|
||||
Packet at index 147 (captured around the same time as the image):
|
||||
Bytes 14-19: [3, 110, 0, 67, 0, 82]
|
||||
|
||||
byte[14] = 3 → seconds (3)
|
||||
byte[15] = 110 → NIBP Systolic = 110 ✅ (matches monitor 110)
|
||||
byte[16] = 0 → high byte padding
|
||||
byte[17] = 67 → NIBP Diastolic = 67 ✅ (matches monitor 67)
|
||||
byte[18] = 0 → high byte padding
|
||||
byte[19] = 82 → NIBP MAP = 82 ✅ (matches monitor 82)
|
||||
|
||||
Earlier packet at index 3:
|
||||
Bytes 14-19: [40, 123, 0, 69, 0, 83]
|
||||
byte[15] = 123 → Not NIBP (too high for that reading)
|
||||
|
||||
Wait - the readings CHANGED between packets. Let me check the full structure:
|
||||
Index 3: bytes[15]=123, bytes[17]=69, bytes[19]=83
|
||||
Index 147: bytes[15]=110, bytes[17]=67, bytes[19]=82
|
||||
|
||||
The index 147 values perfectly match the monitor!
|
||||
And index 3 was captured earlier when NIBP values may have been different (cuff measurement changes).
|
||||
|
||||
Full 45-byte packet layout:
|
||||
[0-1] Packet length (LE u16) = 45
|
||||
[2-3] Marker: 01 46
|
||||
[4-7] Sub-header
|
||||
[8-9] Year (LE u16) = 2026
|
||||
[10] Month = 7
|
||||
[11] Day = 9
|
||||
[12] Hour = 18 (0x12)
|
||||
[13] Minute = 42 (0x2a) or 47 (0x2f)
|
||||
[14] Second = 40 (0x28) or 3 (0x03)
|
||||
[15] NIBP Systolic (single byte!)
|
||||
[16-17] NIBP Diastolic (BE u16, but high byte usually 0)
|
||||
[18-19] NIBP MAP (BE u16, but high byte usually 0)
|
||||
[20-21] Alarm limit: SpO2 HIGH (LE u16) = 156 → WAIT that's 0x9c = 156
|
||||
|
||||
Hmm, let me re-examine. Looking at bytes starting at offset 14:
|
||||
Packet 147: [03, 6e, 00, 43, 00, 52, 00, 9c, 00, 5a, 00, 02, 00, 5a, 00, 32, 00, 02, 00, 6a, 00, 3c, 00, 02, ...]
|
||||
|
||||
0x6e=110, 0x43=67, 0x52=82 → These are NIBP values!
|
||||
|
||||
So the structure from offset 14:
|
||||
[14] Seconds
|
||||
[15] NIBP Systolic = 110
|
||||
[16] 0x00
|
||||
[17] NIBP Diastolic = 67
|
||||
[18] 0x00
|
||||
[19] NIBP MAP = 82
|
||||
[20-21] 0x009c = 156 (HR alarm HIGH)
|
||||
[22-23] 0x005a = 90 (HR alarm LOW)
|
||||
[24-25] 0x0002 = 2 (flag)
|
||||
[26-27] 0x005a = 90 (SpO2 alarm...)
|
||||
[28-29] 0x0032 = 50 (SpO2 alarm LOW)
|
||||
[30-31] 0x0002 = 2 (flag)
|
||||
[32-33] 0x006a = 106 (Resp alarm HIGH?)
|
||||
[34-35] 0x003c = 60 (Resp alarm LOW?)
|
||||
[36-37] 0x0002 = 2 (flag)
|
||||
|
||||
--- 286-BYTE PACKET (Subtype 21) = WAVEFORM + SpO2/HR SUMMARY ---
|
||||
[264-265] SpO2 PR (pulse rate from SpO2 sensor) - changes: 255→98→99
|
||||
This is NOT ECG HR, it's the SpO2 pulse rate!
|
||||
[266-267] ECG HR: 9999 (sentinel = ECG disconnected or not reporting HR via ECG)
|
||||
[268-269] 100 = SpO2 HIGH alarm limit (NOT live SpO2!)
|
||||
[270-271] 90 = SpO2 LOW alarm limit
|
||||
[272-273] 2 = flag
|
||||
[274-275] 120 = ECG HR HIGH alarm limit
|
||||
[276-277] 50 = ECG HR LOW alarm limit
|
||||
[278-279] 1 = flag
|
||||
|
||||
So offset 264 in the 286-byte packet gives us:
|
||||
SpO2 Pulse Rate (which equals SpO2% when sensor is connected)
|
||||
|
||||
But WAIT - the value 98 at offset 264 matches the SpO2 value (98%), not HR!
|
||||
And ECG HR (88) is NOT in this packet at all.
|
||||
|
||||
Let me check: at index 74, offset 264 = 98, which matches SpO2 = 98%.
|
||||
At index 186, offset 264 = 99, but the SpO2 was still 98 on monitor...
|
||||
|
||||
Actually, offset 264 could be the SpO2 Pulse Rate (PR), which is the heart rate
|
||||
derived from the SpO2 sensor. When ECG is also connected, the ECG HR is shown
|
||||
separately. But the PR from SpO2 sensor = 98 is close to the SpO2% = 98.
|
||||
|
||||
Hmm, actually both the SpO2 percentage AND the pulse rate from the SpO2 sensor
|
||||
can have similar values. We need to figure out which is which.
|
||||
|
||||
--- 56-BYTE PACKET (Subtype 23) = FLOAT32 NIBP/TEMP ---
|
||||
[8-11] f32=9999.0 → NIBP Systolic (sentinel = no measurement)
|
||||
[12-15] f32=9999.0 → NIBP Diastolic (sentinel)
|
||||
[16-19] f32=9999.0 → NIBP MAP (sentinel)
|
||||
[20-23] f32=39.0 → TEMP alarm HIGH limit (NOT actual temp!)
|
||||
[24-27] f32=36.0 → TEMP alarm LOW limit (NOT actual temp!)
|
||||
|
||||
--- 989-BYTE PACKET (Subtype 20) = Full waveform, no vitals in tail ---
|
||||
No vital sign values found in this packet.
|
||||
|
||||
=== CONCLUSIONS ===
|
||||
1. The 286-byte packet at offset 264 contains SpO2 PR (pulse rate), NOT ECG HR
|
||||
2. ECG HR (88 bpm) is NOT transmitted in any of these packets!
|
||||
The ECG HR is only available on the monitor's own display.
|
||||
3. NIBP values (110/67/82) are in the 45-byte packet at bytes 15/17/19
|
||||
4. Temperature 39.0 in the 56-byte packet is the ALARM HIGH limit, not actual temp
|
||||
5. The actual temperature value is NOT transmitted (temp probe disconnected)
|
||||
6. SpO2 percentage is NOT directly transmitted - only the SpO2 PR is at offset 264
|
||||
(but these are often the same value when the sensor is working)
|
||||
"""
|
||||
print("Analysis complete - see code comments for findings")
|
||||
@@ -0,0 +1,11 @@
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/hl7_forwarder.log", "r") as f:
|
||||
content = f.read()
|
||||
|
||||
warnings = re.findall(r"unrecognized. Len: (\d+)", content)
|
||||
lengths = [int(l) for l in warnings]
|
||||
print("Unique unrecognized packet lengths and their frequencies:")
|
||||
for length, count in Counter(lengths).most_common():
|
||||
print(f" Length {length}: {count} times")
|
||||
@@ -0,0 +1,39 @@
|
||||
import re
|
||||
import struct
|
||||
|
||||
# Helper to read uint16 from bytes
|
||||
def read_u16_le(data: bytes, offset: int) -> int:
|
||||
if offset + 2 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<H', data, offset)[0]
|
||||
|
||||
# Read the log file
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/hl7_forwarder.log", "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Find all occurrences of Hex head logging
|
||||
hex_heads = re.findall(r"Raw hex \(first 32 bytes\): ([0-9a-fA-F]+)", content)
|
||||
warnings = re.findall(r"unrecognized. Len: (\d+), Hex head: ([0-9a-fA-F]+)", content)
|
||||
|
||||
print(f"Found {len(hex_heads)} hex heads from server logging")
|
||||
print(f"Found {len(warnings)} unrecognized packet hex heads")
|
||||
|
||||
# Also find all data packets printed or parsed in logs
|
||||
# Let's inspect some of the unrecognized packets of size 56 and 45
|
||||
print("\nSample of Len 56 packets:")
|
||||
count = 0
|
||||
for len_str, hex_str in warnings:
|
||||
if len_str == "56":
|
||||
print(f" {hex_str}")
|
||||
count += 1
|
||||
if count >= 5:
|
||||
break
|
||||
|
||||
print("\nSample of Len 45 packets:")
|
||||
count = 0
|
||||
for len_str, hex_str in warnings:
|
||||
if len_str == "45":
|
||||
print(f" {hex_str}")
|
||||
count += 1
|
||||
if count >= 5:
|
||||
break
|
||||
@@ -0,0 +1,18 @@
|
||||
import sqlite3
|
||||
|
||||
def main():
|
||||
conn = sqlite3.connect("hl7_forwarder.db")
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute("SELECT id, device_id, ip_address, status, last_seen FROM devices")
|
||||
rows = cursor.fetchall()
|
||||
print("=== Registered Devices ===")
|
||||
for row in rows:
|
||||
print(f"ID: {row[0]} | Device ID: {row[1]} | IP: {row[2]} | Status: {row[3]} | Last Seen: {row[4]}")
|
||||
except Exception as e:
|
||||
print("Error:", e)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,56 @@
|
||||
import struct
|
||||
|
||||
def read_u16_le(data: bytes, offset: int) -> int:
|
||||
if offset + 2 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<H', data, offset)[0]
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
# Extract 45-byte packets (subtype 22)
|
||||
packets = []
|
||||
consumed = 0
|
||||
while consumed < len(stream) - 4:
|
||||
b0 = stream[consumed]
|
||||
b1 = stream[consumed + 1]
|
||||
b2 = stream[consumed + 2]
|
||||
b3 = stream[consumed + 3]
|
||||
|
||||
if (b2 == 0x04 or b2 == 0x01) and b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 45 and consumed + pkt_len <= len(stream):
|
||||
packets.append(stream[consumed:consumed + pkt_len])
|
||||
consumed += pkt_len
|
||||
else:
|
||||
if pkt_len < 5 or pkt_len > 8192:
|
||||
consumed += 1
|
||||
else:
|
||||
consumed += pkt_len
|
||||
else:
|
||||
consumed += 1
|
||||
|
||||
print(f"Found {len(packets)} 45-byte packets:")
|
||||
seen_timestamps = set()
|
||||
for p in packets:
|
||||
# Decode timestamp
|
||||
year = read_u16_le(p, 8)
|
||||
month = p[10]
|
||||
day = p[11]
|
||||
hour = p[12]
|
||||
minute = p[13]
|
||||
second = p[14]
|
||||
ts_str = f"{year:04d}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||
|
||||
if ts_str in seen_timestamps:
|
||||
continue
|
||||
seen_timestamps.add(ts_str)
|
||||
|
||||
# Let's decode all uint16 fields from offset 15 onwards
|
||||
fields = []
|
||||
for offset in range(15, len(p) - 1, 2):
|
||||
val = read_u16_le(p, offset)
|
||||
fields.append(f"off{offset}:{val}")
|
||||
|
||||
print(f"Timestamp: {ts_str} | Payload: {p[15:].hex()}")
|
||||
print(" Decoded uint16s: " + ", ".join(fields))
|
||||
@@ -0,0 +1,40 @@
|
||||
import struct
|
||||
|
||||
def read_f32_le(data: bytes, offset: int) -> float:
|
||||
if offset + 4 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<f', data, offset)[0]
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
# Extract 56-byte packets (subtype 23)
|
||||
packets = []
|
||||
consumed = 0
|
||||
while consumed < len(stream) - 4:
|
||||
b0 = stream[consumed]
|
||||
b1 = stream[consumed + 1]
|
||||
b2 = stream[consumed + 2]
|
||||
b3 = stream[consumed + 3]
|
||||
|
||||
if (b2 == 0x04 or b2 == 0x01) and b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 56 and consumed + pkt_len <= len(stream):
|
||||
packets.append(stream[consumed:consumed + pkt_len])
|
||||
consumed += pkt_len
|
||||
else:
|
||||
if pkt_len < 5 or pkt_len > 8192:
|
||||
consumed += 1
|
||||
else:
|
||||
consumed += pkt_len
|
||||
else:
|
||||
consumed += 1
|
||||
|
||||
print(f"Found {len(packets)} 56-byte packets:")
|
||||
# Print the float32 values of the first packet at every possible offset
|
||||
if packets:
|
||||
p = packets[0]
|
||||
print(f"Sample 56-byte packet: {p.hex()}")
|
||||
for offset in range(8, len(p) - 3, 4):
|
||||
val = read_f32_le(p, offset)
|
||||
print(f" Offset {offset:02d}: {val}")
|
||||
@@ -0,0 +1,87 @@
|
||||
import struct
|
||||
|
||||
def read_u16_le(data: bytes, offset: int) -> int:
|
||||
if offset + 2 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<H', data, offset)[0]
|
||||
|
||||
def read_f32_le(data: bytes, offset: int) -> float:
|
||||
if offset + 4 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<f', data, offset)[0]
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
print(f"Total stream size: {len(stream)} bytes")
|
||||
|
||||
# Extract packets
|
||||
packets = []
|
||||
consumed = 0
|
||||
while consumed < len(stream) - 4:
|
||||
b0 = stream[consumed]
|
||||
b1 = stream[consumed + 1]
|
||||
b2 = stream[consumed + 2]
|
||||
b3 = stream[consumed + 3]
|
||||
|
||||
if (b2 == 0x04 or b2 == 0x01) and b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len < 5 or pkt_len > 8192:
|
||||
consumed += 1
|
||||
continue
|
||||
|
||||
if consumed + pkt_len <= len(stream):
|
||||
packets.append(stream[consumed:consumed + pkt_len])
|
||||
consumed += pkt_len
|
||||
else:
|
||||
break
|
||||
else:
|
||||
consumed += 1
|
||||
|
||||
print(f"Successfully extracted {len(packets)} packets")
|
||||
|
||||
# Analyze packet types and sizes
|
||||
by_size = {}
|
||||
for p in packets:
|
||||
l = len(p)
|
||||
subtype = p[6] if l > 6 else -1
|
||||
key = (l, subtype)
|
||||
if key not in by_size:
|
||||
by_size[key] = []
|
||||
by_size[key].append(p)
|
||||
|
||||
print("\nPacket breakdown (length, subtype): count")
|
||||
for key, list_p in by_size.items():
|
||||
print(f" Length {key[0]}, Subtype {key[1]}: {len(list_p)} packets")
|
||||
|
||||
# Dump first 2 packets of each type
|
||||
for key, list_p in by_size.items():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Sample packets for Length {key[0]}, Subtype {key[1]}:")
|
||||
print(f"{'='*60}")
|
||||
for idx, p in enumerate(list_p[:2]):
|
||||
print(f"Packet #{idx+1} hex:")
|
||||
print(" " + p.hex())
|
||||
|
||||
# Let's search this packet for NIBP values (108, 53, 75) as uint16
|
||||
print(" Looking for NIBP values (108, 53, 75) as uint16:")
|
||||
found_u16 = []
|
||||
for offset in range(0, len(p) - 1, 1):
|
||||
val = read_u16_le(p, offset)
|
||||
if val in [108, 53, 75, 88, 100]:
|
||||
found_u16.append(f"offset {offset}: {val}")
|
||||
if found_u16:
|
||||
print(" uint16 matches: " + ", ".join(found_u16))
|
||||
|
||||
# Let's search as float32
|
||||
print(" Looking for float32 values (108.0, 53.0, 75.0, 88.0, 100.0):")
|
||||
found_f32 = []
|
||||
for offset in range(0, len(p) - 3, 1):
|
||||
val = read_f32_le(p, offset)
|
||||
if val is not None:
|
||||
# check if close to target values
|
||||
for target in [108.0, 53.0, 75.0, 88.0, 100.0]:
|
||||
if abs(val - target) < 0.1:
|
||||
found_f32.append(f"offset {offset}: {val:.1f}")
|
||||
if found_f32:
|
||||
print(" float32 matches: " + ", ".join(found_f32))
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Search the entire 286-byte and 989-byte packets for the ECG HR value (88).
|
||||
The ECG HR must be somewhere in the packet data.
|
||||
"""
|
||||
import struct
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
# Find all 286-byte packets with SpO2=98 at offset 264
|
||||
# (indicating a time when the monitor was showing ECG=88, SpO2=98)
|
||||
packets = []
|
||||
i = 0
|
||||
while i < len(stream) - 8:
|
||||
if (stream[i + 2] == 0x04 or stream[i + 2] == 0x01) and stream[i + 3] == 0x46:
|
||||
pkt_len = struct.unpack_from('<H', stream, i)[0]
|
||||
if 30 < pkt_len < 1050 and i + pkt_len <= len(stream):
|
||||
packets.append((pkt_len, stream[i:i + pkt_len]))
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Search for byte value 88 (0x58) in 286-byte packets
|
||||
print("=== Searching ALL offsets in 286-byte packets for u16=88 ===")
|
||||
for idx, (l, pkt) in enumerate(packets):
|
||||
if l == 286:
|
||||
for off in range(4, l-1, 2):
|
||||
v = struct.unpack_from('<H', pkt, off)[0]
|
||||
if v == 88:
|
||||
print(f" Packet {idx}, offset {off}: u16={v}")
|
||||
# Also search single bytes
|
||||
for off in range(4, l):
|
||||
if pkt[off] == 88:
|
||||
print(f" Packet {idx}, byte[{off}] = 88 (0x58)")
|
||||
break # Just check first one
|
||||
|
||||
# Search 989-byte packets
|
||||
print("\n=== Searching 989-byte packets for byte=88 in offsets 0-30 and 950-989 ===")
|
||||
for idx, (l, pkt) in enumerate(packets):
|
||||
if l == 989:
|
||||
for off in range(0, 30):
|
||||
if pkt[off] == 88:
|
||||
print(f" Packet {idx}, byte[{off}] = 88")
|
||||
for off in range(950, l):
|
||||
if pkt[off] == 88:
|
||||
print(f" Packet {idx}, byte[{off}] = 88")
|
||||
break
|
||||
|
||||
# Search 45-byte packets
|
||||
print("\n=== Searching 45-byte packets for byte=88 ===")
|
||||
for idx, (l, pkt) in enumerate(packets):
|
||||
if l == 45:
|
||||
for off in range(8, l):
|
||||
if pkt[off] == 88:
|
||||
print(f" Packet {idx}, byte[{off}] = 88")
|
||||
break
|
||||
|
||||
# The ECG HR may also be encoded differently. Let's check what's at byte
|
||||
# positions right before the alarm limits in the 45-byte packet
|
||||
print("\n=== 45-byte packet: All non-zero bytes with their positions ===")
|
||||
for idx, (l, pkt) in enumerate(packets):
|
||||
if l == 45:
|
||||
for off in range(8, l):
|
||||
if pkt[off] != 0:
|
||||
print(f" byte[{off}] = {pkt[off]} (0x{pkt[off]:02x})")
|
||||
break
|
||||
|
||||
# Check the 45-byte packet at index 147 specifically (when NIBP was 110/67/82)
|
||||
print("\n=== 45-byte packet at the time of NIBP 110/67/82 ===")
|
||||
count = 0
|
||||
for idx, (l, pkt) in enumerate(packets):
|
||||
if l == 45:
|
||||
count += 1
|
||||
if count > 36: # Skip to around packet index 147
|
||||
for off in range(8, l):
|
||||
if pkt[off] != 0:
|
||||
print(f" byte[{off}] = {pkt[off]} (0x{pkt[off]:02x})")
|
||||
break
|
||||
@@ -0,0 +1,58 @@
|
||||
import struct
|
||||
|
||||
filepath = "/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin"
|
||||
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
stream = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
packets_288 = []
|
||||
packets_989 = []
|
||||
i = 0
|
||||
while i < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[i], stream[i+1], stream[i+2], stream[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 288 and i + pkt_len <= len(stream):
|
||||
packets_288.append(stream[i:i + pkt_len])
|
||||
elif pkt_len == 989 and i + pkt_len <= len(stream):
|
||||
packets_989.append(stream[i:i + pkt_len])
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
print(f"Loaded {len(packets_288)} 288-byte packets and {len(packets_989)} 989-byte packets.")
|
||||
|
||||
# Search all offsets in 288-byte packets for any u16 LE value between 70 and 95
|
||||
print("\nScanning 288-byte packets for u16 LE values in range [75, 95] across all packets:")
|
||||
found_offsets_288 = {}
|
||||
for idx, pkt in enumerate(packets_288):
|
||||
for off in range(4, len(pkt) - 1, 2):
|
||||
val = struct.unpack_from('<H', pkt, off)[0]
|
||||
if 75 <= val <= 95:
|
||||
found_offsets_288.setdefault(off, []).append((idx, val))
|
||||
|
||||
for off, matches in found_offsets_288.items():
|
||||
if len(matches) > 5:
|
||||
# print first few matches and total count
|
||||
vals = [m[1] for m in matches]
|
||||
print(f" Offset {off}: found {len(matches)} times. Values: {set(vals)}")
|
||||
|
||||
# Search all offsets in 989-byte packets for any u16 LE value between 70 and 95
|
||||
print("\nScanning 989-byte packets for u16 LE values in range [75, 95] across all packets:")
|
||||
found_offsets_989 = {}
|
||||
for idx, pkt in enumerate(packets_989):
|
||||
# ECG packets are mostly waveform data in the first 900 bytes, so let's check from 900 to 988
|
||||
for off in range(900, len(pkt) - 1, 2):
|
||||
val = struct.unpack_from('<H', pkt, off)[0]
|
||||
if 75 <= val <= 95:
|
||||
found_offsets_989.setdefault(off, []).append((idx, val))
|
||||
|
||||
for off, matches in found_offsets_989.items():
|
||||
if len(matches) > 5:
|
||||
vals = [m[1] for m in matches]
|
||||
print(f" Offset {off}: found {len(matches)} times. Values: {set(vals)}")
|
||||
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
|
||||
filepath = "/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin"
|
||||
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
f.seek(0, 2)
|
||||
size = f.tell()
|
||||
# Read the last 2000 bytes
|
||||
f.seek(max(0, size - 2000))
|
||||
data = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Total file size: {size} bytes")
|
||||
print(f"Read last {len(data)} bytes of stream.")
|
||||
|
||||
# Let's search for potential packet headers in the last 2000 bytes.
|
||||
# Typically headers are like: [len_lo] [len_hi] [04/01] [46]
|
||||
# Let's print out the hex around any occurrences of 0x46 or 0x47.
|
||||
print("\nScanning for potential 0x46 or 0x47 headers...")
|
||||
for i in range(len(data) - 4):
|
||||
b0, b1, b2, b3 = data[i], data[i+1], data[i+2], data[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
print(f"Index {i} (absolute {size - 2000 + i}): Header match! Bytes: {b0:02x} {b1:02x} {b2:02x} {b3:02x} -> Length: {pkt_len}, Subtype: {b2}")
|
||||
# print hex representation of next 16 bytes
|
||||
next_bytes = data[i:i+20]
|
||||
print(f" Hex: {next_bytes.hex()}")
|
||||
elif b3 == 0x47:
|
||||
print(f"Index {i} (absolute {size - 2000 + i}): End-marker? Bytes: {b0:02x} {b1:02x} {b2:02x} {b3:02x}")
|
||||
@@ -0,0 +1,83 @@
|
||||
import socket
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT = 12345
|
||||
|
||||
VT = b'\x0b'
|
||||
FS_CR = b'\x1c\x0d'
|
||||
|
||||
def generate_hl7_message(patient_id="PAT-12345", device_id="MOCK-CMS7000"):
|
||||
# Generate realistic vital signs with small random variations
|
||||
hr = random.randint(70, 95)
|
||||
spo2 = random.randint(96, 99)
|
||||
sys = random.randint(115, 125)
|
||||
dia = random.randint(75, 83)
|
||||
map_bp = int(dia + (sys - dia) / 3)
|
||||
resp = random.randint(14, 18)
|
||||
temp = round(random.uniform(36.5, 37.1), 1)
|
||||
|
||||
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
|
||||
# Construct HL7 message segments
|
||||
msh = f"MSH|^~\\&|{device_id}|MOCK_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|{now_str}||ORU^R01|MSG{now_str}|P|2.3.1\r"
|
||||
pid = f"PID|||{patient_id}^Doe^John||19800101|M\r"
|
||||
pv1 = "PV1||I|ICU^Bed1^Room1||||||||||||||||1001\r"
|
||||
|
||||
# OBX segments for each vital parameter
|
||||
obx_hr = f"OBX|1|NM|HR^Heart Rate|1|{hr}|bpm|60-100|N|||F\r"
|
||||
obx_spo2 = f"OBX|2|NM|SPO2^Oxygen Saturation|1|{spo2}|%|95-100|N|||F\r"
|
||||
obx_sys = f"OBX|3|NM|SYS^Systolic BP|1|{sys}|mmHg|90-140|N|||F\r"
|
||||
obx_dia = f"OBX|4|NM|DIA^Diastolic BP|1|{dia}|mmHg|60-90|N|||F\r"
|
||||
obx_map = f"OBX|5|NM|MAP^Mean Arterial Pressure|1|{map_bp}|mmHg|70-105|N|||F\r"
|
||||
obx_resp = f"OBX|6|NM|RESP^Respiratory Rate|1|{resp}|/min|12-20|N|||F\r"
|
||||
obx_temp = f"OBX|7|NM|TEMP^Temperature|1|{temp}|C|36.1-37.2|N|||F"
|
||||
|
||||
hl7_msg = msh + pid + pv1 + obx_hr + obx_spo2 + obx_sys + obx_dia + obx_map + obx_resp + obx_temp
|
||||
return hl7_msg
|
||||
|
||||
def main():
|
||||
print(f"Starting mock patient monitor simulator...")
|
||||
print(f"Connecting to Contec receiver at {HOST}:{PORT}...")
|
||||
|
||||
while True:
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
print(f"Connected to Contec server at {HOST}:{PORT}")
|
||||
|
||||
while True:
|
||||
hl7_msg = generate_hl7_message()
|
||||
# Frame message with MLLP wrappers (VT ... FS_CR)
|
||||
framed_msg = VT + hl7_msg.encode('utf-8') + FS_CR
|
||||
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] Sending mock HL7 vitals:")
|
||||
# Print individual observations for clear console trace
|
||||
for line in hl7_msg.split('\r'):
|
||||
if line.startswith("OBX"):
|
||||
print(f" {line}")
|
||||
|
||||
s.sendall(framed_msg)
|
||||
|
||||
# Check for ACK response
|
||||
try:
|
||||
s.settimeout(1.0)
|
||||
response = s.recv(4096)
|
||||
if response:
|
||||
print(" [ACK Received]")
|
||||
except socket.timeout:
|
||||
pass
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
except (ConnectionRefusedError, ConnectionResetError) as e:
|
||||
print(f"Connection error: {e}. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {e}. Retrying in 3 seconds...")
|
||||
time.sleep(3)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,73 @@
|
||||
import socket
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime
|
||||
import threading
|
||||
|
||||
HOST = "127.0.0.1"
|
||||
PORT = 12345
|
||||
|
||||
VT = b'\x0b'
|
||||
FS_CR = b'\x1c\x0d'
|
||||
|
||||
def generate_hl7_message(patient_id, device_id):
|
||||
hr = random.randint(70, 95)
|
||||
spo2 = random.randint(96, 99)
|
||||
sys = random.randint(115, 125)
|
||||
dia = random.randint(75, 83)
|
||||
map_bp = int(dia + (sys - dia) / 3)
|
||||
resp = random.randint(14, 18)
|
||||
temp = round(random.uniform(36.5, 37.1), 1)
|
||||
|
||||
now_str = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
|
||||
msh = f"MSH|^~\\&|{device_id}|MOCK_FACILITY|RECEIVING_APP|RECEIVING_FACILITY|{now_str}||ORU^R01|MSG{now_str}|P|2.3.1\r"
|
||||
pid = f"PID|||{patient_id}^Doe^John||19800101|M\r"
|
||||
pv1 = "PV1||I|ICU^Bed1^Room1||||||||||||||||1001\r"
|
||||
|
||||
obx_hr = f"OBX|1|NM|HR^Heart Rate|1|{hr}|bpm|60-100|N|||F\r"
|
||||
obx_spo2 = f"OBX|2|NM|SPO2^Oxygen Saturation|1|{spo2}|%|95-100|N|||F\r"
|
||||
obx_sys = f"OBX|3|NM|SYS^Systolic BP|1|{sys}|mmHg|90-140|N|||F\r"
|
||||
obx_dia = f"OBX|4|NM|DIA^Diastolic BP|1|{dia}|mmHg|60-90|N|||F\r"
|
||||
obx_map = f"OBX|5|NM|MAP^Mean Arterial Pressure|1|{map_bp}|mmHg|70-105|N|||F\r"
|
||||
obx_resp = f"OBX|6|NM|RESP^Respiratory Rate|1|{resp}|/min|12-20|N|||F\r"
|
||||
obx_temp = f"OBX|7|NM|TEMP^Temperature|1|{temp}|C|36.1-37.2|N|||F"
|
||||
|
||||
hl7_msg = msh + pid + pv1 + obx_hr + obx_spo2 + obx_sys + obx_dia + obx_map + obx_resp + obx_temp
|
||||
return hl7_msg
|
||||
|
||||
def run_monitor(patient_id, device_id, interval):
|
||||
print(f"Starting simulated {device_id}...")
|
||||
while True:
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
print(f"[{device_id}] Connected to server")
|
||||
while True:
|
||||
hl7_msg = generate_hl7_message(patient_id, device_id)
|
||||
framed_msg = VT + hl7_msg.encode('utf-8') + FS_CR
|
||||
s.sendall(framed_msg)
|
||||
try:
|
||||
s.settimeout(1.0)
|
||||
response = s.recv(4096)
|
||||
except socket.timeout:
|
||||
pass
|
||||
time.sleep(interval)
|
||||
except Exception as e:
|
||||
print(f"[{device_id}] Error: {e}. Reconnecting in 3 seconds...")
|
||||
time.sleep(3)
|
||||
|
||||
def main():
|
||||
t1 = threading.Thread(target=run_monitor, args=("PAT-1001", "CMS7000PLUS_127.0.0.1", 3), daemon=True)
|
||||
t2 = threading.Thread(target=run_monitor, args=("PAT-8500", "CMS8500_127.0.0.1", 4), daemon=True)
|
||||
t1.start()
|
||||
t2.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("Stopping simulators.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
import re
|
||||
import struct
|
||||
|
||||
def read_u16_le(data: bytes, offset: int) -> int:
|
||||
if offset + 2 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<H', data, offset)[0]
|
||||
|
||||
# Let's read the log line by line and reconstruct packets
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/hl7_forwarder.log", "r") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# We want to find raw reads and buffer state
|
||||
# The server logs:
|
||||
# "Received {len} bytes from ... on port 511 (buf={len})"
|
||||
# "Raw hex (first 32 bytes): {hex}"
|
||||
# Wait, did the server ever log the full packet data?
|
||||
# Let's look at the log lines.
|
||||
print("Searching for raw hex data logs...")
|
||||
hex_patterns = []
|
||||
for line in lines[:5000]:
|
||||
m = re.search(r"Raw hex \(first 32 bytes\): ([0-9a-fA-F]+)", line)
|
||||
if m:
|
||||
hex_patterns.append(m.group(1))
|
||||
|
||||
print(f"Total raw hex logs: {len(hex_patterns)}")
|
||||
if hex_patterns:
|
||||
print(f"Example raw hex: {hex_patterns[0]}")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,51 @@
|
||||
import struct
|
||||
|
||||
def search_scaled_values(p: bytes, val_sys: int, val_dia: int, val_map: int):
|
||||
# We will search for scaled values: val, val * 10, val * 100
|
||||
targets = [
|
||||
(val_sys, "Sys"), (val_sys * 10, "Sys*10"), (val_sys * 100, "Sys*100"),
|
||||
(val_dia, "Dia"), (val_dia * 10, "Dia*10"), (val_dia * 100, "Dia*100"),
|
||||
(val_map, "Map"), (val_map * 10, "Map*10"), (val_map * 100, "Map*100")
|
||||
]
|
||||
|
||||
for offset in range(len(p) - 1):
|
||||
v = struct.unpack_from('<H', p, offset)[0]
|
||||
for target_val, name in targets:
|
||||
if v == target_val:
|
||||
print(f" Found {name} ({target_val}) at offset {offset}")
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
consumed = 0
|
||||
packets = []
|
||||
while consumed < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[consumed:consumed+4]
|
||||
if (b2 == 0x04 or b2 == 0x01) and b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
packets.append(stream[consumed:consumed+pkt_len])
|
||||
consumed += pkt_len
|
||||
else:
|
||||
consumed += 1
|
||||
|
||||
print(f"Scanning {len(packets)} packets for scaled BP (108, 53, 75)...")
|
||||
for idx, p in enumerate(packets):
|
||||
l = len(p)
|
||||
subtype = p[6] if l > 6 else -1
|
||||
|
||||
# We want to print only if something is found
|
||||
# Redirect output internally
|
||||
import io
|
||||
import sys
|
||||
old_stdout = sys.stdout
|
||||
new_stdout = io.StringIO()
|
||||
sys.stdout = new_stdout
|
||||
|
||||
search_scaled_values(p, 108, 53, 75)
|
||||
|
||||
output = new_stdout.getvalue()
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if output.strip():
|
||||
print(f"Packet #{idx} (Len={l}, Subtype={subtype}):")
|
||||
print(output, end="")
|
||||
@@ -0,0 +1,63 @@
|
||||
import struct
|
||||
|
||||
def search_value_in_packet(p: bytes, val: int, label: str):
|
||||
# Search as uint8
|
||||
for offset in range(len(p)):
|
||||
if p[offset] == val:
|
||||
print(f" {label} found as uint8 at offset {offset}")
|
||||
|
||||
# Search as uint16 LE
|
||||
for offset in range(len(p) - 1):
|
||||
v = struct.unpack_from('<H', p, offset)[0]
|
||||
if v == val:
|
||||
print(f" {label} found as uint16 LE at offset {offset}")
|
||||
|
||||
# Search as float32 LE
|
||||
for offset in range(len(p) - 3):
|
||||
v = struct.unpack_from('<f', p, offset)[0]
|
||||
if abs(v - float(val)) < 0.01:
|
||||
print(f" {label} found as float32 LE at offset {offset}")
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
# Extract packets
|
||||
packets = []
|
||||
consumed = 0
|
||||
while consumed < len(stream) - 4:
|
||||
b0 = stream[consumed]
|
||||
b1 = stream[consumed + 1]
|
||||
b2 = stream[consumed + 2]
|
||||
b3 = stream[consumed + 3]
|
||||
|
||||
if (b2 == 0x04 or b2 == 0x01) and b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len < 5 or pkt_len > 8192:
|
||||
consumed += 1
|
||||
continue
|
||||
|
||||
if consumed + pkt_len <= len(stream):
|
||||
packets.append(stream[consumed:consumed + pkt_len])
|
||||
consumed += pkt_len
|
||||
else:
|
||||
break
|
||||
else:
|
||||
consumed += 1
|
||||
|
||||
print(f"Loaded {len(packets)} packets. Scanning for HR=88, Systolic=108, MAP=75...")
|
||||
|
||||
for idx, p in enumerate(packets):
|
||||
l = len(p)
|
||||
subtype = p[6] if l > 6 else -1
|
||||
|
||||
# Let's decode timestamp if it is a 45-byte packet
|
||||
ts_str = ""
|
||||
if l == 45:
|
||||
year = struct.unpack_from('<H', p, 8)[0]
|
||||
month, day, hour, minute, second = p[10], p[11], p[12], p[13], p[14]
|
||||
ts_str = f" @ {year}-{month:02d}-{day:02d} {hour:02d}:{minute:02d}:{second:02d}"
|
||||
|
||||
print(f"\nPacket #{idx} (Len={l}, Subtype={subtype}){ts_str}:")
|
||||
search_value_in_packet(p, 88, "HR (88)")
|
||||
search_value_in_packet(p, 108, "Systolic (108)")
|
||||
search_value_in_packet(p, 75, "MAP (75)")
|
||||
@@ -0,0 +1,21 @@
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
packets = []
|
||||
i = 0
|
||||
while i < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[i], stream[i+1], stream[i+2], stream[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 288 and i + pkt_len <= len(stream):
|
||||
packets.append(stream[i:i + pkt_len])
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if packets:
|
||||
pkt = packets[-1]
|
||||
print("Last 40 bytes of 288-byte packet:")
|
||||
for off in range(248, 288, 2):
|
||||
val = pkt[off] | (pkt[off+1] << 8)
|
||||
print(f" Offset {off}: hex={pkt[off]:02x} {pkt[off+1]:02x} -> u16 LE = {val}")
|
||||
@@ -0,0 +1,21 @@
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
packets = []
|
||||
i = 0
|
||||
while i < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[i], stream[i+1], stream[i+2], stream[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 989 and i + pkt_len <= len(stream):
|
||||
packets.append(stream[i:i + pkt_len])
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if packets:
|
||||
pkt = packets[-1]
|
||||
print("Last 100 bytes of 989-byte packet:")
|
||||
for off in range(889, 989, 2):
|
||||
val = pkt[off] | (pkt[off+1] << 8)
|
||||
print(f" Offset {off}: hex={pkt[off]:02x} {pkt[off+1]:02x} -> u16 LE = {val}")
|
||||
@@ -0,0 +1,41 @@
|
||||
import struct
|
||||
|
||||
filepath = "/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin"
|
||||
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
stream = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
# Find latest 288-byte and 989-byte packets
|
||||
pkt_288 = None
|
||||
pkt_989 = None
|
||||
i = 0
|
||||
while i < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[i], stream[i+1], stream[i+2], stream[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if pkt_len == 288 and i + pkt_len <= len(stream):
|
||||
pkt_288 = stream[i:i + pkt_len]
|
||||
elif pkt_len == 989 and i + pkt_len <= len(stream):
|
||||
pkt_989 = stream[i:i + pkt_len]
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if pkt_288:
|
||||
print("=== Scanning 288-byte packet (tail, offsets 250-287) for values 75-90 ===")
|
||||
for off in range(250, len(pkt_288) - 1, 2):
|
||||
val = struct.unpack_from('<H', pkt_288, off)[0]
|
||||
if 75 <= val <= 95:
|
||||
print(f" Offset {off}: u16 LE = {val}")
|
||||
|
||||
if pkt_989:
|
||||
print("\n=== Scanning 989-byte packet (tail, offsets 900-988) for values 75-90 ===")
|
||||
for off in range(900, len(pkt_989) - 1, 2):
|
||||
val = struct.unpack_from('<H', pkt_989, off)[0]
|
||||
if 75 <= val <= 95:
|
||||
print(f" Offset {off}: u16 LE = {val}")
|
||||
@@ -0,0 +1,49 @@
|
||||
import sys
|
||||
import struct
|
||||
sys.path.insert(0, "/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main")
|
||||
|
||||
from contec_parser import parse_contec_binary_packet
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
i = 0
|
||||
pkts = []
|
||||
while i < len(stream) - 8:
|
||||
if (stream[i + 2] == 0x04 or stream[i + 2] == 0x01) and stream[i + 3] == 0x46:
|
||||
pkt_len = struct.unpack_from('<H', stream, i)[0]
|
||||
if 30 < pkt_len < 1050 and i + pkt_len <= len(stream):
|
||||
pkts.append(stream[i:i+pkt_len])
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
print(f"Loaded {len(pkts)} packets.")
|
||||
|
||||
vitals_cache = {
|
||||
"heart_rate": None,
|
||||
"spo2": None,
|
||||
"temperature": None,
|
||||
"systolic_bp": None,
|
||||
"diastolic_bp": None,
|
||||
"map_bp": None,
|
||||
}
|
||||
|
||||
last_printed = {}
|
||||
|
||||
for idx, pkt in enumerate(pkts):
|
||||
vitals = parse_contec_binary_packet(pkt, "192.168.100.120")
|
||||
if vitals:
|
||||
# Merge like contec_server.py
|
||||
changed = False
|
||||
for field in vitals.present_fields or []:
|
||||
val = getattr(vitals, field)
|
||||
if vitals_cache[field] != val:
|
||||
vitals_cache[field] = val
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
current = {k: v for k, v in vitals_cache.items()}
|
||||
if current != last_printed:
|
||||
print(f"Pkt #{idx} (len={len(pkt)}) -> Cache: {current}")
|
||||
last_printed = current
|
||||
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
Verify the new parser offsets on the raw captured stream.
|
||||
New mappings:
|
||||
- HR: 989-byte packet, offset 904 (u16 LE)
|
||||
- SpO2: 286-byte packet, offset 264 (u16 LE)
|
||||
- Temperature: 56-byte packet, offset 8 (float32)
|
||||
- NIBP: 45-byte packet, offset 15, 17, 19 (u16 LE)
|
||||
"""
|
||||
import struct
|
||||
|
||||
def read_u16_le(data, offset):
|
||||
if offset + 2 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<H', data, offset)[0]
|
||||
|
||||
def read_f32_le(data, offset):
|
||||
if offset + 4 > len(data):
|
||||
return None
|
||||
return struct.unpack_from('<f', data, offset)[0]
|
||||
|
||||
with open("/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin", "rb") as f:
|
||||
stream = f.read()
|
||||
|
||||
# Parse packets
|
||||
i = 0
|
||||
pkts = []
|
||||
while i < len(stream) - 8:
|
||||
if (stream[i + 2] == 0x04 or stream[i + 2] == 0x01) and stream[i + 3] == 0x46:
|
||||
pkt_len = struct.unpack_from('<H', stream, i)[0]
|
||||
if 30 < pkt_len < 1050 and i + pkt_len <= len(stream):
|
||||
pkts.append((pkt_len, stream[i:i + pkt_len]))
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
vitals_cache = {
|
||||
"heart_rate": None,
|
||||
"spo2": None,
|
||||
"temperature": None,
|
||||
"systolic_bp": None,
|
||||
"diastolic_bp": None,
|
||||
"map_bp": None,
|
||||
}
|
||||
|
||||
last_printed = {}
|
||||
|
||||
for idx, (pkt_len, pkt) in enumerate(pkts):
|
||||
changed = False
|
||||
if pkt_len == 989:
|
||||
hr = read_u16_le(pkt, 904)
|
||||
# 65535 or 9999 means disconnected
|
||||
if hr is not None and hr != 65535 and hr != 9999 and 20 <= hr <= 300:
|
||||
if vitals_cache["heart_rate"] != float(hr):
|
||||
vitals_cache["heart_rate"] = float(hr)
|
||||
changed = True
|
||||
elif hr == 65535 or hr == 9999:
|
||||
if vitals_cache["heart_rate"] is not None:
|
||||
vitals_cache["heart_rate"] = None
|
||||
changed = True
|
||||
elif pkt_len == 286:
|
||||
spo2 = read_u16_le(pkt, 264)
|
||||
if spo2 is not None and spo2 != 65535 and spo2 != 255 and 50 <= spo2 <= 100:
|
||||
if vitals_cache["spo2"] != float(spo2):
|
||||
vitals_cache["spo2"] = float(spo2)
|
||||
changed = True
|
||||
elif spo2 == 65535 or spo2 == 255:
|
||||
if vitals_cache["spo2"] is not None:
|
||||
vitals_cache["spo2"] = None
|
||||
changed = True
|
||||
elif pkt_len == 56:
|
||||
temp = read_f32_le(pkt, 8)
|
||||
if temp is not None and abs(temp - 9999.0) > 1.0 and 30.0 < temp < 45.0:
|
||||
if vitals_cache["temperature"] != round(temp, 1):
|
||||
vitals_cache["temperature"] = round(temp, 1)
|
||||
changed = True
|
||||
elif temp is not None and abs(temp - 9999.0) <= 1.0:
|
||||
if vitals_cache["temperature"] is not None:
|
||||
vitals_cache["temperature"] = None
|
||||
changed = True
|
||||
elif pkt_len == 45:
|
||||
sys_bp = read_u16_le(pkt, 15)
|
||||
dia_bp = read_u16_le(pkt, 17)
|
||||
map_bp = read_u16_le(pkt, 19)
|
||||
|
||||
# 9999 means no NIBP measurement active
|
||||
if sys_bp is not None and sys_bp != 9999 and sys_bp > 0:
|
||||
vitals_cache["systolic_bp"] = float(sys_bp)
|
||||
changed = True
|
||||
elif sys_bp == 9999 or sys_bp == 0:
|
||||
if vitals_cache["systolic_bp"] is not None:
|
||||
vitals_cache["systolic_bp"] = None
|
||||
changed = True
|
||||
|
||||
if dia_bp is not None and dia_bp != 9999 and dia_bp > 0:
|
||||
vitals_cache["diastolic_bp"] = float(dia_bp)
|
||||
changed = True
|
||||
elif dia_bp == 9999 or dia_bp == 0:
|
||||
if vitals_cache["diastolic_bp"] is not None:
|
||||
vitals_cache["diastolic_bp"] = None
|
||||
changed = True
|
||||
|
||||
if map_bp is not None and map_bp != 9999 and map_bp > 0:
|
||||
vitals_cache["map_bp"] = float(map_bp)
|
||||
changed = True
|
||||
elif map_bp == 9999 or map_bp == 0:
|
||||
if vitals_cache["map_bp"] is not None:
|
||||
vitals_cache["map_bp"] = None
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
current_vitals = {k: v for k, v in vitals_cache.items()}
|
||||
if current_vitals != last_printed:
|
||||
print(f"Pkt #{idx} (len={pkt_len}) -> Vitals: {current_vitals}")
|
||||
last_printed = current_vitals
|
||||
@@ -0,0 +1,50 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from contec_parser import parse_contec_binary_packet
|
||||
|
||||
# Read captured stream
|
||||
filepath = "/home/prathiyuman/Prathiyuman/ContecMonitor/fiveparaminte-main/scratch/raw_contec_stream.bin"
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
stream = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Loaded raw stream of {len(stream)} bytes")
|
||||
|
||||
# We will scan the stream and feed packets into the parser
|
||||
# Since we know the packets start with 0x46 at index + 3, let's scan.
|
||||
i = 0
|
||||
packet_count = 0
|
||||
parsed_count = 0
|
||||
last_vitals = None
|
||||
|
||||
while i < len(stream) - 4:
|
||||
b0, b1, b2, b3 = stream[i], stream[i+1], stream[i+2], stream[i+3]
|
||||
if b3 == 0x46:
|
||||
pkt_len = b0 | (b1 << 8)
|
||||
if 30 < pkt_len < 1050 and i + pkt_len <= len(stream):
|
||||
pkt_data = stream[i:i + pkt_len]
|
||||
packet_count += 1
|
||||
|
||||
# Feed packet to parser
|
||||
vitals = parse_contec_binary_packet(pkt_data, "192.168.100.139")
|
||||
if vitals:
|
||||
parsed_count += 1
|
||||
# Check if fields changed
|
||||
v_dict = {k: v for k, v in vitals.model_dump().items() if v is not None and k not in ['device_id', 'patient_id', 'timestamp', 'ip_address', 'present_fields']}
|
||||
if v_dict and v_dict != last_vitals:
|
||||
print(f"Index {i} (len={pkt_len}) -> Vitals: {v_dict}")
|
||||
last_vitals = v_dict
|
||||
i += max(pkt_len, 4)
|
||||
else:
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
print(f"\nDone. Scanned {packet_count} packets, successfully parsed {parsed_count} packets.")
|
||||
Reference in New Issue
Block a user