About two years ago, a friend of mine contacted me and said he needed some help on a project. He was designing an aeroponic based system intended to support plants through the application of water and nutrients through the use of a high pressure mist. While he is rather skilled in agriculture, he has very little experience with coding or electrical design, so he contacted me to see if I could give him a hand. What started as a basic Python script running on a Raspberry Pi two years ago has slowly grown into a professional embedded project with a custom PCB, firmware written in C with the ESP-IDF, a mobile app for user control, and IoT support using AWS IoT Core. The scope of this project is far to large to cover in this single post, so this will instead be a brief overview of the individual systems required for this device. Additional in depth detail will be available in posts dedicated to each component of the system.
When designing a project of this magnitude it is critical to first assess what features will be necessary to achieve your desired results. My use case for this project is a control system that is capable of storing configuration data from users, controllable over the internet and from a mobile app, support for over-the-air updates, some form of user authentication for the IoT control, and is reliable enough to work consistently without constant supervision from the user. Bonus points for expandability in the event that I desire to create extra modules down the road, such as a light control system or pH balancer. This is sizeable feature list, so I have included a diagram below to give a visual aid for a basic project level overview.
Based on the above product requirements, we can prepare a list of the different software features this device will need to support. The following list contains links to detailed articles on each portion of the system. Notice: I am writing this overview on the system before writing the detail of each individual component, so depending on when you view this article, the following writeups may not yet be complete.
- Software Architecture
- Core Libraries
- Program Lifecycle
- Flash Storage
- User Configuration
- IoT Credential Storage
- WiFi Connectivity and Setup
- Connecting the Device to a Network
- Cloud Connection
- Over The Air Updates
- General Purpose Input/Output
- User Customization
- Power Loss Fallbacks
- Mobile App
- User Authentication
- Cloud Control
- Direct Device Control
- Cloud Infrastructure
- MQTT Broker
- Device Management
- User Authentication
- Device Provisioning
- Electrical/PCB Design
As you can see from the above list, the requirements for this project are rather broad. However, many of the requirements overlap. For example, flash storage is required to store user configuration regarding timing for the spray system as well as storing IoT credentials for device identification and security purposes. This is also closely linked to device provisioning as unique credentials must be flashed onto each individual device. These connections can be drawn between many of the different components of this system, making this less of a linear set of requirements and more of an interconnected web of systems. More detail on this can be seen in the Software Architecture writeup.
With each of the components laid out, we can now go into brief detail on some of the larger portions of the system.
When designing a software system it is critical that you choose a system or framework that provides the tools required to complete your specific use case. For my purposes, I chose to go with the Espressif Internet Development Framework, or ESP-IDF for short. The ESP-IDF is based on FreeRTOS, but includes a specific Hardware Abstraction Layer that makes interfacing with specific features of the ESP32 microcontroller very easy. This is very beneficial to me, as FreeRTOS gives me the ability to design systems that can complete two different tasks in parallel (such as turning on a GPIO pin to water plants while simultaneously sending data to a cloud provider) while the HAL greatly reduces the amount of time I as a single developer would have to spend wrestling the the hardware of the chip. Furthermore, the ESP-IDF is open source meaning that I can easily inspect the code in the event that I do not understand how a function works. This combination of integrated WiFi, an RTOS, and ease of use made it a great choice for me, with my lack of supporting developers.
Another desired requirement for this project is for it to be easily expandable. If I were to want to design another similar system in the future, it would be much easier if I could reuse much of my work to save development time. For example, if I were to want to develop a controller that manages LED systems for plant growth, I could likely reuse much of the code in the system I designed for spraying the plants with water. In order to facilitate this, I started by designing a core library that would contain all components that would be likely to be reused. Using this, I only needed to code core components such as WiFi connection and flash storage one time. Combining this library with Git version control gave me even more flexibility. It is very easy to have different branches with minor modifications in the event that core functionality must be minorly modified for a specific use case.
One of the most critical components of the Software Architecture is the Program Lifecycle. What happens when the device boots? What is the fallback case if WiFi network credentials are incorrect or missing altogether? These and many more questions are critical to building a stable and robust product. To simplify the explanation, I have included a very simple diagram that includes a basic version of the main events in the program lifecycle.
As you can see, once the system boots, it first checks for the most critical resource, user configuration regarding spray timing. If the data is found in flash, it starts two spray timers, one which tracks how long the spray system should be turned on, and one which tracks how long the spray system should be turned off. This is the primary function of this portion of the system, the spray and mist allows very fine grain control in the amount of water your plants receive, meaning this could be used with a variety of different types of plant. In the event that the spray timing data can not be retrieved from flash due to it being absent or corrupted, the system falls back to the firmware defaults.
Once the critical spray component is running the system begins connecting to the users WiFi network to allow both local and cloud control. It does this by checking for network credentials in it’s flash storage. If these credentials are absent or incorrect, the device instead hosts it’s own access point and REST server, which will allow the user to connect using the mobile app and input the SSID and password for their network. After a reboot, the device should then connect to the network correctly. Once it connects to a network with internet access, the system attempts to connect to AWS using encrypted credentials which were stored in the during a device provisioning step at the factory.
At this point the device is booted, has started it’s spray timing cycles, has either connected to a local network or is hosting it’s own, and has the ability to read and write to flash storage. If left alone, the device will run it’s spray cycles indefinitely. The user (and in some cases the MQTT broker) can still interact with the device to do configuration work, such as changing the timers on the spray system, updating the network credentials, or even starting an over-the-air update. All of these are triggered through one of two methods, either the user uses the mobile app to send commands directly to the REST server on the device, or the MQTT broker publishes a message on a topic that the device is subscribed to.
This is considerably simplified lifecycle of the device. As one could expect, there are many different edge cases in the event of unexpected behavior that are not shown is this diagram for the sake of simplicity.
Including completely remote control using the Internet Of Things was one of the most challenging portions of building this system. Two major requirements guided me to choosing Amazon Web Services as my cloud provider and MQTT broker. First, their scalability and very generous free tier. AWS has the benefit of handling all the low level infrastructure for this project. As more of these systems enter the field, AWS automatically handles the increased load on their servers without me having to manage anything. This is also extremely convenient as their free tier is very generous and allows you to send literally millions of messages on the MQTT broker without incurring any costs. This allowed me to design and build the system with no need to worry about expensive cloud costs.
Secondly, their features fit several of my products requirements. First of which was User Authentication. One critical component of any IoT based product is strong security, and user authentication is a huge part of that. If a user wants to change the settings on one of their systems remotely, how do we ensure that they can only access their own systems and not those of other users? In my case, AWS Cognito was the answer. This identity service lets AWS handle all of the authentication and password work, letting me work on the system as opposed to handling the sensitive task of storing passwords. AWS IoT also has a feature called the Thing Shadow. This creates a virtual representation of an IoT device on the cloud, which can then be controlled regardless of the physical devices internet connation status. Whenever the physical device gets internet access, it will simply check it’s virtual copy, or shadow, and update it’s local state. This is very important for a field like agriculture, where a device not receiving a timing update because of lack of connection could lead to decreased yields, or at worse, dead plants. Furthermore, AWS also has many other useful services, such as their Simple Message Service, which handles push notifications. This allows me to easily expand the system in the future if I so desire. For example, consider a module which tracks the humidity and temperature of your plants and then sends you warning push notifications to your phone in the event that they deviate from your set values.
Google Cloud Provider was the other major player I was looking into for this project. Their service was very appealing, particularly because I chose to build the mobile application using Google’s Flutter framework, which happens to play very well with GCP. That being said, AWS has recently added official flutter support to their Amplify platform which made me confident enough to choose them as my cloud provider.
The mobile application plays a critical role in this project as well. To cut costs and reduce complexity, no physical control is included on the device (with the exception of a planned factory reset button). Instead of including physical buttons which have limited modification ability after device deployment, or a touchscreen which is expensive and complex (not to mention may cause problems in a high humidity environment, such as one in which water is regularly being sprayed), I opted to go completely mobile for the control. While assuming 100% of users will always have their systems on a working internet connection would make design a lot easier, it would not make for a very robust product. The lack of physical control makes these “rainy day” scenarios even more dangerous. This was the main driver to give the device an offline mode in which the user could control it entirely over the WiFi network that the device itself hosts. Regardless of how the user actually connects to the device, the input is all done via the mobile app (unless someone were to go out of their way manually send HTTP requests to the device using some sort of REST client).
With the app being the primary method of control and monitoring for the system is critical that it is both up to date and stable. Again, being a solo developer makes designing for two platforms, Android and iOS, rather challenging. To combat this, I decided to go with Google’s Flutter framework. It is a rapidly growing cross-platform development framework meaning it roughly halves development time. Furthermore, it has massive community support, tons of open source widgets, and plenty of support. This made it an easy choice to design the mobile app with. The primary other alternative was Xamarin, a similar cross-platform framework developed by Microsoft. Xamarin had several problems however, the first being that it is somewhat reliant on Visual Studio. I primarily develop on a Linux machine, meaning I could have had trouble with some of the Visual Studio and .NET work that the framework required. It also builds UIs in XML as opposed to the widget based system written in Dart that Flutter employs. I personally find Flutter’s method to be much more intuitive. Because of these reasons I ultimately chose Flutter over Xamarin for the mobile app.
Local control provided several potential issues when it came to connecting to the devices. If a user was trying to control a device connected to their personal network, they would need to know the device’s IP in order to know what endpoint to connect to. For example, I connect a system to my network and it receives the IP 192.168.1.7, I would have to send an HTTP POST to the URL 192.168.1.7/api/settimings to update the spray timings on the device. For someone sitting next to the development boards with an active serial connection, this is not a big deal. Simply print the local IP to the serial terminal and use that in whatever REST client I chose to use. For someone who is using a completed product, this is a bit more of a challenge. In order to combat this, it is rather easy to have the devices consistently assign themselves a specific range of IPs. In the event that this is unavailable for some reason, the same trick can be done with the MAC addresses. Now, the app only has to search the network for devices with this unique range of MAC addresses or local IPs. The same can be done when the device hosts it’s own mobile network by changing the networks SSID to something unique.
These three components I have gone over are really just the tip of the iceberg on this project, but I don’t want this article to be 5000 words so I’ll leave the rest to more dedicated posts. With this brief overview hopefully you can get an idea of how this system works and how many moving parts are actually in a commercial product like this, even if the end goal seems relatively simple, such as spraying plants on a timer. This product is not yet being rolled out, but we are planning on starting a closed beta run in the next few months, if you are interested in the idea and would like to participate, you can reach me from the “Contact” page on this website.