Software details:

The software running on the buoy is written in C (Arduino Framework). I might eventually clean it up and publish it. The backend was initially the Helium Google Forms integration. In order to support transmitting the raw measurements (hundreds of bytes instead of 24 bytes), I switched to point-to-point LoRa. This uses a second esp32 that receives the frames, encodes them to json, and publishes them to mqtt. Then some python running on a raspberry pi takes the readings off mqtt, calculates some derived values, and posts them to a google form as well as uploads processed versions to google cloud storage.

In the Buoy:

The buoy now contains 3 MCUs that communicate over two i2c busses. The main application logic now runs on an avr64db32. The LoRa board runs the network code only. Finally an ATTiny1624 acts as a watchdog and cycles power for the rest of the system if it hangs.

avr64db32

The main application logic now runs on an avr64db32. This wakes up at 16hz to collect samples continually. It wakes the ESP every ~16 seconds to transmit data. This chip draws ~1-2ma at 4mhz. As much of the logic as possible runs here. The full firmware image for this chip is 64kb making it the easiest and safest to update. At SF7 CR4_5 this takes 35 seconds to transmit an update and another 10s or so to apply and verify.

Heltec Wireless Stick Lite (esp32)

The main purpose of this component is to run arduino-lmic. It also impliments all of the cryptography and the firmware update routines for the avr64 and itself. It is a power hog, running 12-20ma "idle". The SPI driver (required for LoRa) prevents ESP32 power managent from dymanically adjusting the clock. While this also supports OTA firmware updates, they are (currently) 6x larger, more error-prone, and it is possible to perform an update to software that breaks further updates.

The Watchdog

Power to the rest of the system (the ESP, the AVR, and the sensors) is controlled by the watchdog. In it also controls the reset lines but I decided to configure it to power-cycle instead. The watchdog firmware is somewhat more flexible and allows configurable (long) intervals. The system currently uses a 90-second interval to avoid triggering a watchdog reset during firmware updates (the interval is increased at the beginning of an esp update). The watchdog also exposes some memory that survies power cycles to store some configuration like LoRaWAN frame counters, reboot counters, and sequence number counters. Finally it controls the ina3221 power monitor and impliments an accumulator to allow the main processor to only read/clear the power usage information once per sample. Communication is via an i2c slave on the watchdog (on the bus mastered by the avr).

The old single-mcu logic was:

Frame format 0b000:

LoraWAN packet size is highly constrained. While a JSON representation of this might run hundreds of bytes we can easily byte pack all the data into a 24-byte frame (the Helium data size unit).
bytes signal format
0 3-bit type code | 5 bits of sequence allows the parser to determine message format. This is format 0b000
1 7 bits of sequence | high bit of timestamp
2-3 low 16-bits of timestamp time in seconds since midnight
4-6 lattitude 24-bits (lat+90)*10000*9.32
7-9 longitude 24-bits (lat+180)*10000*4.66
10 battery current ma + 127
11 solar current ma + 127
12 temperature degrees C * 4
13-14 wave height mm
15-16 gps horizontal accuracy cm
17-18 wave period ms
19-21 pressure 24-bit
22 battery 8-bit, (mv-3280)>>2 with 0 and 255 hardcoded for out of range
23 sat count byte, could steal 4 bits here

Frame format 0b001:

This frame format encodes only the GPS delta from the previous 0b000 frame.
bytes signal format
0 3-bit type code | 5 bits of sequence allows the parser to determine message format. This is format 0b001
1 7 bits of sequence | high bit of timestamp
2-3 low 16-bits of timestamp time in seconds since midnight
4 lattitude delta 8-bits (lat+90)*10000*9.32
5 longitude 8-bits (lat+180)*10000*4.66
10 battery current ma + 127
11 solar current ma + 127
12 temperature degrees C * 4
13-14 wave height mm
15-16 gps horizontal accuracy cm
17-18 wave period ms
19-21 pressure 24-bit
22 battery 8-bit, (mv-3280)>>2 with 0 and 255 hardcoded for out of range
23 sat count byte, could steal 4 bits here

Cryptography and raw packet formats

This section is mostly TBD. At a high level, a message is fragmented into 255-byte frames. The first frame includes a header with a message type, a frame sequence number, and an (ecb) encrypted SHA256 of the full message. A KDF is run off the decrypted hash to produce a per-message key. That key is used for GCM encryption of the remaining frames. A 64-bit tag is used per-frame to cheaply detect accidental corruption, if that passes the full sha256 is checked once the final message is assembled. Messages where the sha256 doesn't pass are discarded. Frame sequence counters prevent replays. This was mostly implimenmted to ensure the integrity of firmware updates but it was easy enough to adapt for the rest of the system. An AVR firmware update is 262 messages so there is retransmission logic as otherwise most updates would fail.

Notes on power management (old version):

I built and deployed the buoy with only a rough sense of the power consumption in various modes as well as the performance of the solar cell under real world conditions. In particular the INA3221 was a late addition and prior to that I was reasonably blind to the power use. To ensure it would work reasonably well across a wide range of possible scenarios (weather), the system was initially designed to adapt its power use depending on how charged its battery is.

The current draw with the GPS module on is around 50ma. The sleep draw is < 2ma. Further optimization is possible, the INA3221 is about 400ua and the mcu is also in the microamp range. This means the average power use can be varied by changing the duty cycle (time between measurements). Wave measurements take about 30 seconds which is similar to the GPS warm up time (a better GPS module would warm-start faster). So figure the system is in high power state for about 30-40s.

With a 2-minute sample rate, the system is on for about 30% of the time for a blended average draw of 15ma. If the battery is below 4.1V, and above 3.8V the system collects a sample every 5 minutes which should reduce the average draw down to 5-7ma. 3.7-3.8V collects a sample every 10 minutes and below 3.7V every 15 minutes.

Further experimentation will be done around comparing the power use for a reading that omits GPS

Measuring average current with the INA3221:

The INA3221 should be configured to spend most of its time measuring current instead of voltage. I use 588us voltage, 8244us current, and 64-sample averaging to produce a reading every ~1074ms which is an easy enough time period to interleve. The readings are accumulated during each measurement, tracking the total time. Use the measurement done flag to ensure each reading is captured exactly once. Wait for a cycle to finish (with the GPS off) before sleeping to ensure the reading on awakening reflects the sleep use. Note this would all be much simpler on an INA233 which has an integrated accumulator.

This library supports the INA3221.

Helium with the HelTec CubeCell:

More to come on Helium. But you can get started at this console.

Comments on making heltec lorawan library work with Helium:

Backend:

The current backend is a quick and dirty placeholder that like all temporary solutions may outlive its intended lifetime. Helium provides a google forms integration and runs user-supplied javascript to decode the payload into JSON and post it to the google form. Forms dumps data to a sheet which generates the graphs.