So it can be done, I’ve now able to read a Seneye USB device using my own server and Python code. You can read about the first couple of steps here and here. The process was difficult due to a number of quirks and barriers:
- the Seneye code uses a C++ STRUCT for data mapping, implying byte-alignment for different data types and bit padding
- the SUD holds local data readings until it is able to reconnect to the cloud, and will fill up to capacity if it is not connected to the cloud
- values are decimal-shifted for display
- if any errors occur the device seems to enter a timeout-locked state (perhaps by missing the BYESUD message?) and has to be unplugged
Firstly the official Seneye C++ code as compiled on my machine and reading the reference mug of water:
Then the output from the Seneye code, without their lovely ASCII logo art:
Device: LSDF0982LSDFOSDLJKS9E89S0D9SDMF v.2.0.16 Type: Home
Temperature (C) │ 20.375 Is Kelvin │ ┌────┘ └────┐
pH │ 7.94 Kelvin │ │ ┌─┐ ┌─┐ │
NH3 (ppm) │ 0.02 PAR │ │ └─┘ └─┘ │
In Water │ True LUX │ │ │
Slide NOT fitted │ False PUR │ │ ┌───┐ │
Slide Expired │ False │ │ │ │ │
Press R for reading, 1-5 to change LED, Q to quit
Taking this and observing that the pH value was 7.94, which is 031a in hex, I scanned through the printed hex dump (and I really must write a small routine to dump binary, hex, and offset in bytes-per-line). The latest output from my code with debugging turned on can be seen below.
('device >>>', )
('interface >>>', )
('endpoint in >>>', )
('endpoint out >>>', )
('HELO ret code>>>', 8)
('HELO hex >>>', (<type 'array.array'>, 64, '88:01:01:01:30:4e:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'))
('READ ret code>>>', 7)
('sensor hex >>>', (<type 'array.array'>, 64, '00:01:57:59:59:59:05:00:00:00:1a:03:10:00:1a:4f:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00'))
('sensor bits len>', 512)
('sensor bits >>>', '00000000000000010101011101011001010110010101100100000101000000000000000000000000000110100000001100010000000000000001101001001111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
('BYE ret code >>>', 6)
This means that the pH starts at position 80, runs for two bytes, and is little endian (so x031a is the equivalent of 794 in decimal, and packed into two bytes as it is a short means it looks like ‘1a.03’. The 7.94 comes about because certain of the values returned are divided by 1000, certain ones by 100. It helps to read and understand the Seneye C++ code.
There is obviously some extra commands that are sent by the Seneye Connect software, plus I believe some cryptographic hashes in operation to ensure devices update, only upload from authorised accounts, and other things to keep the Seneye ecosystem together.
This means that it is unlikely that home DIYers will be able to replicate the full Seneye Connect experience. A LattePanda running Windows 10 and the Seneye Connect software or Seneye SWS probably still give the best experience, along with SMS text alerts and the Seneye dashboard. However, for those who are willing to tinker with code this project provides a reasonable solution.