donderdag 11 augustus 2016

Reverse engineering the SLAB HT2000 CO2, temperature and relative humidity sensor

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

Writing a C program that reads out these values was trivial and published on GitHub.

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!