TL;DR: I reverse engineered the SLAB HT2000 CO2, temperature and relative humidity (RH) data logger made by Dongguan Xintai Instrument Corporation. Sourcecode and binaries available at http://GitHub.com/tomvanbraeckel/slab_ht2000
I found a great CO2, temperature and relative humidity meter with USB connection. It's made by Dongguan Xintai Instrument Co. in China and branded under the name HT-2000. Available
online for less than $100, it is quite cheap for this kind of device.
There's only one downside to a lot of cheap Chinese product and this one is no exception.. it came bundled with:
- no Windows software or drivers, although I found one online
- no manual, although I found one online
- no Linux software or drivers
- no protocol specification
- no datasheet
So how does one read out the values or communicate with this thing through the USB port?
With the help of the Linux kernel, of course!
Connecting the device to a modern Linux (kernel version 4.7) and running lsusb yields in:
# lsusb
Bus 003 Device 011: ID 10c4:82cd Cygnal Integrated Products, Inc. # this is the one!
Cygnal makes USB-to-serial devices that are supported by the cp120x driver but this device actually reports itself to be a Human Input Device in its USB device descriptor:
# dmesg | tail
[407925.138165] usb 3-6: new full-speed USB device number 10 using xhci_hcd
[407947.073443] usb 3-6: new full-speed USB device number 11 using xhci_hcd
[407947.203629] usb 3-6: New USB device found, idVendor=10c4, idProduct=82cd
[407947.203632] usb 3-6: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[407947.203634] usb 3-6: Product: HT2000
[407947.203635] usb 3-6: Manufacturer: SLAB
[407947.209156] hid-generic 0003:10C4:82CD.0007: hiddev0,hidraw0: USB HID v1.01 Device [SLAB HT2000] on usb-0000:00:14.0-6/input0
And our Linux system creates a /dev/hidraw0 device as a virtual respresentation of the physical hardware device, the CO2 meter.
Manually reading out the device descriptor results in more confirmation that this is a raw HID device:
# lsusb -v -v -v
Bus 003 Device 003: ID 10c4:82cd Cygnal Integrated Products, Inc.
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x10c4 Cygnal Integrated Products, Inc.
idProduct 0x82cd
bcdDevice 0.00
iManufacturer 1 SLAB
iProduct 2 HT2000
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 41
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 64mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 No Subclass
bInterfaceProtocol 0 None
iInterface 0
Warning: Descriptor too short
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.01
bCountryCode 0 Not supported
bNumDescriptors 2
bDescriptorType 34 Report
wDescriptorLength 128
bDescriptorType 31 (null)
wDescriptorLength 157
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x01 EP 1 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 10
Device Status: 0x0000
(Bus Powered)
So we know it's a Human Input Device but we have no further info on how to get data from it.
No data is coming out when just reading from it while the device is making measurements:
sudo head -c 1 /dev/hidraw0 # Never finishes so not a single byte of data comes out here
Time to check out the Linux kernel samples of how to deal with hidraw devices. In the Linux kernel sources (version 4.7) there is an example samples/hidraw/hid-example.c which shows how to read a report from a hidraw device.
This example tool yields the following output:
# sudo ./hid-example /dev/hidraw0
Report Descriptor Size: 128
Report Descriptor:
6 0 ff 9 1 a1 1 85 1 95 6 75 8 26 ff 0 15 0 9 1 91 2 85 2 95 3c 75 8 26 ff 0 15 0 9 1 91 2 85 3 95 1 75 8 26 ff 0 15 0 9 1 91 2 85 4 95 2 75 8 26 ff 0 15 0 9 1 91 2 85 5 95 1f 75 8 26 ff 0 15 0 9 1 81 2 85 6 95 3c 75 8 26 ff 0 15 0 9 1 81 2 85 7 95 3c 75 8 26 ff 0 15 0 9 1 81 2 85 8 95 3c 75 8 26 ff 0 15 0 9 1 81 2 c0
Raw Name: SLAB HT2000
Raw Phys: usb-0000:00:14.0-6/input0
Raw Info:
bustype: 3 (USB)
vendor: 0x10c4
product: 0x82cd
ioctl HIDIOCGFEATURE returned: 4
ioctl HIDIOCGFEATURE returned: 61
Report data (not containing the report number):
8 9b a5 22 5 5 9b a3 22 5 5 9b a1 22 5 5 9b a1 22 5 5 9b a1 22 54 5 9b a2 22 54 5 9b a4 22 54 5 9b a5 22 54 5 9b a4 22 84 5 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
write() wrote 2 bytes
read: Resource temporarily unavailable
The "Report data" looks interesting but we are looking for live measurements and these values don't seem to change when running the program multiple times.
So we need to dig deeper.
Now, looking at the source code of the hidraw example, I noticed that it is trying to read out report number 9. So I set out to read the other report numbers and seeing whether anything useful comes out.
Report numbers 1 to 4 all have the same content as the 9, except that the first byte is different and seems to contain the report number itself.
Report number 5 is interesting though. The report data seems to be live because it is changing slightly after every run:
5 77 0 c4 e1 0 36 2 8c 1 f7 1 90 3 20 0 64 3 b6 b0 3 1 a1 22 2 73 0 0 7 d0 5 9b
5 77 0 c4 c6 0 64 2 8c 1 f9 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 75 0 0 7 d0 5 9b
5 77 0 c4 e6 0 64 2 8c 1 fa 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 7c 0 0 7 d0 5 9b
5 77 0 c4 c7 0 64 2 8c 1 f9 1 90 3 20 0 64 3 b6 b0 3 0 a1 22 2 7f 0 0 7 d0 5 9b
AHA! That is live data we are seeing there!
Comparing these values to the actual measurements on the display allowed me to spit out the following partial reverse engineered specification for this report #5:
Output:
5 77 0 c5 f2 0 64 2 97 1 ee 1 90 3 20 0 64 3 b6 b0 3 0 ff ff 2 e8 0 0 7 d0 ff ff
DD D DD DD T TT H HH C CC
Where:
DD D DD DD = a timestamp, seconds since epoch + 2004450700 (magic number)
T TT = the temperature, multiplied by 10 and plus 400 (26.3 degrees Celcius in the example above)
H HH = the humidity, multiplied by 10 (49.4 % R.H. in the example above)
C CC = the CO2 concentration, 744 ppm in the example above
Future work
Report #6
Report #6 toggles between 2 different outputs:
6 1 3 20 0 64 3 b6 0 57 22 b5 b6 1 57 22 b5 b6 0 0 0 0 7 d0 0 0 0 0 0 0 0 6 ff ff ff 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
and
6 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 fe 20 0 64 1 0 1 b0 1 90
Report #7
There seems to be a pattern here, similar to what report #6 outputs:
7 1 3 20 0 64 3 b6 0 57 22 b5 b6 1 57 22 b5 b6 0 64 0 0 7 d0 0 0 0 0 0 0 0 7 ff ff ff 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
More features
According to the specifications, this device also contains memory for at least 10000 measurements and can log in different modes (Immediately, Schedule, Real-time & Roll-over) but I could not figure out how to change the default mode without having the Windows PC software. This means the "REC" button does not do anything because the mode is set to "Immediately" instead of "Manual".
Also, recalling minimal and maximal values should be possible on the display but I have no idea how.
If you have any idea on how to use those extra features, leave a comment please. Thanks!