-
Create and edit Embedded Rust programs visually by dragging and dropping blocks
-
Work In Progress: Generates Embedded Rust code for PineTime Smart Watch with Apache Mynewt realtime operating system
-
Also generates Embedded Rust code for STM32 Blue Pill with Apache Mynewt realtime operating system
Watch the demo...
Read the articles...
-
"Rust Rocks NB-IoT! STM32 Blue Pill with Quectel BC95-G on Apache Mynewt"
-
"Visual Programming with Embedded Rust? Yes we can with Apache Mynewt and Google Blockly!"
-
Usage
-
Build The Firmware
-
Connect The Hardware
-
Flash The Firmware To Blue Pill
-
Run The Program
-
Function 1: On Start
-
Function 2: Start Sensor Listener
-
Function 3: Handle Sensor Data
-
Function 4: Send Sensor Data
-
Rust Source Files
-
Program Settings
-
CoAP: Constrained Application Protocol
-
Quectel NB-IoT AT Commands
-
Configuring the CoAP Server at thethings.io
-
Typeless Rust
-
How Small Is Rust?
-
Why Blue Pill? Power vs Price Compromise
-
Why Apache Mynewt? Evolution of Rust on Bare Metal
-
How Safe Is Rust? Safe Wrappers for Mynewt
-
Inside The Visual Embedded Rust Extension for Visual Studio Code
-
Building The Visual Embedded Rust Extension
-
References
-
Release Notes
-
In Visual Studio Code, Click
File → Open
to open any folder -
In the
Explorer → (Folder Name)
pane at top left, create a new Rust source file, likelib.rs
-
Edit the Rust source file. Click
Visual Editor
at top right -
When prompted to populate the visual program into the Rust source file, click
OK
-
Click the Rust source file to see the generated Rust code. Save the file to save the visual program. Don't edit the Rust source file manually, always use the visual editor.
Sample Rust source file containing generated Rust code and XML blocks
To compile the generated Rust program into Blue Pill firmware...
-
Click here to install
Build Tools For Visual Studio 2019
:
https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019 -
Click the
Individual Components
tabSelect the following components:
Windows 10 SDK (10.0.18362.0)
C++ CMake Tools for Windows
(This should be automatically selected)MSVC v142 — VS 2019 C++ x64/x86 Build Tools
-
Install rustup according to the instructions here:
https://rustup.rsClick the link provided to download
rustup‑init.exe
Launch the downloaded filerustup‑init.exe
If you see the message
Windows Defender SmartScreen prevented an unrecognised app from starting
…
ClickMore Info
ClickRun Anyway
At the
Welcome to Rust!
prompt, press Enter to select the default option:
1) Proceed with installation (default)
-
Open the Windows Command Prompt. Enter into the command prompt:
rustup default nightly rustup update rustup target add thumbv7m-none-eabi rustc -V
The reported version of rustc should be 1.38.0 or later:
rustc 1.38.0-nightly (435236b88 2019–08–01)
-
Download the
stm32bluepill-mynewt-sensor.7z
file attached below…
https://github.com/lupyuen/stm32bluepill-mynewt-sensor/releases/tag/v7.0.3Expand the
.7z
file with 7zip…
https://www.7-zip.org/download.html -
Install Arm Cross-Compiler and Linker for Windows from Arm Developer Website…
https://developer.arm.com/-/media/Files/downloads/gnu-rm/8-2019q3/RC1.1/gcc-arm-none-eabi-8-2019-q3-update-win32-sha1.exe?revision=fcadabed-d946-49dc-8f78-0732d2f43773?product=GNU%20Arm%20Embedded%20Toolchain,32-bit,,Windows,8-2019-q3-updateSelect this option at the last install step:
Add path to environment variable
-
Download the ST-Link USB driver from ST-Link Driver Website (email registration required)…
https://www.st.com/en/development-tools/stsw-link009.htmlClick
Get Software
Unzip the downloaded file. Double-click the driver installer:
dpinst_amd64.exe
-
Launch Visual Studio Code
Install the extension “Cortex-Debug”…
https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug -
Click
File → Open Folder
Select the downloaded folder
stm32bluepill-mynewt-sensor
When prompted to open the workspace, click
Open Workspace
-
Copy your Visual Program source file to
stm32bluepill-mynewt-sensor/rust/app/src/lib.rs
. Overwrite the existing file. -
Delete the files
app_network.rs
andapp_sensor.rs
in that folder -
If you have a Quectel NB-IoT module…
Open the following file and configure the program settings:
targets/bluepill_my_sensor/syscfg.yml
Change the NB-IoT band settingNBIOT_BAND
. Check with your NB-IoT operator for the band to use. -
Click
Terminal → Run Task → [1] Build bluepill_boot
This builds the bootloader, which starts the Apache Mynewt operating system upon startup. If it shows errors, compare with this build log.
-
Click
Terminal → Run Task → [2] Build bluepill_my_sensor
This builds the firmware containing our Rust program. Compare with this build log.
When our Rust program has been successfully compiled as Blue Pill ROM firmware, we should see this…
-
Click
Terminal → Run Task → [3] Image bluepill_my_sensor
This creates the Blue Pill flash image from the firmware. Compare with this image log
If any source files or configuration files are changed, rebuild the application by clicking
Terminal → Run Task → [2] Build bluepill_my_sensor
From top to bottom: STM32 Blue Pill, ST-Link V2, Quectel BC95-G breakout board with antenna, NB-IoT SIM |
We’ll need the following hardware… [1] STM32 Blue Pill: Under $2, search AliExpress for stm32f103c8t6 development board [2] ST-Link V2 USB Adapter: Under $2, search AliExpress for st-link v2 Optional: To transmit data to the NB-IoT network, we’ll also need… [3] Quectel BC95-G Global NB-IoT Module (breakout board with antenna) I ordered mine from Taobao. The manual in Chinese is here. BC95-G works in all NB-IoT frequency bands worldwide. If you’re buying a different NB-IoT module, check that it supports your local NB-IoT Frequency Band. (For example: In Singapore I’m using NB-IoT Frequency Band 8 with StarHub) [4] NB-IoT SIM from your local NB-IoT network operator Many thanks to StarHub for sponsoring the NB-IoT SIM that I used for this tutorial! |
|
Connect Blue Pill to Quectel BC95-G and ST-Link as follows…
Blue Pill | Quectel BC95-G | ST-Link V2 | Wire Colour |
---|---|---|---|
PA2 (UART2 TX2) |
RXD (Pin 4) |
Green | |
PA3 (UART2 RX2) |
TXD (Pin 3) |
Blue | |
GND |
GND (Pin 1) |
Black | |
VCC (Pin 2) |
5.0V (Pin 10) |
Yellow | |
3V3 |
3.3V (Pin 8) |
Red | |
DIO |
SWDIO (Pin 4) |
Orange | |
DCLK |
SWDCLK (Pin 2) |
Brown | |
GND |
GND (Pin 6) |
Black |
Both yellow jumpers on Blue Pill should be set to the 0 position, as shown in the above photo.
SIM partially exposed to show the unusual orientation |
Note that we are powering the Quectel module with 5V from ST-Link instead of 3.3V from Blue Pill. That’s because the module requires more power than Blue Pill can provide. (How did I find out? Because the module kept restarting when I powered it from Blue Pill.) Check the documentation for your Quectel breakout board to confirm that it supports 5V. (Mine does) Insert the NB-IoT SIM according to the orientation shown in the photo. (Yes the SIM notch faces outward, not inward). Remember: Always connect the antenna before powering up the NB-IoT module! If you’re using Windows: Make sure that the ST-Link Driver has been installed before connecting ST-Link to your computer |
|
-
Check that the Blue Pill is connected to ST-Link…
And that the ST-Link is connected to your computer’s USB port.
Now let’s head back to Visual Studio Code… -
Click
Terminal → Run Task → [4] Load bluepill_boot
This flashes the bootloader to Blue Pill, to start the Apache Mynewt operating system upon startup. If it shows errors, compare with this flash log.
-
Click
Terminal → Run Task → [5] Load bluepill_my_sensor
This flashes the firmware (containing our Visual Program) to Blue Pill. If it shows errors, compare with this flash log.
-
Click
Debug → Start Debugging
-
Click
View → Output
Select
Adapter Output
to see the Blue Pill log -
The debugger pauses at the line with
LoopCopyDataInit
Click
Continue
or pressF5
-
The debugger pauses next at the
main()
function.Click
Continue
or pressF5
Our Blue Pill should now poll its internal temperature sensor every 10 seconds. It should also transmit the temperature data to the CoAP server hosted at thethings.io.
The Blue Pill log should look like this. The log is explained below in the "Quectel NB-IoT AT Commands" section.
Upon clicking the URL https://blue-pill-geolocate.appspot.com/?device=5cfca8c…
that’s shown in the Blue Pill log, we’ll see a web page that displays the temperature received by the server at thethings.io.
The server has converted the raw temperature into degrees Celsius. We convert the temperature at the server to conserve RAM and ROM on Blue Pill.
Display of sensor data received from our Blue Pill
On Start
marks the start of the program. Here we define some constants — values used by the program that won’t change as the program runs…
-
SENSOR_DEVICE
is the name of the sensor that the program will poll (check periodically). We’ll be polling Blue Pill’s Internal Temperature Sensor, which is namedtemp_stm32_0
-
SENSOR_POLL_TIME
is the time interval (in milliseconds) for polling the sensor. We’ll set this to 10 seconds (or 10,000 milliseconds) -
TEMP_SENSOR_KEY
is the name of the sensor data field that our program will send to the server. We’ll call itt
to tell the server we’re sending a temperature value. -
TEMP_SENSOR_TYPE
is the type of sensor data that our program will send: Raw ambient temperature in whole numbers (integers from 0 to 4095), henceSENSOR_TYPE_AMBIENT_TEMPERATURE_RAW
Why do we send the temperature in raw form instead of the usual decimal (floating-point) form like 28.9 degrees Celsius? That’s because Blue Pill has very limited RAM and ROM. Sending the raw temperature without conversion will save us from reserving RAM and ROM that would be needed for the floating-point conversion. We’ll let the server convert instead.
By Rust convention, constants are named in uppercase. Hence we name the constants as SENSOR_DEVICE
instead of sensor_device
Next we call the function start_sensor_listener
to begin polling the temperature sensor every 10 seconds. More about this in the next section.
Finally we call start_server_transport
, which is a system function defined in the sensor_network
library. This function starts a background task to establish a connection to the NB-IoT network. For this tutorial, we’ll be transmitting sensor data over the NB-IoT network, which is available worldwide.
It may take a few seconds to complete, but the function executes in the background so it won’t hold up other tasks, like polling the temperature sensor.
Take note of the Rust convention… sensor_network::start_server_transport
refers to the function start_server_transport
that’s found inside the Rust Library sensor_network
. Rust Libraries are also known as “Crates”.
How was the On Start
function created?
By dragging and dropping the blocks from the Blocks Bar at the left of the Visual Program.
That’s how we create a Visual Program… By arranging the blocks to compose a program!
To start_sensor_listener With ...
is the way that we define functions in the Visual Program. Here we define start_sensor_listener
as a function that accepts 4 parameters (or inputs), whose values we have seen from the previous section…
-
sensor_name
: Name of the sensor to be polled. Set toSENSOR_DEVICE
(i.e.temp_stm32_0
) -
sensor_key
: Name of the sensor data field to be sent to the server. Set toTEMP_SENSOR_KEY
(i.e.t
) -
sensor_type
: Type of sensor data that will be sent to the server. Set toSENSOR_TYPE_AMBIENT_TEMPERATURE_RAW
-
poll_time
: Time interval (in milliseconds) for polling the sensor. Set toSENSOR_POLL_TIME
(i.e. 10,000 milliseconds or 10 seconds)
Next we call the system function set_poll_rate_ms
, defined in the sensor
library. The sensor
library comes from the Apache Mynewt operating system, which manages all sensors on Blue Pill.
By calling the function set_poll_rate_ms
with sensor_name
set to temp_stm32_0
and poll_time
set to 10000
(milliseconds), we are asking the system to poll the temperature sensor every 10 seconds. And the system will happily fetch the temperature value on our behalf every 10 seconds.
What shall we do with the temperature value? We’ll define a Listener Function to transmit the data. But first…
We call function mgr_find_next_bydevname
(also from the sensor
library) to fetch the sensor driver from the system and store it in the variable sensor_driver
. By passing the sensor_name
as temp_stm32_0
, the function returns the driver responsible for managing the temperature sensor. The driver will be used for setting the Listener Function in a while.
Before that, we check the sensor driver was actually found. If we had misspelt the name of the sensor, the sensor driver would not be found and it would be set to null
, a special Rust value that means “nothing”. Hence we check to ensure that sensor_driver
is not null
.
We create a sensor listener (stored as listener
) by calling the system function new_sensor_listener
, passing in the sensor_key
(set to t
) and the sensor_type
(raw ambient temperature). func is the name of the Listener Function that will be called after reading the sensor data: handle_sensor_data
. Which we’ll cover in the next section.
To register the Listener Function in the system, we call the system function register_listener
, passing in the sensor_driver
and the sensor listener that we have just created.
After that, the operating system will automatically read the temperature sensor every 10 seconds and call our function handle_sensor_data
with the temperature value.
How shall we handle the temperature data that has been read? handle_sensor_data
passes the sensor data to another function send_sensor_data
that transmits the sensor data to the server. More about send_sensor_data
in a while.
The function handle_sensor_data
doesn’t seem to do much… why did we design the program this way? It’s meant for future expansion — when we need more complicated logic for handling sensor data, we’ll put the logic into handle_sensor_data
handle_sensor_data
could be extended to handle multiple sensors, aggregating the sensor data before transmitting. Or it could check for certain conditions and decide whether it should transmit the data. This program structure gives us the most room to expand for the future.
The final function in our program, send_sensor_data
, is called by handle_sensor_data
to transmit sensor data. The parameter sensor_data
contains the field name t
and the sensor value, like 1715
. Remember that this is a raw temperature value. The server will convert the raw value to degrees Celsius later.
We call get_device_id
from the sensor_network
library to fetch the Device ID from the system. This is a long string of random letters and digits like a8b2c7d8e9b2...
Each time we restart Blue Pill we’ll get a different Device ID. We’ll use this Device ID later to identify our Blue Pill uniquely and check whether the server has received the temperature sensor data from our Blue Pill.
Next we call init_server_post
(also from sensor_network
library) to prepare a sensor data message that will be sent to the server. Because Blue Pill has limited RAM, this function will ensure that only one task is allowed to compose messages at any time. The other tasks will have to wait for their turn.
init_server_post
returns a true/false result (known as a boolean) that indicates whether the NB-IoT network connection has been established. This stored in the variable network_ready
.
Only when network_ready
is true, which means that the device has connected to the NB-IoT network, then we proceed to compose a CoAP Message.
What’s a CoAP Message? It’s a standard format for transmitting sensor data over NB-IoT. Here we are transmitting two data values in the CoAP Message...
-
device_id
: The randomly-generated Device ID that uniquely identifies our Blue Pill. This field shall be transmitted with the field name device -
sensor_data
: Contains the field namet
and the sensor value, like1715
The CoAP Message is transmitted only when function do_server_post
is called. Again this transmission takes place in a background task, so it won’t hold up our program from polling the sensor.
Notice that _payload
is named differently… it begins with an underscore _
. By Rust convention, variables that are set but not read should be named with an underscore _
as the first character. Because the Rust Compiler will warn us about unused variables.
This effectively tells the Rust Compiler: “Yes I’m setting the variable _payload
and I’m not using the value… Please don’t warn me that I may have misspelt the name _payload
"
At the end of the function, we display a URL in the Blue Pill log that contains the Device ID. The URL looks like this: https://blue-pill-geolocate.appspot.com/?device=5cfca8c… We’ll click this URL to verify that the server has received our sensor data.
|
The program settings may be found in the file targets/bluepill_my_sensor/syscfg.yml
COAP_HOST, COAP_PORT
: The program will send CoAP messages to this IP address and port number, which defaults to the CoAP server at thethings.io.
Keep the default settings if you wish to view your sensor data at blue-pill-geolocate.appspot.com.
Change the setting to use your own CoAP server instead of thethings.io
COAP_URI
: The CoAP message will be delivered to this URI at the CoAP server (which defaults to coap.thethings.io
).
Keep the default settings if you wish to view your sensor data at blue-pill-geolocate.appspot.com.
For thethings.io, the last part IVRi…
is the Thing Token. If you wish to send sensor data to your own Thing at thethings.io, replace the last part of the URI with your Thing Token.
For the purpose of NB-IoT Education, I’ll allow you to transmit sensor data to the Thing Token IVRi… from my personal, paid thethings.io account. Which will forward the sensor data to blue-pill-geolocate.appspot.com
for viewing.
NBIOT_BAND
: The program connects to this NB-IoT Frequency Band. The Frequency Band depends on your country and your NB-IoT network operator. Check with your NB-IoT network operator for the Frequency Band to use.
Note: The example below assumes that our device is transmiiting the temperature as a floaing-point value like tmp: 28.9
. However our Visual Embedded Rust program transmits the temperature as a raw integer like t: 1879
. Please do the necessary mental adjustments
How do we achieve “low cost, long battery life, and high connection density” with NB-IoT? Next time you attend a party, try this…
Mingle around as many people as you can. Listen to what EVERYONE has to say.
When a conversation gets boring, just move away without saying anything.
Don’t feel obligated to continue any conversation. It’s your right to listen as much or as little as you want. You’re NOT committed to stay CONNECTED to any person at the party.
Yes your friends will find you strangely anti-social. You will miss out on some great stories from your friends, but then again, they are probably repeating the same old stories.
BUT you will learn lots more, from more people. And feel less drained.
That’s the better way… the CONNECTIONLESS way!
We have been using TCP (Transmission Control Protocol) since the beginning of the internet to connect our gadgets (HTTP and MQTT are two popular protocols based on TCP). However TCP is Connection-Oriented — when two devices are connected via TCP, they need to stay connected… or they will be penalised.
If any packets are dropped (due to poor network coverage or congestion) or delayed, both devices will need to resynchronise their TCP windows by retransmitting their packets. Which may lead to severe problems like the MQTT Server congestion on Moon Base One!
In a Connectionless Network there’s no commitment to stay connected to any device: Just transmit or receive a message. And move on. (Like our Connectionless Party!)
Instead of establishing a TCP connection, we transmit a UDP (User Datagram Protocol) packet without waiting for the acknowledgement.
But because they are Connectionless, UDP packets do not enjoy guaranteed delivery.
Most UDP packets are delivered properly, but if any packets are dropped (due to poor network coverage or congestion), UDP devices don’t attempt to resynchronise and retransmit the lost packets.
Isn’t it a serious problem when packets disappear in our IoT network? Well, do we really need to receive every single sensor reading? Instead of suffering a server failure or network congestion, could we compromise by dropping a couple of sensor messages? That’s how we achieve High Connection Density in NB-IoT!
When we go Connectionless, our gadgets become a lot simpler to build… no messy sliding window protocols and waiting forever! Thus our NB-IoT gadgets are Low Cost, and enjoy Long Battery Life!
💎 Many other things are switching to the simpler Connectionless way… 1️⃣ HTTP version 3 will switch from TCP to a UDP protocol named QUIC. Because it just works better on lossy mobile networks. 2️⃣ YouTube and many video streaming services are already running on RTSP based on UDP. It allows video quality to be negotiated in real time based on network conditions. 3️⃣ Most massively multiplayer games already use UDP to achieve lower latency.
In the Connection-Oriented Universe, we have the MQTT protocol for transmitting TCP sensor messages to the IoT Server. What’s the equivalent for the Connectionless Universe that will allow us to transmit UDP sensor messages?
Answer: Constrained Application Protocol, or CoAP
Why “Constrained”? Because CoAP was designed for low-power microcontrollers that don’t have easy access to power (like the crop sensors on Moon Base One). And CoAP requires little bandwidth… 120 bytes is all we need to send a sensor message to a CoAP server (like thethings.io)… Perfect for NB-IoT!
Google Sheet for encoding CoAP messages
I have prepared a Google Sheet that shows how a CoAP message is encoded for sending sensor data.
Click the sheet and make your own copy.
Let’s look at the three parts of a CoAP message…
0️⃣ CoAP Preamble
1️⃣ CoAP Options
2️⃣ CoAP Payload
The Preamble appears at the start of every CoAP message. The important parts are…
NON
is the recommended Message Type. NON
messages don’t require any acknowledgement from the CoAP Server. So it’s highly efficient for transmitting sensor data and keeps the device firmware simple. If acknowledgement is desired (think very carefully!), select CON
as the Message Type.
POST
will transmit sensor data to thethings.io. GET
will fetch the last transmitted sensor value. Yes, CoAP follows the same conventions as HTTP and REST.
After the Preamble, the Options section appears next. The Options will remind you of HTTP Headers…
The last gibberish part (IVRi…) is the Thing Token in thethings.io. More about that later.
Note that Content Format and Accept fields each require only 1 byte 32 to specify the value application/json
. Constrained and highly-efficient indeed!
Finally we have the Payload, which contains the sensor data. Here we use a JSON document to encode the sensor values device=4BUXIW6W, tmp=28.1
…
thethings.io requires the device to transmit sensor values in the above format: an array of values, with key and value in each entry.
thethings.io will look up the Thing that we have specified in the URI Options (IVRi…
) and set the temperature tmp
to 28.1
.
What’s device
? This the random Device ID that uniquely identifies our Blue Pill, so that we may view the sensor data received by the server.
How do we know where the Options end and where the Payload starts? Easy — just look for the End Of Options Marker FF
. The CoAP format is so simple that it doesn’t need any fields to indicate the sizes of the Options and the Payload!
In under 150 bytes we have created a UDP message that our device may transmit over NB-IoT to update the sensor data for our Thing at thethings.io. We’ll learn next how to send this packet with a simple AT command.
For more details on CoAP, check the CoAP specifications (RFC7252)
We'll look at this Blue Pill log to understand the Quectel NB-IoT AT Commands sent by our Blue Pill to the Quectel BC95-G NB-IoT module...
Blue Pill connects to the Quectel module via the UART port at 9600 bps, 8 data bits, No parity bit, 1 stop bit.
For every AT command sent, Blue Pill sends CR
(Ctrl-M
or 0x0d
) and LF
(Ctrl-J
or 0x0a
) at the end of each AT command.
First we reboot the Quectel module to start from a fresh, clean state…
AT Commands (in bold) | Remarks |
---|---|
Boot: Unsigned Security B.. Verified Protocol A.. Verified Apps A...... Verified REBOOT_CAUSE_SECURITY_RESET_PIN Neul OK |
When connected to our computer, the Quectel module shows REBOOT_CAUSE_SECURITY_RESET_PIN . This is normal. Ignore the ERROR message that may appear at the beginning. The Blue Pill program attempts a few retries until it gets the OK response. |
AT+NCONFIG=AUTOCONNECT,FALSE |
Disable auto-connecting to the NB-IoT network upon restarting |
OK |
This enables us to specify which NB-IoT Band to search, which should be faster |
AT+NRB |
Reboot the module |
REBOOTING -f-f�-f -f� Boot:Unsigned SecurityB..Verified ProtocolA..Verified AppsA......Verified REBOOT_CAUSE_APPLICATION_AT Neul OK |
Module reboots |
After rebooting, we specify the network settings and attach to the NB-IoT network…
AT Commands (in bold) | Remarks |
---|---|
AT+NBAND=8 |
Select NB-IoT Band 8 |
OK |
This is specific to your NB-IoT operator |
AT+CFUN=1 |
Enable full functionality |
OK |
Now we may start attaching to the network |
AT+CGATT=1 |
Attach to the NB-IoT network |
OK |
Attach operation begins |
AT+CEREG? |
Are we registered to the NB-IoT network? |
=+CEREG:0,1 OK |
0,1 means we have been registered 0,2 means we should wait a few seconds then recheck via AT+CEREG? |
AT+CGATT? |
Are we attached to the NB-IoT network? |
=+CGATT:1 OK |
1 means we are attached to the NB-IoT network. Ready to transmit. |
We are now ready to transmit. For the specific AT command for transmitting our message, look in the CoAP Message Encoder Google Sheet.
AT Commands (in bold) | Remarks |
---|---|
AT+QREGSWT=2 |
Don't use Huawei IoT Server. Note: This command has been moved to the above section |
OK |
|
AT+NSOCR=DGRAM,17,0,1 |
Allocate a local network port. DGRAM,17 means UDP, 0 means allocate a new port, 1 means allow receiving of messages from server |
1 OK |
1 is the local port allocated |
AT+NSOST=1,104.199.85.211,5683,147, (data omitted) ,100 |
Transmit the CoAP UDP packet using local port 1 to server IP address 104.199.85.211 , server port 5683 with size 147 bytes and msg sequence 100 . Use the AT command from the Google Sheet |
1,147 OK |
1 is the local port |
147 is the number of bytes being transmitted |
|
=+NSOSTR:1,100,1 |
1,100,1 means port 1 , msg sequence 100 was transmitted (1 ) |
The CoAP Server at thethings.io returns a response to our message…
AT Commands (in bold) | Remarks |
---|---|
=+NSONMI:1,35 |
1,35 means port 1 has received a server response of 35 bytes |
AT+NSORF=1,35 |
Read the server response for port 1 , returning 35 bytes |
1,104.199.85.211,5683,35,58410001 0000164A272AE239C132FF7B227374617 47573223A2263726561746564227D,0 OK |
Server response for port 1 , server IP address 104.199.85.211 , server port 5683 |
AT+NSOCL=1 |
Close port 1 |
OK |
We may use Wireshark to decode the above server response (which is another CoAP message)…
58 41 00 01 00 00 16 4A 27 2A E2 39 C1
32 FF 7B 22 73 74 61 74 75 73 22 3A 22
63 72 65 61 74 65 64 22 7D
See the section “Advanced Topic: What’s Inside The CoAP Message?” in “Connect STM32 Blue Pill to ESP8266 with Apache Mynewt”
Insert a space between each byte before decoding with Wireshark. The decoded response from thethings.io should read…
{"status":"created"}
Which means that the Thing State has been successfully updated in thethings.io.
Here are some AT commands useful for troubleshooting…
AT Commands (in bold) | Remarks |
---|---|
AT+CGPADDR |
Display the IP address allocated by the network |
=+CGPADDR:0,10.26.255.38 OK |
IP address of the module |
AT+NUESTATS |
Display the network statistics |
Signal power:-758 Total power:-706 TX power:230 TX time:438 RX time:16193 Cell ID:3535136 ECL:0 SNR:132 EARFCN:3518 PCI:22 RSRQ:-108 OPERATOR MODE:4 OK |
Network statistics of the module |
Read more about Quectel NB-IoT AT Commands.
Upon clicking the URL https://blue-pill-geolocate.appspot.com/?device=5cfca8c…
that’s shown in the Blue Pill log, we’ll see a web page that displays the temperature received by the server at thethings.io.
The server has converted the raw temperature into degrees Celsius. We convert the temperature at the server to conserve RAM and ROM on Blue Pill.
The temperature data appears in a web page like this (it refreshes every 10 seconds)…
Display of sensor data received from our Blue Pill
How did the sensor data get posted on a public website that’s outside thethings.io?
That’s my demo server hosted at Google Cloud running AppEngine Go. Here's what just happened…
-
Recall that the Program Settings in targets/bluepill_my_sensor/syscfg.yml includes a
COAP_URI
setting -
The
COAP_URI
setting includes a Thing Token for thethings.io:IVRiBC...
-
This Thing Token refers to a specific Thing in my thethings.io account. What sensor data are we sending to the Thing?
-
We are sending the temperature data plus a
device
field.device
is a random Device ID generated by our program. When my CoAP Server at thethings.io receives thedevice
andt
fields, it pushes the two values to my Google Cloud server, along with the computed floating-point temperature. (Yes it’s possible with thethings.io Cloud Code!) -
Then when you click the URL in the Blue Pill log...
https://blue-pill-geolocate.appspot.com/?device=5cfca8c…
The Device ID appears inside the URL, so my Google Cloud server renders the computed temperature value associated with your Blue Pill. That’s the power of end-to-end IoT Integration with Quectel modules, NB-IoT, thethings.io and Google Cloud!
For the purpose of NB-IoT Education I’ll allow you to send CoAP messages to my (personal, paid, non-sponsored) account at thethings.io… because there’s no better way to learn CoAP!
💎 If you wish to create your own free account for thethings.io, check the section “Configuring the CoAP Server at thethings.io” in this article. Copy your Thing Token to the COAP_URI
setting.
Open the Developer’s Console in thethings.io. Updates to the Thing State triggered by CoAP messages will be shown here. You will not longer be able to view the sensor data on my Google Cloud server, but you may follow the instructions in the above article to view your sensor data in thethings.io dashboards.
The integration code for thethings.io and Google Cloud is available at...
github.com/lupyuen/thethingsio-wifi-geolocation
github.com/lupyuen/gcloud-wifi-geolocation
thethings.io Dashboard with Raw Temperature t
and Computed Temperature tmp
To making coding easier for beginners, the extension generates Typeless Rust code like this...
#[infer_type] // Infer the missing types
fn start_sensor_listener(sensor_name: _, sensor_key: _, sensor_type: _, poll_time: _) ...
// Call Mynewt API
sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;
When the typeless code is compiled, the infer_type
Procedural Macro infers the types by matching the variables against the Mynewt API...
// Call Mynewt API
sensor::set_poll_rate_ms(sensor_name, poll_time) ? ;
// `sensor_name` inferred as type `&Strn`
// `poll_time` inferred as type `u32`
The macro then injects the inferred types into the typeless code...
fn start_sensor_listener(sensor_name: &Strn, sensor_key: &'static Strn,
sensor_type: sensor_type_t, poll_time: u32) ...
The inferred types are stored in infer.json
. The enables the infer_type
macro to infer new types based on types already inferred for other functions...
"start_sensor_listener": [
[ "sensor_name", "&Strn" ],
[ "sensor_key", "&'static Strn" ],
[ "sensor_type", "sensor_type_t" ],
[ "poll_time", "u32" ]
],
"send_sensor_data": [
[ "sensor_data", "&SensorValue" ]
],
"handle_sensor_data": [
[ "sensor_data", "&SensorValue" ]
]
This diagram illustrates the Type Inference…
How the infer_type macro infers missing types
Here’s an animation (done with Visual Studio Code) that explains how the types were inferred by the infer_type
macro. At top left are the types to be inferred. At bottom left are the known type signatures from the Mynewt API.
The infer_type
macro scans the Typeless Rust program recursively, hence we see the roving red highlight. When the macro finds a match with the Mynewt API, the code flashes green.
Green ticks at the top left mean that we have successfully inferred the types.
The recursive Rust code parsing was implemented with the excellent syn
crate. The quote
crate was used to emit the transformed Rust code.
How the infer_type macro infers missing types, animated in Visual Studio Code with the Visual Embedded Rust Extension
More details in the article "Advanced Topics for Visual Embedded Rust Programming"
Here’s the RAM and ROM usage of the Visual Embedded Rust firmware on Blue Pill. The left section shows Rust and Apache Mynewt functions sorted by size (largest on top). The right section shows the total RAM and ROM used by each library.
Our firmware occupies 55 KB of ROM and 13 KB of RAM, out of Blue Pill’s 64 KB ROM and 20 KB RAM. (That’s 86% of ROM and 65% of RAM used)
The spreadsheet below was generated based on the Linker Memory Map created during the firmware build. Read more about memory maps
RAM and ROM usage of the Visual Embedded Rust firmware
STMicroelectronics STM32F103C8T6 microcontroller on Blue Pill |
Blue Pill was chosen for the tutorial because it best represents a real-world, low-cost microcontroller with limited RAM and ROM. The microcontroller is found in many off-the-shelf products, even flying drones! To be clear what’s a “Blue Pill”… The heart of Blue Pill is an STMicroelectronics STM32F103C8T6 microcontroller. That’s a tiny module surface-mounted on a Blue printed-circuit board (hence the name Blue Pill). Without the Blue (and Yellow) parts, it would be extremely difficult for us to experiment with the microcontroller. So we buy a Blue Pill and use it like an Arduino. |
Thus Blue Pill is clearly a Developer Kit that marks up the cost of the microcontroller. Blue Pill retails for $2, but the STM32F103C8T6 microcontroller sells for only 40 cents! Perfect for creating millions and millions of IoT sensors! (Actually a $2 dev kit is so affordable that it begs you to go ahead and do many many crazy things with it!) At this price we get a 32-bit Arm processor with many goodies (GPIO, UART, I2C, SPI, USB, …) But the catch: It has only 64 KB of ROM and 20 KB of RAM. (Similar to the Apple II!) With Blue Pill’s extremely limited RAM and ROM, we can’t code in decent programming languages like MicroPython and JavaScript (for MakeCode, which I tried and failed). C was the only option… Until Rust came along! Rust is a systems programming language that’s as low level as C (i.e. no garbage collection). Yet it solves the painful pointer problem in C. |
This research paper presents an excellent comparison of C with Rust
Read more about Blue Pill and the upgraded version, Super Blue Pill
Why are we running the Apache Mynewt realtime operating system on Blue Pill together with Rust? Mynewt was built with C (and therefore susceptible to C defects)… Why not run Rust on Blue Pill without C?
This means we would be running Rust on “Bare Metal”, without any other operating system. In fact, Rust becomes the operating system. Rust would have to handle GPIO, UART, I2C, SPI, USB, … Plus multitasking and related functions like semaphores and messages queues.
Why reinvent the Rusty wheel when we already have realtime operating systems like Mynewt, FreeRTOS, Zephyr, …? It takes a lot of effort to build a new realtime operating system in Rust that supports all kinds of microcontrollers.
There’s a great team hard at work creating the Embedded Rust platform. In the meantime, I’m taking a shortcut and using Mynewt… But in a safe way, with Safe Wrappers.
I’m not solving the problem of devices crashing due to bugs in the operating system… Because I trust experienced C coders in the Mynewt community for creating high-quality kernel code. Instead I’m solving the problem of devices crashing due to bugs in the C application code… And experienced C application coders are really hard to find.
Can we solve this problem with Rust?
Rust won’t let us use pointers freely like in C (unless we explicitly write unsafe
code). Mynewt however is written in C and it will blindly accept any pointer we pass, even bad ones. How shall we reconcile the two?
The solution: Create Safe Wrappers that validate all pointers passed from Rust to Mynewt and vice versa. A Safe Wrapper is a Rust function that wraps around the Mynewt API, converting the unsafe Mynewt API into a safe Rust API.
For example: When a Mynewt API accepts a string input (const char *
), we wrap the API with a Rust function that accepts a Strn
type instead.
Strn
is a type I created that represents a null-terminated string. Unlike C, Rust strings are not expected to be terminated by the null byte. So when we pass strings to the Safe Wrapper as Strn
, the wrapper verifies that the string is indeed terminated by a null.
When Mynewt returns a string, the Safe Wrapper also uses the Strn
type to verify that the returned string is properly terminated by null.
Read more about Mynewt Safe Wrappers in the section “Advanced Topic: Safe Wrappers for Mynewt”
Read this excellent paper that compares Unsafe Coding in C with Safe Coding in Rust
In C we would manipulate a string as char *
. In Rust it weirdly becomes &'static Strn
… a reference to a static null-terminated string. (Strn
is my own helper type for embedded platforms.) Yes Rust is 100% precise in describing types… perhaps too precise?
Many Rust learners struggle with Rust Types (I’m still struggling). The Rust Compiler helpfully suggests how to fix coding issues with types. If the Rust Compiler is really so smart, why not go all the way and fix the types for us?
Thus Visual Embedded Rust becomes an experiment in “Typeless Rust”. Visual Coding doesn’t give us much room to express precise types in our Visual Programs. (How would you visually represent a reference to a static null-terminated string?) And if Visual Rust is meant for beginners, maybe we shouldn’t even mention types, to flatten the Rust learning curve.
Rust Learners shall stroll up the Rust learning slope starting with…
-
Visual Rust, devoid of types, followed by…
-
Typeless Rust, the typeless code generated by Visual Rust, and finally…
-
Fully-Typed Rust, a.k.a. Rust 2018
The source code for the Visual Embedded Rust extension is located at github.com/lupyuen/visual-embedded-rust
The extension is published in the Visual Studio Marketplace here
The extension wraps the web-based visual code editor from Google Blockly into a VSCode WebView. Blockly uses XML to represent a visual program.
The extension is activated when we edit a Rust source file (*.rs
). Here’s a sample Rust source file containing a Visual Program
There are two parts of the file…
-
Rust Source Code: Which is autogenerated by the Blockly Code Generator from the Blockly XML
-
Blockly XML: The XML representation of the visual program. It’s located at the bottom of the source file, marked by
BEGIN BLOCKS … END BLOCKS
Logic Flow in the Visual Embedded Rust Extension
-
Main logic for the VSCode Extension is in extension.ts
The extension contains two asset folders:
resources
: Contains a visual program template that will be used to populate empty Rust source filesmedia
: Contains the Blockly JavaScript code that will be embedded in the WebView to render the visual editor and generate Rust source code…media/blockly-mynewt-rust
contains the Blockly JavaScript code with a custom Rust Code Generatormedia/closure-library
is the Google Closure Library needed by Blocklymedia/vscode
contains JavaScript code that enables VSCode Message Passing in the WebView to implement save/load functions and modal prompts -
The extension creates a WebView that embeds the HTML and JavaScript code from Google Blockly.
-
The VSCode Extension and the WebView are running in separate JavaScript sandboxes.
Hence we’ll be using VSCode Message Passing to communicate between the VSCode Extension and WebView, as we shall soon see…
-
When the WebView loads, it notifies the VSCode Extension to fetch the contents of the Rust source file.
The VSCode Extension responds by passing the contents of the active Rust source file to the WebView via Message Passing.
The WebView extracts the Blockly XML embedded in the file contents (at the bottom). The WebView refreshes the Blockly workspace with the Blockly XML.
If the active Rust source file is empty, the VSCode Extension populates the file with a template containing Blockly XML
-
When the visual program is updated, the WebView sends the updated Blockly XML and the generated Rust code (via Message Passing) to the VSCode Extension.
The extension updates the Rust document in VSCode with the Blockly XML and generated Rust Code.
-
The custom-built Rust Code Generator for Blockly is here…
github.com/lupyuen/blockly-mynewt-rust/blob/master/generators/rust.js
github.com/lupyuen/blockly-mynewt-rust/tree/master/generators/rust
The Rust Code Generator for Blockly is explained in this article
To build the extension, two repositories need to be cloned into the media folder: blockly-mynewt-rust
and closure-library
:
cd media
git clone https://github.com/lupyuen/blockly-mynewt-rust
git clone https://github.com/google/closure-library
The following files may be useful for reference…
Read more about hosting Rust applications on Mynewt
For changelog refer to...