118 lines
4.8 KiB
Python
118 lines
4.8 KiB
Python
# 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")
|