Reading a Seneye using a Raspberry Pi – conclusion

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:

IMG_20170703_142232

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       >>>', )
('configuration>>>', )
('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)
{'InWater': True,
 'NH3': 0,
 'SlideExpired': False,
 'SlideNotFitted': False,
 'Temp': 20,
 'pH': 7}

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.

Conclusion

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.

13 thoughts on “Reading a Seneye using a Raspberry Pi – conclusion

  1. Hi there, Doug!

    Listen, we just stumbled across your page (actually one of my fellow project members searched out to see if we were about to embark on re-inventing the wheel) about making a RaspberryPi / IoT computer run Seneye SUD run without SCA or SWS.

    Perhaps we can help each other. 🙂

    I’ve already built my own RaspberryPi Aquarium Controller this past spring using Home Assistant as the foundation. With more than 10 aquariums in my house, I needed a more affordable and scalable aquarium controller solution than could be had by even some of the commercial open source alternatives (i.e. – Reef Angel)

    You can find more details about Home Assistant, the opensource home automation project here:
    http://www.home-assistant.io/

    And here’s where you can read my first post all the way to the current status today.
    https://community.home-assistant.io/t/going-to-next-level-of-aquarium-automation-whos-with-me/18486

    Among things we are working on adding are Variable speed DC pump control, traditional pH/ORP/EC probes, and also the Seneye Probe support (because I have 3 x Seneyes, as does at least one other guy).

    Over this weekend, I just added Seneye API support and will be looking at the LDE integration, but my dream goal is to realize native USB support for the Seneye.

    I saw you were now running on a different board and were going to Windows. I thought to ask…. any chance you’d still be interested in seeing that USB support and / or helping us also help realize it for ourselves and others?

    And of course, if you want to get in on HA for using as a controller on your aquarium…. well, that’s what we’re here for. 😉

    All the best and hope to hear from you,

    Kevin (aka cowboy on the HomeAssistant Forum link above).

  2. Hi,

    I’m at a complete loss after many hours of trying to get my seneye home device to work on raspberry pi (for aquarium automation, of course). I didn’t want to bother you with this, but I hope I can have a bit of your time.
    I follow your code, can see the device using lsusb and udevadm,
    looking at device ‘/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2’:
    KERNEL==”1-1.2″
    SUBSYSTEM==”usb”
    DRIVER==”usb”
    ATTR{bDeviceClass}==”00″
    ATTR{manufacturer}==”Seneye ltd”
    ATTR{bmAttributes}==”80″
    ATTR{bConfigurationValue}==”1″
    ATTR{version}==” 2.00″
    ATTR{devnum}==”6″
    ATTR{bMaxPower}==”250mA”
    ATTR{idProduct}==”2204″
    ATTR{avoid_reset_quirk}==”0″
    ATTR{urbnum}==”102″
    ATTR{bDeviceSubClass}==”00″
    ATTR{maxchild}==”0″
    ATTR{bcdDevice}==”0125″
    ATTR{bMaxPacketSize0}==”64″
    ATTR{idVendor}==”24f7″
    ATTR{product}==”Seneye SUD v 2.0.16″
    ATTR{speed}==”12″
    ATTR{removable}==”removable”
    ATTR{ltm_capable}==”no”
    ATTR{serial}==”F50020C1585018AFAF05910006FF701B”
    ATTR{bNumConfigurations}==”1″
    ATTR{busnum}==”1″
    ATTR{authorized}==”1″
    ATTR{quirks}==”0x0″
    ATTR{configuration}==””
    ATTR{devpath}==”1.2″
    ATTR{bDeviceProtocol}==”02″
    ATTR{bNumInterfaces}==” 1″

    I can also run your code, but get invalid results.
    (‘device >>>’, )
    (‘configuration>>>’, )
    (‘interface >>>’, )
    (‘endpoint in >>>’, )
    (‘endpoint out >>>’, )
    (‘HELO ret code>>>’, 8)
    (‘HELO hex >>>’, (, 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 >>>’, (, 64, ’00:01:13:87:46:5b:05:00:00:00:30:03:30:00:1f:63: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:01:00:00:00:00:00′))
    (‘sensor bits len>’, 512)
    (‘sensor bits >>>’, ‘00000000000000010001001110000111010001100101101100000101000000000000000000000000001100000000001100110000000000000001111101100011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000’)
    (‘BYE ret code >>>’, 6)
    {‘InWater’: False,
    ‘NH3’: 0,
    ‘SlideExpired’: True,
    ‘SlideNotFitted’: True,
    ‘Temp’: 25,
    ‘pH’: 8}

    Temp and pH could be correct, but NH3, SlideExpired are not, but I can see there are values…
    So I guessed the offset of the values must ‘ve changed and I turned to the HUDdriver from seneye themselves. My c skills are rusty/non existant, so I decided to just compile their code and see how far that would get me. After quite some digging into how to compile this, I ran the program, which just says:
    Select the device: (y to select)
    No device selected
    Needless to say, y doesn’t show me anything…
    This is the command I used to compile: g++ -o main main.cpp linux/hid.c -lncurses -ludev

    I’m at a complete loss now, so if you could point me in the right direction, I’d be forever gratefull.

    1. Hi Gert, I think you are very close.

      I also struggled with understanding the C++ code but relied on that to confirm I had the correct results from my Python code. I no longer have the development environment in which I did this work and didn’t capture the exact commands I used to compile their code, but your experience feels very familiar! Looking in my project directories I seemed to use CMake to compile and I attach the CMakeLists below. I think I learned CMake just enough to get this working. I’m not a C programmer either.

      I wonder if it could also be lack of access to the USB? Are you using sudo to run the compiled binary, or have you added udev rules or put your userid in dialout group? My guess is that the python would react the same if you didn’t have access.

      CMakeLists file
      ==========
      # use curses
      set(CURSES_USE_NCURSES TRUE)
      find_package(Curses REQUIRED)
      include_directories(${CURSES_INCLUDE_DIR})

      # use libudev
      find_package(udev REQUIRED)
      include_directories(${UDEV_INCLUDE_DIRS})

      # Set the output folder where your program will be created
      set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/bin)
      set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
      set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR})
      set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -g -std=c++03 -lncurses -ludev”)
      set(CMAKE_C_FLAGS “${CMAKE_C_FLAGS} -g -lncurses -ludev”)

      # Libraries and header files
      include_directories(“${PROJECT_SOURCE_DIR}/lib”)

      # source
      set(SOURCE_FILES main.cpp hid.c)

      add_executable(sudtest ${PROJECT_SOURCE_DIR}/src/main.cpp ${PROJECT_SOURCE_DIR}/src/hid.c)
      #add_executable(sudtest ${PROJECT_SOURCE_DIR}/src/main.cpp)

      # link libraries
      #target_link_libraries(sudtest “-lncurses”)
      #target_link_libraries(sudtest curses)
      #target_link_libraries(sudtest ${CURSES_LIBRARIES} ${LIBUDEV_LIBRARIES})
      target_link_libraries(sudtest ${CURSES_LIBRARIES} ${UDEV_LIBRARIES})

      # end

  3. Hi,
    I woke up this morning, re-attached the seneye (I had put it on my windows for the night to get the readings), re-ran the compiled C program and … it found the usb device this time… I don’t know why, but now at least the C code from senEye is running. The only thing I could think of is that the python code (might ‘ve still be opened in one of the many terminals I had open yesterday) was blocking access to the usb device.
    When I get back from work tonight I’ll try to understand the code and see if I can find any offset errors in the output python generates (and fix them). If I can’t get the python to work I think I’ll rewrite the C code to just auto-connect to the device and output any readings to a file, from where I can read them with python code. (would be kind of messy, but it’s a means to an end…)
    Thanks for sharing, and helping out. If I get it to work (one way or the other), I’ll report back.

  4. So I got your code working again for the most part. Thanks for your effort and help.

    There are still some minor things I can’t fix. The position of the bits for ‘in water’, ‘slide not fitted’ and ‘slide expired’, I have not been able to identify. Well, I identified ‘slide not fitted’ as sitting at position 52 (by removing the slide and diff’ing). I tried taking the device out of the water to identify the ‘in water’ bit (since I wanted to use this to know if something would be leaking when we ‘re on holiday). However, that would make the device not take a reading at all. Also when trying the same with the Seneye SUDDriver c code it didn’t work (reading timed out). When I connected the device to the seneye connect app (on windows) I would get an out of water reading, so I guess something changed in their device but they forgot to update the developer code. I am working around that now with a try/except block, that would alert me if it couldn’t take a reading. I should be able to extract the position of those bits from the c code:
    mvprintw(8, 22, “%-10s”, curr_reading.Reading.Bits.InWater ? “True ” : “False”);
    mvprintw(9, 22, “%-10s”, curr_reading.Reading.Bits.SlideNotFitted ? “True ” : “False”);
    mvprintw(10, 22, “%-10s”, curr_reading.Reading.Bits.SlideExpired ? “True ” : “False”);
    But I don’t understand the %-10s code (which I think would define the location in the bitarray?)…

    Another thing I changed in your code is casting the integers to float before dividing by 100(0); otherwise the nh reading is always 0 and you loose the nitty gritty details I so love 🙂 …
    I also dump the values directly in a database each 2 minutes, and 2 times an hour I let the driver reconnect to the system, I start a USB server on the pi which connects the usb over ethernet to my windows pc, so the Seneye connect app can update the values at senEye website as well. After a couple of minutes I stop the server and data collection to my db continues.

    Should you (or anyone else) want/need the code, it is available on simple request. I will post the progress on my own blog and eventually the code, but I’m quite meticulous so I need to sanitize and optimize the code before actually publishing (and this might take some time). If someone needs it however I’m willing to share as is…

    Thanks again!

  5. Hi Doug!
    I copied the code you posted on the GitHub repo and ran it on my pi. I have set up the Seneye and installed all relevant dependencies but I get a reading return of all 0s. I have confirmed the Seneye is working through the Seneye Connect Application on a windows computer, so I know the script’s results are wrong. I have tried unplugging and replugging the Seneye but to no avail. Any help or ideas would be greatly appreciated.

    1. Hi,
      I’m at a training at the moment without access to my aquarium server, but I have python code running on a raspberry, reading the senEye values. I also have scripted a reset of the usb port (because it sometimes cuts out). I also run a USB server twice an hour for a couple of minutes, so then it connects the USB of senEye to Windows USBHub (over ethernet) and syncs with the seneye cloud (so you can also follow aquarium evolution from there) If you want I can send you my code when I get back home (saturday).
      Best Regards,
      Gert

  6. I’ve been playing with this a bit and have the code reading temp and the bits without a slide inserted. The main challenge seems to be how to activate a slide. The C# example in the Seneye git has a slide command that takes a “code”. There are no further notes on how this might or might not work. Do you know of anyone who has this working long term without the Seneye App?

    1. I no longer use the Seneye system, but you may like to contact Gert Nelen (details in the comments above) as he took my work further and had some ideas about getting the device to ‘check in’ with the Seneye cloud on a regular basis. I haven’t heard of anyone doing slide registration, but I’d long suspected that Seneye use a native protocol instead of the exposed API and of course that’s entirely reasonable given their ownership of their whole system. Kevin McPeake and others at Reef2Reef forum were also working on taking it further, especially in this thread where I commented once: https://www.reef2reef.com/threads/has-anyone-created-a-full-raspberry-pi-aquarium-monitoring-control-system.264093/unread. You may like to check with either of those contacts.

  7. Hello, i wonder if the slides are needed to read the USB device directly (done with the Python code). I’ve developed (with a friend) a koi pond monitor system, it would be nice to put the Seneye to the list of functions it can handle. So no extra costst would be made after buying a seneyse USB device.

    1. Do you actually have a Seneye device? The slides are essentially similar to a litmus strip which ages the longer it is left in the water. The original C++ code from Seneye does activate slides, but not in my Python code. I no longer use the Seneye system nor do I have an aquarium, but note that the simple API exposed here has a number of limitations, some of which are mitigated by Gert Nelen’s approach. There is also an internal protocol as noted in one of the PR’s on my Github repo. I am no longer developing this code.

Leave a comment