This is an attempt to describe the SUD protocol based on reading the main.cpp file that Seneye released. Contributions via git pull requests are most welcome.
USB devices have a hierarchy of interfaces, like so:
- Device
- Configuration
- Interface
- Alternate setting
- Endpoint
The SUD device seems to have these 2 types of USB transfers available: interrupt and bulk. HID devices only use 2 transfer modes of either control or interrupt. From from https://en.wikipedia.org/wiki/USB_human_interface_device_class HID devices do not use the 'bulk' interface, so only the 'interrupt' interface is used. In any case, we search for the endpoints rather than hard code them.
Max packet size is 64 bytes for all endpoints. From printing the configuration these are the endpoints:
- ENDPOINT 0x81: Interrupt IN
- ENDPOINT 0x1: Interrupt OUT
- ENDPOINT 0x82: Bulk IN
- ENDPOINT 0x2: Bulk OUT
From what I have been able to determine from the C++ module that Seneye released, the flow goes a little like this: once you have obtained correct access to the device via USB handlers, you start by sending the "HELLOSUD" message and receive a version message in response. You then ask for the sensor values using a message "READING", and get back a return code if the command was accepted. You then read the next message (no command) with a longer timeout and get the sensor values. At the end you then send a "BYESUD" closing message to stop communication and close down the USB handlers as appropriate. The command to switch on or off the LEDs is similar.
I also found after a number of tests that the slide LED would light up, and no more readings could be taken. This seemed to be due to the device filling up some sort of offline memory and preventing any more until it had been loaded to the cloud. This was cleared by using the Windows Seneye Connect app and I noticed that it went through a routine of doing the offline uploads, after which the LED cleared and it could be read again. This feature may limit the usefulness of any foreign program reading the SUD, unless we can either dummy up the upload or utilise some of the control messages to the SUD.
There are command type messages, and response messages with readings from the device.
- Command: Communication starts with a message "HELLOSUD".
- Command: Request sensor parameters with a "READING" message.
- Command: Turn off or on LEDS with a "LED" message.
- Command: Communcation ends with a "BYESUD" message
- Response: from "HELLOSUD"
- byte 1: eyecatcher value = 0x88
- byte 2: 0x01
- byte 3: '1'=success, otherwise=failure
- byte 4: the type of the device, 0/1=home, 2=pond, 3=reef
- byte 5: device version byte 1
- byte 6: device version byte 2, version is x.x.x
- Response: from "READING"
- byte 1: eyecatcher value = 0x88
- byte 2: '0x02'
- byte 3: '1'=success, otherwise=failure
- Response: from "LED"
- byte 1: eyecatcher value = 0x88
- byte 2: '0x03'
- byte 3: '1'=success, otherwise=failure
- Response: from "BYESUD"
- byte 1: eyecatcher value = 0x77
- byte 2: '0x01'
- byte 3: '0x01'
- Response: subsequent read after "READING" command and response
- byte 1: eyecatcher value = 0x00
- byte 2: '0x01'=reading
- Response: subsequent read after "READING" command and response
- byte 1: eyecatcher value = 0x00
- byte 2: '0x02'=lightmeter reading
Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 | Byte 8 | Description |
---|---|---|---|---|---|---|---|---|
H | E | L | L | O | S | U | D | Start comms |
R | E | A | D | I | N | G | READING | |
L | E | D | L1 | L2 | L3 | L4 | L5 | LEDs 1-5 on/off |
B | Y | E | S | U | D | End comms |
Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 | Byte 8 | Description |
---|---|---|---|---|---|---|---|---|
0x88 | 0x01 | 1 | type | ver.b1 | ver.b2 | Response from HELLOSUD | ||
0x88 | 0x02 | 1 | Response from READING | |||||
0x88 | 0x03 | 1 | Response from LED | |||||
0x77 | 0x01 | 1 | Response from BYESUD | |||||
0x00 | 0x01 | Reading | ||||||
0x00 | 0x02 | Lightmeter reading |
The message returned from the READING command contains a mixture of bits and byte values. This means a little more work in Python but we can use the Bitstring library. The use of a STRUCT will mean that bit padding will occur so that things like char, int and short are byte-aligned. The ints and shorts are also little-endian.
I have mapped the following values:
C type | length in bits | Position | Name | Description |
---|---|---|---|---|
int | 32 | Ts | Unix Timestamp | |
bit | 2 | reserved | unknown bit[2] | |
bit | 1 | 36 | InWater | in or out of water |
bit | 1 | 37 | SlideNotFitted | is there a slide |
bit | 1 | 38 | SlideExpired | has the slide expired |
bit | 2 | StateT | unknown | |
bit | 2 | StatePh | unknown | |
bit | 2 | StateNh3 | unknown | |
bit | 1 | Error | unknown | |
bit | 1 | IsKelvin | valid Kelvin measure is taken | |
short | 16 | 80 | pH | pH level (x100) |
short | 16 | 96 | Nh3 | Ammonia level (x1000) |
int | 32 | 112 | T | Temperature (x1000) |
char | 128 | reserved | unknown char[16] | |
char | 64 | reserved | unknown char[8] | |
int | 32 | Kelvin | light colour temperature in the Kelvin scale (x1000) | |
int | 32 | x | unknown | |
int | 32 | y | unknown | |
int | 32 | Par | PAR light level | |
int | 32 | Lux | Lux level | |
char | 4 | PUR | PUR value in percentage |
Lightmeter readings are continuosly sent by the device periodically after the "HELLOSUD" message. They can arrive at any time and get mixed with other readings.
The values are a subset of a full reading and include just the lightmeter measurements:
C type | length in bits | Position | Name | Description |
---|---|---|---|---|
int | 32 | IsKelvin | valid Kelvin measure is taken | |
int | 32 | Kelvin | light colour temperature in the Kelvin scale (x1000) | |
int | 32 | x | unknown | |
int | 32 | y | unknown | |
int | 32 | Par | PAR light level | |
int | 32 | Lux | Lux level | |
char | 4 | PUR | PUR value in percentage |