What it does

Living above the Arctic Circle poses extra challenges for maintaining plants in the winter. Remembering to water them is arguably a non-geographical problem.
This project is a highly customizable autonomous plant irrigation system. Being used indoors or outside is only a matter of ensuring the components are protected from moisture and the plants protected from low temperatures. In it's standard configuration it can monitor soil moisture in two different pots or outdoor areas. One water pump supplies both areas and it's behavior can be customized via the IoT Central interface. A plant light provides the right wavelengths when sunlight is missing and can be configured to give the plants a rest during the night.
The implementation is pretty generic and can easily provide a starting point for controlling anything from heating, lighting to air conditioning. While this is applied in a domestic setting, I have a grand scale in mind, particularly in a smart city context.
This project posed a great opportunity to explore key features of Azure Sphere and the MT3620 starter kit, and put it to the test in a real-world project. Having made many battery powered cloud-enabled prototypes with low-energy embedded systems and lpwan's, security has become my main concern. While sacrificing power consumption in the mA and μA range (wake and deep sleep) the Azure Sphere platform proposes unrivaled out-of-the-box security measures. Plans of low-power versions have been revealed, so learning the ecosystem at this point is a good investment.

What you can expect

This project description is not mainly intended to show how to get my exact setup working for you, but rather to show how it was made, what challenges I faced so you may be inspired to make your own project. Many of the challenges I spent a great deal of time solving were due to early versions of the frameworks and the authentication regime and have since been improved or documented well. Getting in early is not plain sailing, but can be rewarding in an educational manner.

Who this is written for

The main audience I have in mind when describing my approach are people who have a background in high-level software development. If you, like me, have spent a career in software, been intrigued by embedded programming such as Arduino, and want to make a secure sensor solution connected with cloud-hosted services, I hope this can help you along. It will broaden your view and open up new fields for the future. If you are an electronics engineer I believe there are interesting takeaways on the architecture, services and security side.


Mixing water with mains voltage in a rushed electronics project based on beta APIs could cause a lot of harm and damage. Please don't attempt things you feel uncomfortable with, and don't work with high voltage without someone around who knows CPR. Although not due to electrocution, I was recently revived after a cardiac arrest, not a fun experience. It is just not worth it.


By Arturo Urquizo -, CC BY-SA 3.0,
The more traditional method the solve this problem electronically is using proportional–integral–derivative controller, PID . This is in my opinion much more fun!

Features and requirements

Project features

Key technologies explored

  • Azure Sphere
  • I2C serial protocol for writing device libraries
  • Oscilloscope with serial decode capability for I2C debugging
  • Click boards
  • Controlling high and low voltage components and appliances using relays (Relay Click)



  • Visual Studio
  • Azure account
  • Azure IoT Central
  • Azure Sphere SDK
  • iotc-explorer
  • Fusion 360
  • (Optional) Simplify3D



  • Multimeter
  • Soldering equipment
  • (Optional) Power supply
  • (Optional) Oscilloscope
  • 3D Printer

Edge or Cloud

The application for the MT3620 has been designed so it can operate autonomously, meaning the logic for taking measurements and deciding whether to turn on pumps or light is embedded. This is not intended to make it cloud-independent, just to make it more responsive and to explore different architectures. In this mode IoT Central is used for receiving telemetry and for providing desired behavior. However, it can also be put in a slave mode where it is controlled from the cloud via rules. In the latter case this is a great example of abstracting specific terminology, logic and behavior, reducing the need for software updates on the edge.

Azure Sphere MT3620 Starter Kit

If you have not already taken the devkit for a spin, please follow this comprehensive guide by Cabe Atwell that covers everything you need to know. I really wish I had gotten the advice of keeping a spreadsheet of all the accounts, passwords and devices before I started. I currently have two accounts, one work-account, two Azure accounts, two IoT Central accounts, one Power BI account and four devkits to keep track of.
  • Install Visual Studio for C++
  • Azure Sphere SDK
  • Create an Azure account
  • Configure and claim devices
  • Prep device for debug
  • Try out a Blink example
  • Create an Azure IoT Central Application
  • Register devices in IoT Central
  • Generate and validate certificate

Now clone the  repo. I usually start by making a new personal project repo out of Spend some time following the tutorial on how to make a Sphere application that connects with IoT Central as described here .


After having tested quite a few soil moisture sensor I have found Chirp! I2C Soil moisture sensor from Catnip Electronics a durable choice. It is actually not the Chirp! version, rather the silent rugged sensor version. But I like the name. You will not have to worry about this sensor getting wet, dirty or corrode as it measures capacitance, not conductivity. It is open hardware and software, find the repository here. I have used this sensor in Arduino environment previously, find the original library here. Coming from a Arduino world I was used to finding libraries for communicating with digital sensors ready for use. The Linux based MT3620 is new and libraries have to be written. Included in my source code is the I2C-library I wrote to talk with the Chirp. This was a highly educational exercise. My takeaways were getting used to c-programming(having spent the passed 15 years in .Net), investigating different c-implementations and bridging missing features, converting Arduino-libraries, understanding the I2C protocol and using an oscilloscope to debug I2C.

I was looking for some clever way to determine if the water tank was empty until I realized I could just use the same capacitive sensor I use for the soil. Now I can use a rule in IoT Central to give me a reminder to fill up the tank when it is about to run dry. In edge-mode the device will not try to water plants if there is no water left.


After moving from pure software to electronics a few years back the best investment I have made to enhance my understanding was the oscilloscope. I started with the most basic soldering kit my dad gave me (thanks Dad for steering my on the right course!) and 3D printed an enclosure. Getting in early on Azure Sphere you should not be surprised if you have to write or convert libraries for digital sensors. Having a scope handy will make a huge difference. Working with the sensors for this project I would not be able to make it in time without a scope due to my lacking understanding of the API, timing issues, weirdness in the sensor firmware, noise, unclarities in pullup values on the I2C bus, timing in the Sphere application, to name some challenges. Since the first basic scope, I have upgraded to one with much more handy features, my favorite being serial decoding and triggering.

Serial Decode

I also make heavy use of Bus Pirate to interact live with sensors, but this requires that you have figured out all settings and levels.

Bus Pirate

MT3620 Starter Kit I2C pullup resistors

I was reading conflicting information whether the starter kit had built-in pullup resistors on the I2C bus. The bus expects the signal to be pulled up to HIGH when idle. When working with different Arduino-compatible hardware you usually have to supply this yourself, usually in the form of a connection to logic level HIGH via a certain value of resistor. Calculating this value is very complex, and an experimental approach is my preference. Too high a resistor value (too much resistance) will not pull up to level HIGH, or not fast enough.

Too low resistance will not allow the clock or data signals of the sensors or the bus to pull down to LOW. For this new hardware I could not rule out that the Click modules provided the pullup, although this would be a very bad design. The reason simply being that two sensors would provide this twice and would not work. Regardless, being able to use a scope to look at the signals at the speed you are intending to use is a great advantage. We are simply looking for "square enough" signals. If you have to provide pullup yourself in a design, this amounts to setting up the scope, preferably in serial decode+trigger mode, and testing different resistor values. I spent quite a lot of time getting comfortable in configuring serial decoding on my scope, but it is all worth it. Refer to your manual, YouTube and brew a large pot of coffee.

In conclusion you do not need to provide pullup when using a fair amount of sensors with short wires on the I2C bus.

No extra pullup added:

No pullup provided

No extra pullup added, zoomed:

No pullup provided

10kOhm pullup added, no significant difference:

Pullup provided explicitly

10kOhm pullup added, no significant difference

Connecting the sensors

While the two Click interfaces are great for rapid prototyping, you quickly run out of slots. I wanted to connect at least two soil moisture sensors and searched for a Click module with e.g. Grove sockets. This would have provided easy connect/disconnect and hopefully many sockets. No luck. I could have connected a Grove hub to the on-board Grove socket, but I was trying to limit the amount of floating PCBs, to reduce complexity in designing the housing.

I tested driving the small pumps at 3.3 and 5 volts from the power rail of the devkit, more on that later, and needed access to ground and power. Proto Click seemed the way to go. Had I done this over I would have still used the Proto Clicks, but found Grove connectors with the correct spacing insted of terminal blocks.

Proto Click with Soil Moisture Sensors

The design is really simple: One terminal block provides a number of ground connections, another is split between 5v and 3.3v. The middle block connects each other terminal to I2C SCL and SDA. This is done by making two rails underneath the Proto Click and alternating which terminal connects to which. I also added two short wires to these SCL and SDA rails as tabs for easy connection to the oscilloscope probes. I use yellow wire for SCL and green for SDA.

Proto Click with Soil Moisture Sensors

Proto Click with Soil Moisture Sensors

Proto Click with Soil Moisture Sensors

Proto Click with Soil Moisture Sensors

Only thing left was to strip and add a bit of solder to the sensor wires before they were screwed in place. I have a bit of bad experience with these kinds of narrow wire and screw terminals losing connection, that being the reason I recommend finding friction based connectors.

Proto Click with Soil Moisture Sensors


Writing library for I2C communication

This was a first for me and in a foreign programming language. I read James Flynn's great blog posts on the topic, connected my scope and just went function by function through the Arduino library. A few times I had to look at the firmware source code and I think I have spotted an error in the way the address is reported. Not important, I have to define the address explicitly anyway. In the end this was an easy library to port, not much logic to handle just writing and reading registers.

Serial decode

Explore SoilMoistureI2cSensor and i2cAccess source code for details.


Change address

One important aspect was that the sensors all come programmed with the same I2C address. To be able to address them individually new addresses need to be assigned. Find the commented out function ChangeSoilMoistureI2cAddress to perform this step. Just remember not to have more than one sensor connected at a time!

The assigned addresses are hardcoded as the following:


static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress1 = 0x20;

static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress2 = 0x21;

static const I2C_DeviceAddress WaterTankI2cDefaultAddress = 0x22;


I do report the addresses and version numbers as read-only Properties in IoT Central, this could be further improved by making them Settings that could be changed remotely. I am not sure this is something you would need, or even a good idea.


Accessing the I2C bus

To actually be able to communicate with the bus a few things have to be in place. Good articles have recently surfaced on this topic and I will just provide references and key settings. The app_manifest.json was extended in the Capabilities collection with the following key and value:


"I2cMaster": [ "$MT3620_ISU2_I2C" ]

The bus is initialized like this:

i2cFd = I2CMaster_Open(MT3620_ISU2_I2C);

I2CMaster_SetBusSpeed(i2cFd, I2C_BUS_SPEED_STANDARD);

I2CMaster_SetTimeout(i2cFd, 100);




In the planning phase I chose to use the Relay Click-modules for this project mainly because I did not have full overview of the actuators I would eventually use. Plus James Flynn had provided libraries that would reduce risk of having too many unknowns. See the relay library. For the light this is a safe bet, for the pumps other options would be less overkill.



At the point of planning and ordering parts I hoped I could solve the watering problem without using a current hungry DC motor. I made attempts in using magnetic solenoids and the principle of siphon and even servos to make water flow, but in the end found it more involved than just using a pump. Some DC motors come with flyback diodes installed, some with capacitors. As I did not know what my pumps looked like inside, I cut one open.


Nothing. So I added a diode as close to the motor as possible.

Flyback diode


Alas, I had not taken a separate power supply into consideration and wanted to see what would happen if I ran it from the board's 5 volt power rail. I connected it to the exposed ground and 5 volt of the proto click. When running the motor the oscilloscope in serial decode mode displayed noise.

Serial Decode

The MCU would read the sensors for most of the time, but intermittently fail and not recover. This seemed to me to be a real problem, but exaggerated on the scope. I tried to keep the probes as far away from the wires and motor as possible, to no avail. I concluded this was the reason one should not share power supply between a motor and sensitive circuits. To my surprise changing to using my non-switching lab power supply made no visible difference!

Lab power supply

I even swapped the Relay Click with an identical one to see if it was faulty. This has left me puzzled and I hope someone can share some light. Regardless I decided to use a 12 volt power adapter from mains and an adjustable regulator to 5 volts. I also added guards in code to try to minimize the chances of I2C access during motor operation.




Plant light

Plant light

I used an AC plant light from my local hardware store. It is plugged into mains, I only opened the chord an split one of the leads in two. I used another piece of two-lead wire to guide the split lead into Relay port 2. This way the relay breaks the circuit when off and leaves the two circuits isolated in any other regard. This part of the project can really be used for controlling a lot of external systems, even though appliances are moving toward more complex operations. Please don't attempt experimenting with this alone if you are inexperienced. I got several shocks of 230 volts and it was unpleasant.

Relay light


Relay 2 is controlled using two settings I call Relay2OnTimeSetting and Relay2OffTimeSetting. These are Date/Time values that simply tells the application when to switch the relay on and off. In code I compare the Time part of these settings, adding up number of minutes since 0:00 to local time. This is a simplification that might have some timezone issues on edge cases, but it seems to work. Please explore the function SwitchOnLampAtDayTime for more insight and how telemetry is sent.


JSON DateTime

I had to relearn string manipulation in c and found it quite a bit of a chore compared to it's high-level equivalents. In particular I had some struggles deserializing the digital twin from JSON-format. Not only does c not have a concept of strings, only arrays of chars, you don't have classes, only structs and pretty low-level libraries for manipulation. On top of that I found that for example the implementation of time.h did not include methods that my Stack Overflow results pointed to. It made me aware of how spoiled us .Net developers are. So to deserialize a DateTime-field I was left hanging. I am sure the time-library will be extended so I ended up making a simple hack for now. In my code you will see how I parse the field indexing the hour and minute part I am interested in and hope the number placements don't change...


Azure IoT Central

So many great guides have been written on setting up IoT Central, I will only supply some screenshots and key properties used in my project.

IoT Central


Look in the function SendTelemetryMoisture how these sensor values are read and reported. Notice how the names are enumerated so it is possible to make a loop that can support any number of sensors.

"Soil Moisture #1", Capacitance1

"Soil Moisture #2", Capacitance2

"Water Tank Capacitance", CapacitanceWaterTank

"Temperature #1", Temperature1

"Temperature #2", Temperature2

"Water Tank Temperature", TemperatureWaterTank


static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress1 = 0x20;

static const I2C_DeviceAddress SoilMoistureI2cDefaultAddress2 = 0x21;

static const I2C_DeviceAddress WaterTankI2cDefaultAddress = 0x22;


static const I2C_DeviceAddress moistureSensorsAddresses[3] =







static char temperatureSensorNames[3][21] =







static char capacitanceSensorNames[3][21] =








These settings are handled in the function TwinCallback.

"Relay #1", Relay1Setting, ON/OFF, Will be overridden by local logic, for debugging

"Relay #2", Relay2Setting, ON/OFF, Will be overridden by local logic, for debugging

"Plant lamp on time", Relay2OnTimeSetting, Show Time On, "Toggles time of day to turn on plant light. The date-part is ignored. Relative to device local time. Must be before Off time."

"Plant lamp off time", Relay2OffTimeSetting, Show Time On, "Toggles time of day to turn off plant light. The date-part is ignored. Relative to device local time. Must be after On time."

"Water pulse seconds", Relay1PulseSecondsSetting, Seconds, 0, 0, 10, "Dictates for how long water pumps should run at a time."

"Pause between watering", Relay1PulseGraceSecondsSetting, Seconds, 0, 0, 36000 (10 hours), "Minimum number of seconds after watering the next attempt may be made."



These read-only properties are reported during HubConnectionStatusCallback.


"Soil Sensor #1 Version", SoilSensorVersionProperty1, Text

"Soil Sensor #1 Address", SoilSensorAddressProperty1, Text

"Soil Sensor #2 Version", SoilSensorVersionProperty2, Text

"Soil Sensor #2 Address", SoilSensorAddressProperty2, Text

"Water Tank Sensor Version", SoilSensorVersionProperty3, Text

"Water Tank Sensor Address", SoilSensorAddressProperty3, Text



I did not go deep into commands in this project as I have done so in the past.


The names of the commands are hardcoded as such:

static const char Relay1PulseCommandName[] = "Relay1PulseCommand";

static const char Relay2PulseCommandName[] = "Relay2PulseCommand";


Look in the function DirectMethodCall for deserialization and how the code responds.



If you wish to control the devices via rules, there are many options. One of them is Flow.





Follow this guide to install and monitor messages. I use the authentication method of "iotc-explorer login "SharedAccessSignature sr=5c31d6e..." providing the whole generated string from Access Tokens in IoT Central. More info and source code.



What really moves an electronics project from an experiment to a prototype is a custom enclosure. I spent a good portion of this project measuring, modelling and 3D printing a house that would protect the electronics and make it stand out. I use calipers a lot!


Measurements 3D

I started out taking measurements of the starter kit and Click boards. I then proceeded modelling them in Fusion 360. It does take some extra time if you can not find existing models of your hardware, but it is almost always time saved in the end.


From experience stacking electronics add errors in placement. Therefore I did not model final holes for fastening the board to the base. Instead I drilled holes after printing and added nylon hex nuts and screws.


Let me also point out I started experimenting with low-poly aesthetics be fore a certain truck was unveiled






I also made some custom fixtures for holding the water tubes in place. They were fixed with epoxy.


Tube fixture


Tutorials on low-poly modeling in Fusion 360:


Water tank

I was not prepared to test this system with a continous pressurized water supply. I therefore bought a simple plastic container I could use during development. I fixed the pumps to the bottom using epoxy. The surfaces were smooth, so I roughened them up using sand paper and a knife.




Tank lid



  • Only supporting positive temperatures made life easier with limited project time. For plants this is fine, but if you attempt to repurpose the project it's worth mentioning.
  • Liquid pumps usually need to be primed, meaning you might have to suck some water into it to get it running.


Source code and models