chenyc
2026-05-20 c8ba0f92b3f84273a78f06de25359db20c1b2a4d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
const assert = require('assert');
const {
  Jh2028Decoder,
  crc8,
} = require('../decoder');
const {
  buildBloodPressurePayload,
  buildFrame,
  buildRealtimePayload,
} = require('../tcp-simulator');
 
describe('Jh2028Decoder', () => {
  it('parses realtime frame with little-endian fields and scale rules', () => {
    const decoder = new Jh2028Decoder({ alModelPath: './alModel.json' });
    const frame = buildFrame(1, 0x01, 0x00, buildRealtimePayload());
    const [result] = decoder.push(frame);
 
    assert.strictEqual(result.publish, true);
    assert.strictEqual(result.messageType, 'realtime');
    assert.deepStrictEqual(result.metric, {
      AF: 36.5,
      F: 36.8,
      A: 2000,
      C: 500,
      B: 250,
      K: 90,
      L: 500,
      D: 320,
      H: -100,
      o: 120,
      J: -80,
      U: 2048,
      G: 138,
      Na: 140,
      HCO3: 25,
      O2Sat: 98.7,
      Hct: 36.5,
      Hb: 13.2,
      Tblood: 36.7,
      ktv: 1.5,
    });
  });
 
  it('parses blood pressure frame and ignores mean pressure and irregular pulse', () => {
    const decoder = new Jh2028Decoder({ alModelPath: './alModel.json' });
    const frame = buildFrame(2, 0x01, 0x01, buildBloodPressurePayload());
    const [result] = decoder.push(frame);
 
    assert.strictEqual(result.publish, true);
    assert.strictEqual(result.messageType, 'blood-pressure');
    assert.deepStrictEqual(result.metric, {
      N: 120,
      O: 80,
      P: 76,
    });
    assert.deepStrictEqual(result.ignored, {
      irregularPulse: 0,
      meanPressure: 93,
    });
  });
 
  it('skips blood pressure error code frames', () => {
    const decoder = new Jh2028Decoder({ alModelPath: './alModel.json' });
    const frame = buildFrame(3, 0x01, 0x01, Buffer.from([2, 0, 0, 0, 0]));
    const [result] = decoder.push(frame);
 
    assert.strictEqual(result.publish, false);
    assert.strictEqual(result.ok, true);
    assert.strictEqual(result.reason, 'blood-pressure-error-code');
    assert.strictEqual(result.errorCode, 2);
  });
 
  it('rejects invalid CRC frames', () => {
    const decoder = new Jh2028Decoder({ alModelPath: './alModel.json' });
    const frame = Buffer.from(buildFrame(1, 0x01, 0x00, buildRealtimePayload()));
    frame[frame.length - 1] ^= 0xFF;
    const [result] = decoder.push(frame);
 
    assert.strictEqual(result.publish, false);
    assert.strictEqual(result.ok, false);
    assert.strictEqual(result.reason, 'crc-invalid');
  });
 
  it('handles sticky packets and split packets', () => {
    const decoder = new Jh2028Decoder({ alModelPath: './alModel.json' });
    const realtimeFrame = buildFrame(1, 0x01, 0x00, buildRealtimePayload());
    const bpFrame = buildFrame(2, 0x01, 0x01, buildBloodPressurePayload());
    const firstPart = realtimeFrame.slice(0, 10);
    const secondPart = Buffer.concat([realtimeFrame.slice(10), bpFrame]);
 
    assert.deepStrictEqual(decoder.push(firstPart), []);
    const results = decoder.push(secondPart);
 
    assert.strictEqual(results.length, 2);
    assert.strictEqual(results[0].messageType, 'realtime');
    assert.strictEqual(results[1].messageType, 'blood-pressure');
  });
 
  it('calculates CRC8 from the PDF algorithm', () => {
    const frameWithoutCrc = Buffer.from([0x55, 0xAA, 0x07, 0x01, 0x01, 0x7F]);
    assert.strictEqual(crc8(frameWithoutCrc), 0x1B);
  });
});