There are a growing number of remote AC relays and outlets available off the shelf these days for wireless control of lights and fans, but not much that uses a radio capable of long distance. I've decided that security and utility would be improved by having more lights around the house, and this remote control option solves two problems.
Code, circuit, and Kicad files:
Before I begin, all the files for this project can be found here: Cj's Gitlab
First being that it's less convenient to have to be at the switch for the light I want on. It would be nice to be able to turn the light on when you need it while outside, or from a different window from where the switch is.
I know I know, normal people would just buy motion sensor lights and be done with it, but normal people don't take up embedded development and PCB design as a hobby. I've lived rural most of my life, and I appreciate the peace of being alone with your thoughts in the dark. I find the constant-on yard lights around most homes a shocking waste of power, and motion lights are annoying when I want to take a walk in the dark.
So the obvious solution is built remote-control lights. On when I want, off when I don't. So it's another chance to make up a PCB design with the Semtech SX1278 'LoRa' long-range radio.
About my point-to-point model 'property area network'
There are a number of projects I've been working on with the Lora radios, and it would be most helpful to have multiple methods of interfacing with them. Turning on lights and opening the gate from the Wi-Fi network is pretty handy, but the cars should have a gate controller too, and what about a handheld remote for the lights? Therefore, the most robust network model I can think of is simple make every LoRa radio 'point-to-point'. Any device can (or should) be able to talk direct with any other. The distances involved will nearly guarantee that every radio can hear and reach every other radio from anywhere on the property. This way no central server is required, and if any one part of the system goes down (most likely something I break while adding new code) everything else should operate normally until I get it fixed. I've got the PCBs complete for the remotes, but for now I'm working on the LAN/Wi-Fi side of the interface
The LAN bridge
So while there is no central 'server' required for this UHF network to operate, it's still pretty handy to be able to connect to all this stuff from the house WiFi or Ethernet. So there will be one fully network capable computer with a LoRa radio attached to act as a 'bridge' between commands and data sent over the network and those sent by radio. So here it is:
You might notice that it appears to be the same enclosure as the camera in the thumbnail photo. It is, and my take on the raspberry-pi IP camera will be the subject of a future (likely next) blog post.
For now however you can see a self-etched board that supports the LoRa radio, a connection for external antenna, and a 5v-12v boost converter (the camera enclosure includes a 12v fan and thermostat circuit, so it will keep the raspberry pi cool in the summer).
The camera is running the RPicam interface, which has a very easy to use macro system to place buttons below the camera stream. For my use these buttons are the fastest networked way of turning on lights and opening the gate.
Easy script connections
A python script parses incoming MQTT messages, making for a hopefully also-robust system that can run independent of other LAN/WiFi based controls I come up with to interface with it. It's still pretty messy, but I've started revamping it to include more class objects. I'm trying to make it simple to register new devices, and add new functions in a way that doesn't involve lots of hard coded variables and device addresses.
Such as it is though it runs pretty reliably so far. With the
mosquitto-clients package on linux it's easy to make a script or call a command over SSH to interface with it. This way I can write and debug other scripts without interfering with the main control script. Code will be posted to Gitlab soon, still cleaning up a few kludges I'm too ashamed to share yet.
Using the Python logging module, I have a Raspberry Pi setup to dump all incoming lora packets to a file. It helps let me know during development whats wrong in the sequence. I.e. did the MQTT not get received, or the lora radio did not transmit, or did the receiving radio not respond?
Looking at RSSI values from radios in different places or different power levels is also helpful.
Functionality and design overview
The board layout:
The radio has it's power stepped down from 5 to 3.3 volts by a typical AMS1117 regulator. The LoRa radio data pins are not 5 volt tolerant, so those inputs are stepped down with a 1k-2k resistor voltage divider on each pin. Cheap and easy. I realize this is not an ideal option especially with higher-speed data, but I've tested it with a couple different SPI speeds from the default library and all have worked without a hitch. The MISO line from radio to Arduino is left alone, as 3.3 volts is reliably read as HIGH by the MCU.
Since this may end up in multiple different enclosures, I put an IPX coaxial connector on the board and am using an IPX-to-SMA adapter cable to put the antenna outside of whatever (sometimes metallic) enclosure I put these in.
The terminal block in the upper left is just two digital pins, to allow for switching the relays on and off at (or within cable range of) the board without needing a remote.
I thought I was clever to include some DIP switches for address-selecting functions. The board would read the switches on startup to select the destination/broadcast address, and if started while the on-board button is pressed, enter a function to select the device address and save to EEPROM.
You'll find those functions in the code, but not used anywhere. Turns out remembering what exactly the jumper switch configuration meant was more annoying that just hard coding every HEX address, and slapping a Dymo label on it to remember each one.
I'm getting better about making break-outs on my PCB designs; that is make at least a solder-able connection available for unused pins in case I think of an extra attachment I'd like to make later. In the upper left of the board I decided to add a 5v/gnd pins and an analog pin for a TMP38 temperature sensor. This could be used for any analog sensor of course, but in my case the structure that contains the pressure tank for the well is just composite siding. Naturally, it's essential that this structure be kept from freezing inside in the winter, so since the RF will penetrate the building easily I can include a temperature sensor to monitor along with switching an outside light that will point towards the chicken coop.
Version 1.2 has more IO broken out for further expansion. There's lots of room left in Flash and RAM memory right now so this could be used along side other projects that could share a radio or MCU.
The power supply
This is my first time using the HLK-PM01 module, a 90-245 VAC to 5vdc power supply in a package hardly bigger than an arduino nano. It is available in 12, 9, 5, and 3.3 volt outputs. It's only 0.6 amps output, so there's plenty of power to work with for fairly simple circuits like this. The biggest current draw in this design is the relay coils. With both on at the same time, the current draw according to my bench supply is just 0.2 amps.No detectable voltage sags happen while switching the relays or transmitting with the radio (stepped down to needed 3.3volts with an AMS1117 regulator).
My confidence is using these modules was boosted after reading some very thorough stress-tests online. One of the best I found is here: lygte-info.dk HLK-PM01 review and testing
It sure beats trying to stuff a wall-wart power supply into your project enclosure.
I'm in the USA, so house power is 110VAC and I followed the recommended trace clearances to keep AC and DC isolated as best as possible. There's no ground plane around the AC power rails, and the AC and DC ground are tied together in only one place. Further safety is provided by fusing both AC inputs with mini-ATO automotive fuses. I think the board is very conservatively rated for at least 4 amps through each relay, but I put a fuse in specific to what power draw I expect for each attached device. Beyond that, on my currently one-and-only installed board, I tapped into the nearest GFCI outlet to power it. This turned out to be pretty handy for testing too, as I could easily shut it off or power-cycle the board from the ground while I debugged some problems with the code.
Putting it together.
I found a weatherproof enclosure on ebay that looks pretty good. Enough room underneath for the AC wiring and some cable glands. None of the standoffs are in the right place so rather than poke holes in the back of the case I mounted a piece of sheet aluminum and put my PCB standoffs on there.
All mounted up:
Then began a week of debugging
I thought it would be so simple. I've done all this before. Parse messages, toggle relays. How hard could this be? People are starting to stare at me as I randomly experience outbursts of debugging frustration.
The problem emerged after about 5 to 8 hours of running perfectly, where the controlling Arduino would hang completely. Totally frozen until power cycle. I'll spare you the tedious details of how I went down false trail after false trail looking at circuit problems. What I ended up discovering is that it wasn't actually hanging in 5 hours, but rebooting itself about every 1/2 hour or so, only hanging after that had happened a dozen times or so.
I was informed that such strange restart/hang behaviors can be attributed to running out of RAM. I started looking at what was possibly different in my relay-board code than my gate-controller, as the gate had been running flawlessly over 2 months when I started in on this. That's when I finally saw it.
Thinking about the amount of time it took for me to find this problem still hurts my brain.
What you see here is the cause of nearly a week of frustration. I don't understand why
if (packetsize = 0) return; doesn't catch the problem. As far as I can currently tell, the radio was decoding random noise and sending it over SPI to the Arduino. Or perhaps the Arduino received electrical noise and interpreted it as legit data. Without a proper address check before filling the message buffer, the MCU was filling its RAM up with garbage until it overflowed, causing random resets and hangs.
I still don't understand why this is the case, as without the proper sync-word I don't believe the Lora radio was receiving noise and treating it as data to be transferred. I watched the IRQ pin with the oscilloscope for a while, and never saw it go HIGH except when receiving a legit packet that I transmitted. Someday I should put a logic analyzer on the data lines and see what was being sent. For now I'm content to have it working. It's nearly two weeks at time of writing with no hiccups at all.
I don't think I can describe what was happening in my head as well as Randall Munroe can:
After all that though, we're two weeks after installing and everything is running fine
The first installed of likely several. Next I need to build a remote to control for these growing Lora projects.