LoRa Irrigation System ====================== This project is to build an irrigation system from LoRa capable microcontrollers. The [Heltec Node151](https://heltec.org/project/lora-node-151/) was chosen due to it's small size and inexpensive cost. Design Decisions ---------------- While investigating this, the LoraWAN protocol was investigated, but after looking at the code complexity and other operational requirements, if was decided that for this project, it was safer to target a direct Node to Node style communication system. This would allow the implementation to be more simple, and security to be built in (LoRaWAN does have a crypto layer, BUT, trusting/auditing it and any library that implements it would be a larger task than I want to undertake). It could also be used for other projects that need security. One of the other requirements is that the code be 100% open sourced, not GPL licensed, and no proprietary components. This meant that using IDE's like ST's STM32CubeIDE which is only available in binary form was not a choice, as that would preclude building on an operating system other than Windows/MaxOS/Linux. Architecture ------------ There are a number of components to make this system work. The overall flow is: ``` ************************************************************************************************ * * * +---------+ +-------------+ +-------------+ +--------------+ * * | lora.py | multicast | loraserv.py | USB VCP | lora.gw.elf | LoRa | lora.irr.elf | * * | +--------------+ +------------+ main.c +---------+ irr_main.c | * * +---------+ +-------------+ +-------------+ +--------------+ * * * ************************************************************************************************ ``` The `lora.py` component is the front end/UI that is used to send commands to controller. This program establishes a secure communications channel to the controller. It's firmware is in `lora.irr.elf`, and the main source file is `irr_main.c`. The middle to components, `loraserv.py` and `lora.gw.elf` are used to pass messages between the former two. The `loraserv.py` program takes multicast datagrams that are received on 239.192.76.111:21089, which are with out any framing, prepends `pkt:`, hex encodes the data and terminated w/ the new line character, and sends them via the USB VCP provided by `lora.gw.elf`. The gateway firmware then decodes the packet and transmits it via the LoRa radio to the irrigation controller. The received packet is returned similarly, but this time with `data:` prepended, for `loraserv.py` to multicast back to `lora.py`. The reason no particular framing is required for addressing or destination is that the protocol is secure, and only the party that is able to encrypt or decrypt the proper packets will be accepted, and any invalid packets will be ignored. Building -------- The build system uses the BSD flavor of make. This is the default make on the BSDs, originally called pmake, but also available as bsdmake for MacOSX, and available as [bmake](https://crufty.net/help/sjg/bmake.html). It also depends upon ARM's [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm), which uses gcc as the compiler. It would be good to get it cross-compile with clang as well, but that requires finding a libc like the nano libc that is provided by the toolchain. One of the required parameters of the build is the shared key used for authentication. A random key can be made using the command: `make irrigation_key`, or it can be provided via the make command by setting the variable IRR_KEY. Note: Both IRR_KEY and the argument to `lora.py` will encode the provided key to UTF-8. Once ARM's toolchain is in your path, the following should work: ``` export MAKEOBJDIR=build mkdir $MAKEOBJDIR bsdmake all IRR_KEY= ``` And in the directory `build`, two files, `lora.irr.elf` and `lora.gw.elf` should be present. The file `lora.irr.elf` should be flashed on the Node151 device that is used for interfacing to the irrigation system as described in [Deploying](#deploying). The file `lora.gw.elf` should be used on another Node151 that will be attached to a computer used as the gateway which runs the `loraserv.py` software as described in [Using](#using). Flashing -------- Flashing can be done via the open source tool [OpenOCD](https://sourceforge.net/projects/openocd/). For this, I use a Digilent HS1 JTAG programmer utilizing the [resistor hack](https://github.com/ntfreak/openocd/blob/master/tcl/interface/ftdi/swd-resistor-hack.cfg) to allow an FTDI JTAG programmer to control the bi-directional `SWIO` pin. One caveat w/ MacOSX, is that it may be necessary to unload the kext `com.apple.driver.AppleUSBFTDI` via the command: ``` sudo kextunload -b com.apple.driver.AppleUSBFTDI ``` as OpenOCD wants direct access to the FTDI driver. Once that happens, the device can be programmed using the following command: ``` sudo openocd -f interface/ftdi/digilent-hs1.cfg -f interface/ftdi/swd-resistor-hack.cfg -f target/stm32l1.cfg -c "init" -c "reset init" -c "program build/lora.irr.elf verify reset exit" ``` Pins ---- The [pinout guide for the Node151](http://resource.heltec.cn/download/LoRa_Node_151/LoRa_Node_151_Pinout_Diagram.pdf). The default pins PB5-7,9 are used as active low controls for the relays. They are mapped to channels 0 through 3 respectively. The LED on PB8 is mapped to channel 4. This is useful for testing if a command works or not. The pin PB15 is used as an analog input for an RNG source. This pin should be grounded. Deploying --------- Here is a diagram of the connections: ``` ******************************************************************************************* * * * GND * * +--------------------------------------------------+ * * | | * * +--------+ ~/~ +-+-----------+ GND +---------+ GPIO +-+--------------+ * * | 24V AC +--------+ +----------------+ Node151 +---------+ Relay | * * +--------+ | | +-+-------+ +-+--------------+ * * | | +5V -> VUSB | | * * | AC-DC PS 5V +------------------+ | +5V -> JD-VCC * * | | | * * | | | * * | +--------------------------------------+ * * +-------------+ * * * ******************************************************************************************* ``` The noral supply used for irrigation values is 24V AC. This means an additional power supply is needed to convert to the 5V supply that is used by the Node151. Make sure this is well filtered as both the relays on the board (talked about below), and the irrigation values will cause significant noise. The first PS I made was a simple DC-DC buck converter + a full wave rectifier which, while alone was enough to power the uC, was not enough when the relays were actuated, and even when a little bit of filtering was added after the rectifier (22uF), enough to keep it happy w/ the relays, it was not enough when the irrigation valves actuated. In order to control the values, a relay board, similar to [this one](https://www.amazon.com/ELEGOO-Channel-Optocoupler-Arduino-Raspberry/dp/B01HEQF5HU), can be used. The nice thing about this board is that the GPIO pins on the Node151 are 3.3V, while the relays need 5V to work. The jumper on the right side, VCC-JD-VCC, can be removed to allow this. The GND on the input/VCC pinout is used for the relay power via JD-VCC, and NOT for the VCC->INx pins, despite them being next to each other. The GND and JD-VCC should be connected to the 5V power supply, while VCC is connected to VDD on the Node151, and INx pins to the respective GPIO pins. Using ----- The `lora.py` requires at least Python 3.8. It also using the strobe library that in distributed with this program. In general a [virtualenv](https://virtualenv.pypa.io/en/latest/) is recommended for all installed Python software to prevent version conflicts, but is not always necessary. The `requirements.txt` file contains the necessary modules to be installed, but simply addeding the directory `strobe/python` to PYTHONPATH is also sufficient. The program `loraserv.py` takes a single argument, which is the device file for the VCP that runs on the gateway. In my case, the device name is `/dev/cu.usbmodem1451` as I am on my MacBook Pro, so the comand to launch the gateway is simply: ``` python loraserv.py /dev/cu.usbmodem1451 ``` Once that is running, then the `lora.py` program's multicast packets will be forwarded out via the LoRa radio. To test it, a simple `ping` command can be used, or turning on or off the on board LED via channel 4 using the `setunset` command. The ping command: ``` python lora.py -s ping ``` To turn off the LED (which defaults to on): ``` python lora.py -s setunset 4 0 ``` Either of these commands should exit w/o message or error.