-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzgm053uka.js
85 lines (74 loc) · 2.26 KB
/
zgm053uka.js
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
import { EventEmitter } from 'node:events'
import { HID } from 'node-hid'
import { usb } from 'usb'
const idVendor = 0x04d9 // Holtek Semiconductor, Inc.
const idProduct = 0xa052 // USB-zyTemp
const warmup = 30 // seconds until device is available
export default class ZGm053UKA extends EventEmitter {
constructor () {
super()
try {
this.device = this.initialize()
} catch (err) {
this.device = false
usb.on('attach', device => {
if (device.deviceDescriptor.idVendor === idVendor && device.deviceDescriptor.idProduct === idProduct) {
setTimeout(() => {
this.device = this.initialize()
}, warmup * 1000)
}
})
}
}
initialize () {
const magicTable = [0, 0, 0, 0, 0, 0, 0, 0]
const device = new HID(idVendor, idProduct)
device.on('error', error => {
this.emit('error', error)
this.close()
process.exit()
})
device.on('data', data => {
const codeCO2 = 0x50
const codeTemp = 0x42
const message = decrypt(data)
const value = (message[1] << 8) | message[2]
switch (message[0]) {
case codeCO2:
this.emit('co2', value)
break
case codeTemp:
this.emit('temp', value)
break
default:
this.emit('other', { code: message[0], value, message: Buffer.from(message), raw: data })
}
})
device.sendFeatureReport(magicTable)
return device
}
close () {
if (this.device) {
this.device.removeAllListeners()
this.device.close()
this.device = false
}
}
}
function truncateU64 (n) {
const s = n.toString(2)
const b = s.substring(s.length - 64)
return BigInt(`0b${b}`)
}
function decrypt (b) {
// Decode buffer received from CO2 monitor.
const magicWord = [132, 71, 86, 214, 7, 147, 147, 86] // b'Htemp99e'
// Rearrange bytes and convert to long int
let res = Buffer.from([2, 4, 0, 7, 1, 6, 5, 3].map(i => b[i])).readBigUInt64BE()
// # Cyclic shift by 3 to the right
res = truncateU64(res >> BigInt(3) | res << BigInt(61))
// # Convert to list
res = [56, 48, 40, 32, 24, 16, 8, 0].map(i => Number((res >> BigInt(i)) & BigInt(255)))
// # Subtract and convert to uint8
return res.map((n, i) => (n - magicWord[i]) & 0xFF)
}