Internet of Microcontrollers made easy with Toit x Soracom

Pycom Microcontroller

When you are planning an IoT project, what do you think of first? I think most people first think of what type of devices to use, as that choice will significantly impact the solution’s feasibility, cost, and scalability. 

There are many different options and parameters to consider, but one crucial choice would be what to use to run the logic on the device side. Roughly speaking, there are two categories to choose from:

  • Linux-based devices: Any computers that run Linux or a full OS fall into this category. Raspberry Pis, similar single-board computers (SBCs), and gateways that run Linux as their operating systems are handy and popular for implementing logic for IoT use cases. 
  • Microcontrollers: A tiny board with CPU, memory, and interfaces to connect digital/analog peripherals is also a viable option for IoT use cases. What is often required at the edge/field is simple. System on-chip microcontrollers with integrated network interfaces such as ESP32 are becoming popular choices for IoT. 

Each of these options has pros and cons. 

Development on Linux-Based Devices can be familiar but at a price

Linux-based devices can be easier when getting started because they can run almost any popular programming language and application framework. This means developers can use the skills they already possess to easily write their own code for those devices. Integration to cloud services should also be simple because cloud service SDKs can run on them. 

There are also solutions for device management and remote access available. If a device is connected with Soracom, the user can use Soracom Napter to open an ephemeral port and use the SSH, RDP, or admin UI of the device in a secure manner. 

As those devices have to run a complete operating system with rich features built-in, however, the power consumption and cost of devices can get a little high. In IoT use cases where devices have to run with a battery, in particular, Linux-based devices cannot always fulfill the requirements for a decent battery life. 

Microcontrollers are more affordable, but require more involved development and management

Microcontrollers may involve more of a learning curve for developers who are not already familiar with them. The choices for programming language and development frameworks are more limited. Developers can be more productive with a high-level language such as Python and Javascript, but they are not optimized for embedded devices with limited CPU/memory resources. The most commonly used language for embedded devices is C or its variants because it is close to an assembler and the compiled code requires a minimalistic footprint. 

There are many challenges we have to face as most developers are more used to what is offered by modern programming languages. For example, you need to be careful not to leak memory because garbage collection is not done automatically!

When it comes to operating systems, you’d have to choose an embedded OS with fewer  features than an OS like Linux, or you’d have to write code without an OS at all! Developing an application without features offered by an OS such as a file system, threading, and IO interruption, is hard. 

When integrating to cloud services, you’d have to find an SDK that suits the environment and code with it. Moreover, a bug in your application can crash the entire device and make it a brick in a remote location if there is no layer that can isolate your application from the rest of the system and restart the application when it crashes.  

Moreover, writing firmware or application code for those devices cannot be a one-time job. You’ll always need to have a way to update the firmware and manage versions remotely. Unlike Linux-based devices, there are not many off-the-shelf solutions for this challenge. 

Why bother with microcontrollers then? Because they are much more affordable and consume much less power. They are quite useful when implementing battery-powered IoT devices. Because of that, we see that experts in the IoT space consider microcontrollers more often than before. 

Isn’t there a way to combine the best parts of both worlds?

Do we really need to choose between Linux-based devices and microcontrollers? Can’t we combine the best parts of both worlds and have a microcontroller that can be easily integrated with cloud services, and programmed and managed without a steep learning curve and headaches? 

The answer is YES! That is what I want to talk about in this blog. 

Toit: Cloud-managed container on ESP32

Our partner Toit offers an elegant solution to address the challenges of development for microcontrollers. They have implemented a thin layer on top of FreeRTOS, a popular real-time OS widely used for microcontrollers, that enables the user to run an application as a container. Yes, like a Docker container on Linux!

Plus, you do not have to use a low level C language to write an application. They have developed a lightweight, modern language for IoT development called Toit. It is a high-level, object-oriented language with garbage collection. Compared to writing in C, you could achieve a lot more in less time, without runtime issues such as memory leaks and segmentation faults. 

Once you write code in Toit, you can deploy it to a remote microcontroller via their web console or API, which means we can bypass the challenges in microcontroller development mentioned above.

Let’s see how it works.

The Setup

First, let’s get an ESP32 board. I chose the Pycom GPy board. It supports all important radio interfaces, i.e. WiFi, Bluetooth, and Cellular (LTE-M and NB-IoT). Compared with a single board computer and an additional cellular modem, the savings are considerable. The retail price of this particular board is around $50USD. You may be able to find similarly a priced SBC, but you would need to buy a cellular modem separately.  

I’ve inserted a SoracomM SIM card into its SIM slot so the board can connect to the Toit cloud from anywhere via an LTE-M network.

The next step is to flash the board with the Toit firmware. For that, I downloaded Toit CLI from their website and set it up on my Macbook. 

I have connected the Pycom GPy to my Macbook via a USB cable. To flash the firmware of a Pycom GPy board, I need to set firmware flash mode. I do so by connecting the P2 pin and the ground with a male-to-male jumper cable and pressing the reboot button.

And then, I ran the following command to flash my Pycom GPy with Toit firmware. Note that I have specified the APN as `soracom.io` to connect to an LTE-M network with my Soracom SIM.

% toit serial reinstall \
  --firmware v1.4.2 \
  -p model.cellular.enabled=1 \
  -p model.cellular.tx=5 \
  -p model.cellular.rx=23 \
  -p model.cellular.rts=19 \
  -p model.cellular.cts=18 \
  -p model.cellular.pwr=27 \
  -p model.cellular.monarch=1 \
  -p cellular.apn=soracom.io

Now that we’re all set, let’s go to the Toit web console and claim my device. I copy and paste the device ID, and hit the claim button.

Then, boom! The device showed up and is connected to the Toit cloud!

Now let’s go to the “CODE” tab and run a code. I found the following code snippet on the Toit library  document

import net
import http

main:
  network_interface := net.open

  host := "www.google.com"
  socket := network_interface.tcp_connect host 80

  connection := http.Connection socket host
  request := connection.new_request "GET" "/"
  response := request.send

  bytes := 0
  while data := response.read:
    bytes += data.size

  print "Read $bytes bytes from http://$host/"

It is a simple code snippet that opens a connection to “www.google.com” port 80, sends an HTTP request, and shows how many bytes have been received. We can copy and paste the code to the code tab and hit the “Run” button.

And… it worked! 

It was so easy, right? But just in case you missed it, note that we just deployed an HTTP client code to an ESP32 microcontroller over the air! It happened on my desk this time, but this can be done even if the board is thousands of miles away, as long as it is connected to a Soracom LTE-M network! How powerful is that! 

Let’s make the board do a real job: Connect a sensor and measure the environment

Now, let’s make it more useful. I got a Bosch BME280 temperature, humidity, and pressure sensor. If we connect this to the board, it should be able to measure the environment. I connected the sensor as in the picture.

One thing to note is that I had to use P12 and P11 of Pycom GPy for GPIO pins 21 and 22, respectively because the board does internal remapping of its pins and GPIO (GPy P12 is mapped to GPIO pin 21 and GPy P11 is GPIO pin 22). See the Pycom spec sheet for more details. Thank you Anders Johnsen from Toit for pointing it out!

Then copy and paste the code found in theToit  Weatherstation tutorial below and hit the run button.

import gpio
import i2c
import bme280

main:
  bus := i2c.Bus
    --sda=gpio.Pin 21
    --scl=gpio.Pin 22

  device := bus.device bme280.I2C_ADDRESS_ALT

  driver := bme280.Driver device

  print "Temperature: $driver.read_temperature C"
  print "Humidity: $driver.read_humidity %"
  print "Pressure: $driver.read_pressure Pa"

Very cool! We just measured the temperature, humidity, and pressure in my room! 

Now, connect it to the cloud! 

This would be more interesting if we sent it to the cloud, because then we could potentially collect data from many sensors like this and analyze it. Let’s do that! But didn’t I say you’d have to find an SDK suitable for the microcontroller and write code with it? Well, the good news is, we don’t have to in this setup. Why? Because the board has connected with a Soracom SIM, which offers cloud connectivity, not just a simple internet connection. 

When Soracom receives data, we can store it in the Soracom cloud or forward it to the cloud service of your choice, whether that’s AWS IoT, Azure IoT, or Google IoT Core (https://www.soracom.io/solutions/connecting-to-cloud/). You can also invoke specified cloud functions deployed on AWS Lambda, Azure Functions, and Google Function, and send responses back to the device. That means you can integrate a microcontroller to cloud services without an SDK or provisioning credentials.

Let’s see it in action! We go back to the Soracom Web console and configure my Soracom SIM to store data in Soracom Harvest, Soracom’s built-in data storage service. 

First, I go to the Soracom console and select my SIM.

Then, we click on the “Actions” menu and select “Change Group” to create a new configuration group that has Soracom Harvest enabled.

Let’s name it “Toitware demo” for now, create and assign the SIM to the group.

Now my SIM belongs to the newly created group. Let’s click on it and configure the group configuration.

Scroll down to “SORACOM Harvest Data”, open the tab, enable the service and click on “Save”.

That’s it! Now Soracom Harvest is enabled for my SIM. Whenever data is received from the SIM, it is stored in Soracom Harvest  where we can query and visualize the data.

Let’s now send the data from the board. I have modified the weather station code a bit so that we convert data to JSON format and send it out to Soracom endpoint (http://unified.soracom.io). The changes I made are:
– Defined a function “read_sensor_data” that reads data from BME280 and returns it in JSON format. It is largely based on the sensor reading code in the above.
– Defined a function “send_data_to_soracom” that takes content and content type as the arguments and sends the data to the Soracom endpoint with HTTP POST. It is largely based on the HTTP sample code we saw earlier.

– Modified the main function to call “read_sensor_data” and “send_data_to_soracom” in the order

import gpio
import i2c
import bme280

import http
import net

main:
  data := read_sensor_data
  send_to_soracom data "application/json"

read_sensor_data:
  bus := i2c.Bus
    --sda=gpio.Pin 21
    --scl=gpio.Pin 22

  device := bus.device bme280.I2C_ADDRESS_ALT

  driver := bme280.Driver device

  temperature := driver.read_temperature
  humidity := driver.read_humidity
  pressure := driver.read_pressure
  
  print "Temperature: $temperature C"
  print "Humidity: $humidity %"
  print "Pressure: $pressure Pa"


  json := "{\"temperature\":$(%.1f temperature),\"humidity\":$(%.0f humidity),\"atmosphericPressure\":$(%.1f pressure)}"
  return json

send_to_soracom content contentType:
  network_interface := net.open

  host := "unified.soracom.io"
  socket := network_interface.tcp_connect host 80

  connection := http.Connection socket host
  request := connection.new_request "POST" "/"
  request.headers.set "Content-Type" contentType
  request.body = content.to_byte_array
  response := request.send
  
  print "Response was $response.status_code $response.status_message"

Let’s run it as we did before. 

It worked! We received a successful response from the Soracom endpoint! Now, let’s go back to the Soracom console and see the harvested data. 

Boom! The web console shows the data from the device!

If we run the code periodically, we can log the weather station data on Soracom Harvest data storage and look at the data at any time. 

If you want, you can now configure the Soracom SIM to forward data to AWS IoT or invoke an AWS Lambda function. You can do all that without changing the device-side logic. 

And as you already know, if you want to make changes on the device side, you can use the Toit cloud to send the updated firmware!

Accelerate your project with Toit x Soracom and have fun!

I hope you see how easy it will be to develop IoT devices with microcontrollers by combining Toit and Soracom services. The integrated solution makes it a lot easier to get started on microcontroller-based IoT device development. If you are interested in giving it a try, please sign up on both services and try it out! If you have any question and/or like to hang out with us, please join #cellular channel on chat.toit.io! Both Toit and Soracom teams, including myself, are on the channel.  We are here to solve all these common challenges in IoT so that you can focus on the fun part of the IoT project! 

More free resources for tech innovators