offset \ˈȯf-ˌset\ noun

a force or influence that makes an opposing force ineffective or less effective

Cuply - Hydroponics Greenhouse Cabinet Powered by Django Channels and Arduino - Back-end

I'm in the process of making a greenhouse out of a glass cabinet that sits on our balcony. Inside it, there's to be a plethora of sensors and actuators hooked up to Arduino Due microcontroller, which is in turn connected to an old Raspberry Pi. The software stack is Django with Channels in the back-end, and React with some D3 in the front-end. It was a project long time in development, and this series of articles will explore it from the inception to the latest product.

  1. Overview
  2. Hardware
  3. Back-end ← You are here
  4. Front-end
  5. Conclusion

Back-end architecture is driven by Python and Django. I am skilled with that stack so I wanted to make something with things I know how to work with. Because of the smaller footprint that the Raspberry Pi has, I had to reduce the number of active processes that would potentially consume the memory. What I ended up with is:

  • Redis server that serves as the support for the websockets
  • Django ASGI process that would deal with both, sockets and web traffic, but another thread is spawned to take care of the sensors and actuators, as explained further down
  • Docker daemon composing the application

The database is SQLite since it's only one user per cabinet. No need to have Postgres there. Originally I was thinking to go raw with Circus managing the processes (I wouldn't go with Supervisor because Circus is simpler and handles sockets as well), but this proved to be better encapsulated with Docker so I wrapped things in it instead.

When an ASGI process is created (because it has to handle websockets), an app gets initialized and there's a piece of code in apps.py that spawns another thread. I had to do it this way since that other thread houses an infinite loop that would block the execution of the normal server code. In that new thread, a loop manager is created and executed, which in turn has several functions called in an infinite loop. They are:

  1. updating the devices needed to be parsed if the device list is changed in any way
  2. updating the readings from the sensors
  3. running actuators depending on the readings from the sensors
  4. communicating the current state to the websocket
  5. saving the snapshot of the state for trend graphs

I had to have the Arduino blueprint sketch written in a C variant, but since I don't know C that well, what it does is sends the state to the USB and reacts to the input coming from the USB. This is done for the analog and digital sensors and actuators, for I2C and for communicating with the servo if attached. The idea is that devices in the cabinet are:

  • analog temperature sensor
  • analog light sensor
  • analog ambient humidity sensor
  • I2C water level sensor (a capacitive one to prevent the corrosion)
  • servo motor for pushing and pulling the door of the cabinet through the rack and pinion mechanism
  • three digital actuator relays for controlling the submerged pump, the air pump and the LED strip

The meat of the code is in Python that wraps the pySerial library around Arduino microcontroller itself. The wrapper methods are in turn called from two places. Django models and the finite state machine library called automat from Glyph. Communicating the state is sending the JSON message to the channel layer for the front-end to parse.

The other bits of the back-end application is a common auth system used by Django together with REST framework to serialize the messages for interfacing with FloraCodex via my Shamrock library. Originally Shamrock was interfacing with Trefle, but Trefle is now discontinued and FloraCodex is picking up the pieces. It works OK for my use-case. At least in the initial test. This might warrant another article, but I'll probably mention it in the future eventually.