What it is
The holidays have arrived, you are in charge of keeping the flowers alive. Presents have been opened, food has been consumed and your mind is far from those silent water gluzzling blossoms. This tutorial will cover creating a low-power LoRaWAN-based device that promises to keep your conscience clear and mood Festivus.
Features
- Creating low-power LoRaWAN devices
- How to measure power consumption in the mA and uA range
- Modifying existing models for 3D printing
- Long lasting soil moisture sensors
- Configuring The Things Network applications
- Reading I2C sensors and serial decoding with oscilloscope
- Switching sensors using MOSFETs
Requirements
- Basic electronics, soldering and Arduino experience
- Arduino environment (Arduino IDE, Visual Studio Code or similar)
- Access to a The Things Network LoRaWAN network
- The Things Network-account
- Rocket Scream Mini Ultra LoRaWAN or similar low-power Arduino compatible
- I2C Soil moisture sensor
- Soldering station
- Wires, resistors, protoboards etc.
- 3D printer
- M2-M3 nuts and bolts
- 2N7000 MOSFET
Recommended equipment
- Qoitech Otii Arc
- or uCurrent with Oscilloscope or Multimeter
Microcontroller
I wanted to use a Rocket Scream Mini Ultra LoRaWAN I had bought a while back. This development board promises 35 uA current consumption during sleep mode when powered by a 3v battery. Ever used the LowPower library in an Arduino-project? It is authored by the same guy who makes the Rocket Scream devboards, Phang Moh. The Mini Ultra LoRaWAN features a tried and proven ATMega328P-AU microcontroller and RN2483A/RN2903A LoRa transceiver. This is neither small or high tech by todays standard, but it's a good place to get started with battery powered LoRaWAN devices. The web shop states they are now retired, replace by the updated Mini Ultra Pro V3 (with radio).
Sensor
I am a big fan of Catnip Electronics I2C Soil moisture sensor, as it is easy to interface but mostly because it does not deteriorate with use, as it relies on capacitance. Other current-resistor based sensors with catodes and anodes are worthless as they destroy them selves with use. You could of course extend operational life by different techniques. The I2C Soil moisture sensor features a light sensor, temperature sensor and a relative moisture sensor. The ruggedized version of the sensor I am using covers the light sensor, so it can not be used. I will be using 3 sensors so I can monitor 3 different plants. To reduce data transmission I am only using one of the temperature sensors. You can get the firmware here.
Current consumption: 1.1mA @ 5V, 0.7mA @ 3.3V when idle, 14mA @ 5V, 7.8mA @ 3.3V when taking a measurement.
Note
Documentation warns:
Upon reading the moisture or temperature value, a value form the previous read command is returned and the new measurement is started. If you do rare measurements and want to act instantly, do two consecutive readings to get the most up to date data. Also you can read GET_BUSY register via i2c - it will indicate when the measurement is done. Basically the process goes like this: read from GET_CAPACITANCE, discard results, then read from GET_BUSY until you get '0' as an answer, then read form GET_CAPACITANCE again - the returned value is the soil moisture NOW.
During my testing I can not see that this is (longer) the case, as my my measurements are up to date. I have disregarded this warning.
Pinout
The pinout of the rugged version (with white cable):
- RED - VCC
- BLACK - GND
- BLUE - SDA
- YELLOW - SCK
Arduino library
A library is provided here. This is pretty much just an I2C-wrapper for each capability of the sensor.
Change addresses
Because this project utilizes 3 sensors, two of them needs to have the default I2C address changed to be uniquely addressable. Follow this Arduino sketch to do so.
Sensor circuit
The needed circuit is pretty simple. I2C master is accessible on the Rocket Scream Mini Ultra LoRaWAN pins A5 (SCL) and A4 (SDA). I decided to use 4 port screw terminals for each sensor and the circuit needed to provide hookups for power, ground, SDA(Blue) and SCL(SCK). The circuit was built on a standard breadboard, starting with one sensor and expanding to 3.
I2C pullup resistors
The documentation states that that neither the dev board nor the sensors provide I2C pullups, as is the preferred design. This means that when the I2C bus is idle, the signals need to be pulled up to positive logic level (3.3v in this case). Simply connecting each of the SDA and SCK lines to positiv power would be too strong for the master and slaves to break, so we need resistors in parallell. It is possible to calculate the appropriate pullup resistor values, but I always end up solving this the experimental way. The absolute most intuitive approach is to use an oscilloscope. The required setup will differ from vendors and models and getting comfortable with all variable conditions takes a bit of practice. YouTube is a great source to getting started, accompanied by the good old manual. My scope has the additional feature of being able to decode the I2C protocol, which is handy for debugging.
This is what it looks like when configured properly.
In this screen we have captured:
- Write 0: Start capacitance measurement
- Read previous capacitance measurement as 2 bytes (relative value of decimal 228)
- Write 5: Start temperature measurement
- Read previous temperature measruement as 2 bytes (24.2 degrees Celcius)
- Write 8: Put sensor in sleep mode
In my first attempt in finding an appropriate pullup resistor value it was too high, I believe it was 3k3 Ohms. The scope and the MCU was able to read the values just fine, but the curves were a bit saggy and seemed to approach idle high just in time for the next signal. I changed to 2k2 and was happy with the sharpness of the curves.
I mentioned the theoretical approach and we will see the many factors that come into play next.
I expanded the circuit with two more sensors. This changed the game on more ways than one. Not only was the load increased, the length of wires drastically increased on my poor breadboard. The latter would not be identical on the final circuit, though.
This is how the curves looked with the same 2k2 pullup resistors.
This would probably never be a problem, but I wanted to see if it could be improved.
I wanted to try to pull up the logic level idle high stronger, i.e. resistors with lower Ohm rating. This is 1k8.
Not a huge difference, but noticable. This is 1k2.
This resembles the one sensor setup more closely. Keep in mind that this is on the breadboard with quite a lot of wire.
Circuit on the breadboard:
This is the final circuit on the protoboard:
Arduino code
I mostly use Visual Studio Code these days, due to the Git-integration and familiarity with my professional work horse, Visual Studio. Arduino IDE, CLI or any other environment would of course do. I am going to assume you are familiar with Arduino development, board setup and library inclusion.
Clone this whole repo to find the source code.
See Board setup.
Libraries used
- RocketScream/LowPower
- Apollon77/I2CSoilMoistureSensor
- TheThingsNetwork/arduino-device-lib
- PaulStoffregen/AltSoftSerial
To learn more about how to use the individual libraries, explore the example sketches provided. We will not be straying far from the default usages.
I decided to split the device secrets into a separate file, secrets.h. I do not want to share these keys and this is a nice way not to forget they should not be pushed into the repository. If you are going to try to run the code you will have to replace the keys in this file as instructed in just a bit.
const char *appEui = "70B3Dxxx"; const char *appKey = "0D48A809xxxxx";
I start by declaring 3 variables of the sensor type. I believe it would be possible to use the same instance and change the address, but this is alien to me as a .net developer and a bit harder to read. The addresses, supplied as hexadecimal integers are the ones I have programmed the sensors with earlier.
I also define a number of milliseconds I want the device to sleep between readings. This will severely affect battery life, and outside debugging I can't imagine you would need to do this more than a few times a day. The actual value is provided to the radio module as it will be responsible for waking the MCU from sleep.
I defined a pin, A0, that was used to toggle power to the sensors. This can be done because a pin on the ATMega328P can safely source 40mA. The sensors provide a sleep mode, but as we will see this is not adequate for long battery life.
Outside Setup with the usual initialization of serial communication and TTN-client setup, the program does the following:
- Perform battery measurement averaging
- Power sensors and put sensor 2 and 3 to sleep
- Read sensor 1 and put it to sleep
- Wake sensor 2, read, put to sleep
- Wake sensor 3, read, put to sleep
- Power down all sensors
- Prepare payload and transmit
- Put radio to sleep with alarm
- Attach interrupt to listen for radio wake up
- Power down MCU
Power measurements and optimization
Developing low power devices that are supposed to last for years on small batteries is futile without proper measurements. I have previously developed a LoRaWAN device that draws 8uA in sleep mode and an 8mA average while measuring and transmitting. This device has so far survived on cold Nordic climate on the same 2x AAA batteries for 2 years. Firstly you will need equipment that is at least good for measuring current in mA and uA range. uCurrent Gold is made for this. Combined with a reasonable price multimeter such as Brymen BM235 you can measure low currents. The problem surfaces when trying to figure out what part of the code is running in any given instance. You will soon grow tired of commenting in and out parts, adding delays etc. You could use an oscilloscope with the uCurrent (Note: Dave Jones has stated that it was not meant for this and you will have to mess around with eliminating noise sources such as switching regulators (both in the circuit and from external power supplies). You could even combine this with serial decode and print messages in code. I did this for a while until I discovered ...
Otii Arc by Qoitech
The Otii Arc is in short a power supply, ammeter, voltmeter, oscilloscope, serial decoder, graphing tool and many other things in one easy to use package. I can't praise it enough! You can select a range of debug statements and get the exact consumption during this time.
The following shows two test runs:
- The red graph shows the board authenticating with TTN, not going into sleep, and periodically sending testdata. Note that no sensors are connected.
- The green graph shows the same as above but with MCU and radio going to sleep.
According to the documentation 35uA is pretty much the lowest consumption you can expect at 3 volts power supply. Great starting point! Beside using an external timer circuit this is the best we can do.
Next we see the effect of only connecting one sensor. It is not even read (though reading it has little effect). Keep in mind that we might end up doing a handfull of measurements per day, almost all of the time will be spent in sleep mode. This will not last years.
Turns out the sensor has a sleep mode:
I did a lot of more testing; disabling floating pins, testing different antennas, disabling serial output, using 1.5v power source, I believe you can download the Otii client and load the profile from the repository for further exploration.
Adding the two further sensors resulted in the expected - 3 times the sensor consumption in sleep mode. This is when I decided to toggle power to the sensors with a pin, and do some other trickery, such as putting the sensors that were not read at a given time to sleep.
We have moved from 35uA in sleep mode to about 1mA, back to 250uA.
MOSFET
I was adviced to look into switching the sensors using a mosfet. I got a bag of them home to test and was happy to see that a 2N7000 reduced current drain to 36.2uA in sleep mode. This is as good as I am getting it at this point and I am very happy with the results.
Otii Arc improvement
There is only one thing I am missing in my workflow and that is the ability to use the Arc as an FTDI programmer. I have not found a way to do this and I currently have to switch between uploading code changes and taking measurements.
The Things Network configuration
If you are new to TTN I suggest reading up on the great step-by-step guides on setting up gateways, applications and devices. In this project the setup was pretty standard, only a few things to note.
DeviceInfo
Before registrering Rocket Scream Mini Ultra LoRaWAN devices we need some info. The RN2483A/RN2903A LoRa transceivers come with an unique machine address embedded. This is great as we don't have to worry about hard coding this in our device program (Note: The appKey is stil unique to the devices and has to be hardcoded per. device). Run DeviceInfo sketch to retrieve the id, and use it as Device EUI when registrering a new device in the TTN application.
Payload decoder
The following JavaScript function takes care of converting the bytes from the device payload into something more structured and readable for further integrations with other systems.
function Decoder(b, port) {
var d = {}; d.battery = (b[0]<<8 | b[1]); d.capacitance1 = ((b[2] & 0x80 ? 0xFFFF<<16 : 0) | b[2]<<8 | b[3]); d.tempc1 = ((b[4] & 0x80 ? 0xFFFF<<16 : 0) | b[4]<<8 | b[5]) / 10.0; d.capacitance2 = ((b[6] & 0x80 ? 0xFFFF<<16 : 0) | b[6]<<8 | b[7]); d.capacitance3 = ((b[8] & 0x80 ? 0xFFFF<<16 : 0) | b[8]<<8 | b[9]); return d; }
The Things Network integrations
Data Storage (TTN)
I always add the integration called Data Storage as a debugging tool. This is creates a free REST-based endpoint providing JSON-data using Swagger. This gives you a 7-day data transmission storage that you can access by any technology that can make an HTTP-call, for instance curl. It creates a nice UI you can access from any browser to query data from your devices. You need to authorize with an application key, this can be found in the TTN console, under [your application] -> Overview -> Access Keys. The default will most often do, unless you want to separate access by creating separate keys.
Data visualization and rules
There are so many different options for integrations with TTN to visualize your data and create triggers/rules. They all have pros and cons and for this project I only wanted some quick graphs and some kind of alert when the plants need water. Some integrations require modifying the decoded payload (e.g. IFTTT), while others even require serializing the data on the device (Cayenne). This can lead to problems when using multiple integrations, and I don't think it's an architecture that is suited to scale. AllThingsTalk keeps all the integration details on the receiving end.
You have to supply quite a lot of details about the devices and applications, so I am worried how this would scale to thousands of devices. Also with the current ATT - TTN integration you have to respecify the binary conversion in a proprietary format called ABCL.
I remember struggling with this quite a bit a few years ago, but have a look at my converter in the repository and I hope you will have a smoother ride.
{ "name": "soilmoisturesensor", "comment": "Sent when lid opens, with battery voltage.", "version": "1.0.0", "sense": [ { "asset": "battery", "value": { "byte": 0, "bytelength": 2, "type": "integer", "signed": false }, "comment": "Voltage value as millivolts." }, { "asset": "tempc1", "value": { "byte": 4, "bytelength": 2, "type": "integer", "signed": true, "calculation": "val/10" }, "comment": "Temperature in Celcius." }, { "asset": "capacitance1", "value": { "byte": 2, "bytelength": 2, "type": "integer", "signed": false }, "comment": "Capacitance 1" }, { "asset": "capacitance2", "value": { "byte": 6, "bytelength": 2, "type": "integer", "signed": false }, "comment": "Capacitance 2" }, { "asset": "capacitance3", "value": { "byte": 8, "bytelength": 2, "type": "integer", "signed": false }, "comment": "Capacitance 3" } ] }
So now we can display graphs, make dashboards and create rules. I will leave you with the official documentation on how to create the integration but I will be happy to answer questions in the comments if you encounter problems.
I defined rules for when the sensors measured dry soil.
With the AllThingsTalk Maker app installed on my devices I get push notifications when the plants need watering. You can also use the Watchdog feature to create notifications when a specified amount of time has passed with no data being received from the device, i.e. bugs or dry battery.
Enclosure
As an indoor device creating an enclosure is vastly simplified. The Rocket Scream Mini Ultra LoRaWAN comes provided with models for a nice housing. However, this enclosure does not provide room for an additional stacking protoboard with circuitry. Creating an enclosure from scratch is more time consuming than one might think so I decided to use this model as a starting point and modifying for my requirements. In previous projects I have had to use software such as Netfabb and Materialise Magics to fix and modify models, this time I was in luck. I only had to lift the lid a few mm and cut some holes for the sensor wires.
I started by cloning the models from the repository. I 3D printed the models as-were to simplify finding desired dimentions. The file that needed modification was RocketScream_MiniUltraLoRaWAN_top.stl. I chose Autodesk Fusion 360 as my tool, made a new project and imported the model from the "Open.. Open from my computer" option. A SketchUp model was available but I was not able to convert it using Fusion.
After importing the .stl-file I first selected it and selected "Flat shaded" in the context menu. This makes selecting the correct surfaces easier. Next, select "Mesh to Brep" with the option New Body so you can start over if anything goes wrong.
Now it was just a matter of finding a suitable height to slice the model in two. I wanted to keep the features such as the indents for the buttons, the cavity for the antenna and the screw slots. Luckily the surface where the inside of the top is situated was just a perfect plane between these features. Therefore I could just select a polygon of this surface as my splitting tool.
Next, select Modify -> Split body to end up with two bodies.
At this point I made a measurement of my 3D print of the original models with the desired content to find out how much I had to raise the lid. I wanted to try to keep the circuitry on the underside of the protoboard to restrict the height, as it became clear it was towering more than I liked. 15 mm seemed enough.
I then selected the topmost of the two new bodies, selected Move and entered 15 mm. This is when I noticed something was wrong. The Measurement tool told me the model was 10 times the size of the printed version. My guess is something went wrong during import - it has no way of knowing what unit of measurement is intended and had to make an educated guess. To rectify I selected the two bodies, selected Modify -> Scale 0.1.
Back to Move, I moved the top part 15 mm upwards (Z-axis in my case).
Adjusting the view angle I was able to select the profile that had emerged underneath the part I had just lifted. Now comes the magic - selecting Create -> Extrude, with option Extent = To Object, Object = the bottom body, I was able to rejoin the two parts as desired.
The next step was to cut holes for the 3 sensor cables. Reproducing detailed steps with illustrations would explode an already extensive tutorial but in short: Create a new Construction plane based on one end of the lid and start a new sketch. Project geometry from the body to the new sketch to attain reference points for alignment. Place three circles with a generous diameter of the cables. Extend the circles with rectangles so they become slots with respect to the lid edge. Extrude with cutting effect to punch out the holes.
I also had to readjust the channels for the screws or there would not be room for the protoboard.
Next, select the new body and chose "Save as STL" to prepare for printing.
Using Simplify3D the model passed all checks for "watertightness" and printing could commence.
Conclusion
I hope this project may inspire some interesting low-power applications and that a few less plants will meet a dry demise. Thanks to Arild and Jørgen for fruitful discussions and valuable help. Thanks to dad for donating a tablet!
Source code and models
Top Comments