So its been a while… I’ve had a chance to try out the Heltec LoRa module. Initially i wasted a solid week trying to use the Heltec library and board definitions etc. I gave up on that and found a library that works well, i can setup the radio and put the device to deep sleep and then successful waking up from LoRa parsing the packet that woke it up. Interestingly the RTC time seems to carry through the deep sleep too. so this should make things easier. SX126x-Arduino Library
Not wanting to go a full LoraWAN setup I have been trying to conceive a way to communicate to a base station securely. Given these radios have a significant transmission distance there is a chance someone could capture the packets and replay them, or decode them and start messing with the blinds. I also wanted to keep it super light weight in the amount of data going back and forward, as once there is dozens of these things around my house it may start getting a little congested. another reason is for trying to keep the device sleeping as long as possible to increase battery life, hopefully smaller packets and simple checking of first byte to decide if it goes back to sleep or stays awake will help
My idea is to use AES encryption for most of the packet, this will stop someone decoding but wont stop the replay attacks, for those I’ve chosen to use a one time password, like a TOTP 6 digit code you get from authenticator that’s included in the encrypted part of the packet. So the idea is to setup the radio and put the device to sleep. when the device receives a packet it will wake up, the first byte will be the address the packet is for - if this doesn’t match it will go back to sleep, otherwise it will read byte 2, which will be the decrypted packet size, it will then decrypt the remainder of the packet. this will include the OTP which it will verify using its RTC. if it passes then it will perform the command received. if it fails the OTP checks it will perform some verification of its own RTC and sync time with the server/base unit. every packet should be completely different from the next one because its effected by the RTC time or random number then encrypted (apart from the first byte or two) and replaying the same packet wont work unless its within a few seconds, which wont effect anything.
LoRa packet setup
01: Address: 0x00 = base, 0x01 thru 0xfe devices, 0xff broadcast to all
02: decrypted size: size of decrypted data. len in decrypt to cleartext
packet encrypted from here to end. use size above and stored key/IV to decode below part of message.
03-08: otp verification bytes
09: from address: to confirm its from base unit or which device to reply.
10: command
11-XX: payload
commands and payloads
direction Command Payload
device to base time request rtc time or if RTC not set generate random number. 10sec timeout. rate limit
base to device time sync epoch time now. used to set RTC. RTC should hold in deep sleep. rate limit
base to device status request blind # (0xff for system status)
device to base blind status blind #, state, position
device to base system status battery, uptime, awake/sleep ratio, total blinds #, blind #, state, position, repeat for each blind
base to device blind command blind # (0xff for all), command (open, close, stop or position)
base to device parameter set blind # (0xff for all), parameter, value
zero limit, na (manually set blind to fully closed, this command sets position sensor value for the position)
open limit, na (manually set blind to fully open, this command sets position sensor value for the position)
Motor open speed, dutycycle (set motor speed/power)
Motor close speed, dutycycle (set motor speed/power)
PWM, freq (set motor PWM)
Open time, time in ms (time blind is expected to take to fully open - used for timeout)
Close time, time in ms (time blind is expected to take to fully close - used for timeout)
Disengage time, time in ms (backdrive time required to set drive back to neutral position)
base to device wifiAP enable true/false
and the time sync/OTP
otp verification notes: to prevent replay attacks
different hmac for send and receive. base encodes with key1, and decodes with key2. device encodes key2 and decodes key 1
use 1sec steps, allow codes for +/- 5 values. this limits replay attacks to 5sec
if out of sync by more than 3 value (3sec) perform time sync of device - device side limit to 1x per min. calibrate rtc if needed. if wake every 10min then it shouldn't drift much
if otp verification failed,
base side: if RTC is set - only allow time request command, number in request to generate the otp for the reply time sync command to device.
if RTC is not set - set RTC to server time, repeat verification
if RTC requres adjustement perform a time sync command using old time to set new time on devices.
device side: if RTC is not set then its expected -
time request with random generated number between 1000 and 300000. wait upto 10sec for reply, then repeat with new random number upto 3 times.
base reply - check random number is valid and use to generate otp for response - will only be valid this request with 10sec timeout.
if RTC is set then possible replay attack -
perform time request with current device rtc, set payload as current rtc.
base reply - if request otp is valid, reply with current epoch time (ie your rtc ok, but likely received a replay, or a replay of time request was sent to server, would fail below).
if otp fails - device time is wrong, use device rtc to generate otp and reply with new time.
I know this isn’t how its normally done, to have AES key and IV stored on the device and on the server and using TOTP like this to block replay attacks… i just find another way to do it with ready made libraries designed for radio transmission.
I’m thinking LoRa for all the main control and using the ESP32 wifi AP to setup the device. Basically on device boot if the device isnt setup or keys etc aren’t set, or if the ‘user’ button pressed or the WiFi enable command received via LoRa… then turn on the AP, and have a simple webpage.
wifi settings: LoRa settings(LoRa settings, AES key/IV, OTP HMAC keys send/receive)
number of blind columns
number of blind rows
perform i2c scan
set order of i2c addresses - test button to function a blind.
per blind setting/status
rw local/i2c
rw i2c address if applicable
ro current position
ro current real position
rw current ewma alpha - exponential moving average filter alpha value
ro current state
rw engaged state
rw zero value
rw max value
rw open speed
rw close speed
rw pwm frequency
rw open time limit
rw close time limit
rw disengage time
And then… all the slave devices will need i2c… ive hashed out the below functions. idea is to use a cheap ESP32-C3 supermini in the slave devices, and if the runs of i2c are a bit long i will include a CJMCU-9515 i2c repeater. power will be from the battery of the main actuator, so only 4 wires daisy chaining the slaves.
i2c commands
get position - read current filtered mapped position
get real position - read current filtered non mapped position
get state - returns open, closed, opening, closing
get engaged - returns true if motor is engaged, or false when disenaged. assumed based on back off time.
get zero limit - get current position as map 0
get open limit - get current position as map 100
get open speed
get close speed
get pwm frequency
get open time limit
get close time limit
get disengage time
get voltage - read voltage at esp 5v input - will need a voltage divider setup to do this. useful for determining voltage drop when motors are running.
set zero limit - set current position as map 0
set open limit - set current position as map 100
set sleep - puts esp and motor driver to sleep.
set position - make it move to position
set disengage - perform disengage routine to try unlocking motor
set open speed
set close speed
set pwm frequency
set open time limit
set close time limit
set disengage time
So the Heltec Wifi stick lite V3 has a MOSFET to connect the battery to a voltage divider for measuring battery voltage without constantly draining the battery, controlled by ADCctl/GPIO37. My thoughts are this MOSFET is rated for a few amps and the trace to it looks beefy, so i will use that over the Vext. The Vext will enable an output supplying 3v3. my thoughts are that there will be some voltage drop on the slaves that are further away, and hopefully the extra voltage will help. it may make the motor speed a bit variable so will need some testing. the output of the MOSFET is a tiny trace with a voltage divider, but soldering directly on the output pin of the MOSFET should do the trick.
My thoughts for the way the device will work, on receiving a command for status or moving the blinds
Set ADCctl GPIO37 low, powers up slave devices
wait for bootup time to pass
scan i2c devices.
read status of each blind (if available in scan)
send sleep command to not required slaves/actuators
send command to required actuator/s
read update periodically
once position achieved or timed out put actuator to sleep.
read battery voltage.
all actuators should be in sleep mode to get accurate reading.
read voltage during moves to see how much it pulls down.
possibly setup voltage divider at esp32 supermini. perhaps bypass shotkey for extra 0.5v
For the base station, i would like to take the command/payload from the decoded LoRa message and transmit via MQTT to HA, and subscribe to topics, and encode them to send to the relevant device. then setup MQTT covers for each blind in HA to be able to control them.
I would like to think this is a great starting point for many battery powered devices, not just these blinds. there are loads of lora sensors that live life sleeping, but i cant see any do-ers, things that can wake up and perform an action.
I’ve been watching this thread, ESPlanty and thinking it would be another good use for it, allowing more range and be able to wake it up remotely to provide additional water if the weather is going to be higher than normal.
I’m also thinking of my cheap outdoor water misting system, it has a timer built in… but i would rather only have it trigger when there is someone in an area and the temperature is above a certain temp, both of which i can get from HA easily… its just finding a battery device that listens and is fairly secure. my old RF switchboard gets randomly activated and deactivated by a neighbor…
Unfortunately I’ve forgotten more than i can remember with C++ and Arduino… and while i can fumble my way through some things i find myself banging my head a against a wall constantly fighting with strings and pointers and all that good stuff. Does anyone want to take on a challenge and help me build this one out?
update1:
program written that handles sleeping/wakeup, and the lora TX/RX including the AES and OTP used as an IV. Time sync is implemented so the device can sync with the master. response time is insane! only 100ms to wake, receive and decrypt the message
next up is parsing the command and payload and doing stuff with it, followed by MQTT on the master
update2:
have had a few devices reporting battery level back to the gateway, and the gateway publishing MQTT to HA for logging, with 60second update intervals the 1100mah battery is dropping by about 0.1v per day. so roughly a week to 10 days battery life without motor movements. these devices are asleep 98.6% of the time! (but i don’t expect to do 60sec updates on the blinds normally)
I’ve got 1 actuator that’s receiving a move command every 30sec and moving for 5.5seconds each time (no load) this is dropping the 2000mh battery by 0.25v per day. this equates to 20% awake time on the device, most of which is motor moving time. well above normal usage.
response time from the command to motor moving is unmeasurable… in the milliseconds. i normally hear the motor start while the text comes in on the terminal from the gateway sending the message. I’ve made a few optimization’s and have the wakeup down to 25ms for a message not directed to that device, and about 80ms to parse the full packet, perform the decryption, verify the OTP, parse the command and trigger the action. I’m also seeing time variations of +/- 2 seconds per day on the RTC used for OTP… so it looks stable enough and shouldn’t be continuously syncing. will be interesting to see how it goes when they get hot in a window.
I tried some small solar panel 110mm x 60mm, but it only charged when in direct sunlight, I’m going to try a few in parallel to see if that helps. but given the low power usage I’m observing so far it shouldn’t take much to to it up each day.
still a load of work to be done, the code is a shambles at the moment but slowly night by night i’m getting more things done. currently im working on the the motor/position control of the actuator and parsing the blind control command, then ill get the MQTT subscriptions on the gateway worked out so i can control via home assistant.
I put together one of the slave devices, with the esp32-c3 and a motor controller, but I’m realizing that i will need a custom PCB to mount the modules to, its very time consuming soldering so many wires, and they are fragile… im also thinking for connectors for the slaves… will be much better to have them on a PCB. maybe when i need a break from programming.