VCON is a framework to connect microcontrollers online. It provides secure IoT connectivity, remote management, and OTA update. VCON consists of two parts:
VCON module is, in turn:
VCON module is a "smart" connectivity module of a new type. Unlike traditional modules, it knows how to reprogram a Host MCU (a microcontroller that is attached to a VCON module). It implements an ARM SWD protocol - so essentially, it is a remote programmer, which allows to connect to any of your live remote devices with a debugger. So VCON can debug and re-program your device at any time, giving you an unprecedented control over your fleet.
Also, VCON module acts as a network bridge - either transparent, or non-transparent. In transparent mode, VCON forwards UART data to the server of your choice. In this case, a Host MCU (a microcontroller used in your product) is not even aware it is connected to the Internet - it thinks it is controlled over the UART locally. This mode allows to retrofit existing non-connected devices to the Internet with minimum efforts.
Step 1. Get VCON-D51 or VCON-328 board and plug it to your workstation's USB
Step 2. Go to https://dash.vcon.io and click on "Add device" button
Step 3. Open https://vcon.io/app/ in any Bluetooth-enabled browser, like Google Chrome or Microsoft Edge. Click on "Choose device" button:
Step 4. Select a device and click "Pair":
Step 5. Go to https://dash.vcon.io and copy device password to the clipboard by by selecting "action" -> "Copy device password to clipboard:
Step 6. Fill in WiFi network name, WiFi password, device password and save settings:
Your device on a dashboard should become online:
Step 7. Compile Arduino firmware (but don't flash it!). Start Arduino IDE, choose "File" -> "Examples" -> "Basics" -> "Blink" sketch. Choose the correct board. For VCON-328, choose "Tools" -> "Board" -> "Arduino AVR Board" -> "Arduino Uno". For VCON-D51, choose "Tools" -> "Board" -> "Adafruit SAMD" -> "Itsy Bitsy M4". Select "Sketch" -> "Export compiled binary". This will produce a compiled .hex or .bin file in the sketch directory.
Step 8. Remote OTA! Go to https://dash.vcon.io, choose "action" -> "Update firmware". Click on a "choose .bin or hex file" button, navigate to your sketch directory, select compiled firmware file. Your device should start blinking.
Congratulations! Your device is now remotely controllable and upgradeable.
The best way to describe this board is "A classic Arduino Uno with built-in OTA and connectivity". It features the venerable Atmega328P micro found on classic Arduinos, like Uno, Nano and Pro Mini. This board is ideal for prototypes, or as a replacement for a classic Arduino when a remote control / remote firmware update is required.
Top and bottom views:
Below is the pinout. Pin names are the same as for the Arduino Uno:
Feature | Value |
---|---|
Dimensions | 21x41mm |
Operating voltage | 3.3V |
Host MCU | ATMEGA328P |
Clock speed | 16 MHz |
RAM | 2 Kb |
Flash | 32 Kb |
Arduino IDE ready | yes |
Connectivity chip | ESP32-PICO-V3 |
WiFi connectivity | Built-in |
Ethernet connectivity | Via an external shield |
Cellular (2G,3G,4G) | Via an external shield |
This board features a powerful SAMD51 microcontroller. You can flash any bootloader on it (remotely!), which makes VCON-D51 to appear as a different board. By default, an Adafruit ItsyBitsy M4's bootloader is flashed, therefore VCON-D51 is recognised by Arduino IDE as Adafruit ItsyBitsy M4.
Ideal for production - environmental sensing, equipment monitoring, where remote control and remote firmware update is required.
Top and bottom views:
Below is the pinout. Pin names are per SAMD51 datasheet:
Feature | Value |
---|---|
Dimensions | 21x41mm |
Operating voltage | 3.3V |
Host MCU | ATSAMD51 |
Clock speed | 120 MHz |
RAM | 192 Kb |
Flash | 512 Kb |
On-board QSPI flash | 4 Mb |
Arduino IDE ready | yes |
Connectivity chip | ESP32-PICO-V3 |
WiFi connectivity | Built-in |
Ethernet connectivity | Via an external shield |
Cellular (2G,3G,4G) | Via an external shield |
The commumication chip can be configured to read the serial output of the Host MCU and forward all data to the MQTT server of your choice. That is called a "UART-MQTT bridge mode". In order to enable that,
Step 1. Login to https://dash.vcon.io, make sure your device is online, choose "action" → "Manage device"
Step 2. In the file editor section, choose config.json
file,
and edit the configuration JSON object by adding the following section:
...
"mqtt": {
"rx": "vcon/rx",
"tx": "vcon/tx",
"url": "mqtt://broker.hivemq.com:1883"
},
...
Step 3. Click on "save" button, then click on "reboot device" button
That's it! Now all data that is printed to the serial, gets forwarded to the
topic vcon/tx
of the MQTT server broker.hivemq.com:1883. Feel free to choose
a different topic or server address.
Note there is an rx
topic, too. You've guessed it: everything that is sent
to that MQTT topic, a communication chip catches and sends to the Host MCU
serial line, so MQTT/Serial communication is bi-directional.
NOTE: on Arduino framework, Serial
on VCON-328 and Serial1
on VCON-D51.
What if you want to send data to something that is not MQTT - for example, to a custom server, or to a database like Influx? That is also possible. When a communication chip reads Host MCU serial, it generates a notification on the management cloud. It is possible to intercept that notification, and have a custom code to forward data to any destination.
Read the Notifications section for exact details.
An easy remote control via MQTT could be implemented by the "Send data to MQTT" recipe. Configure your RX and TX topics, MQTT server, then send data to the TX topic.
Use a simple Arduino sketch to read Serial to turn an LED on or off:
// IMPORTANT! On VCON-D51, use Serial1 instead of Serial
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (Serial.available() > 0) {
int character = Serial.read();
if (character == '0') digitalWrite(LED_BUILTIN, LOW); // '0' switches LED off
if (character == '1') digitalWrite(LED_BUILTIN, HIGH); // '1' switches LED on
}
}
Of course, the serial message format in this simple example is trivial. It is done for demonstration purposes. You could implement any format you wish, test it on a local serial monitor, then it is going to work the same way when you send commands over MQTT.
Keep it simple, though.
When a communication chip is in Serial/MQTT bridge mode, a Host MCU is not aware it is connected to the Internet. It "thinks" that it is controlled via Serial, but in fact, serial data comes from the internets. A communication chip is, so to say, transparent, invisible to the Host MCU.
It is possible to put communication chip into an RPC mode. In this mode, a communication chip expects data to be in a JSON-RPC format: each chunk of data must be a JSON-RPC frame, separated by a newline character. This way, a communication chip is not transparent to the Host MCU: Host MCU knows it is there, it could send JSON-RPC requests to it and receive responses.
In RPC mode, a Host MCU can register any number of custom handler functions,
like GetStatus
, SwitchOnMotor
, or whatever is required. A communication
chip can bridge these to the management cloud, and any function could be
called via a familiar REST API.
Read RPC section below for a detailed explanation and an example sketch.
This recipe demonstrates how to build a complete custom monitoring dashboard using VCON platform. The implementation is very compact, well commented, and is designed to be a reference implementation of the production system. It consists of the following parts:
The overall architecture of such setup is as follows:
Step 1. Follow Quick Start Guide to register a VCON device on a dash.vcon.io management dashboard
Step 2. Clone or download the https://github.com/cesanta/vcon-app-example repository to your workstation
Step 3. Flash the firmware/firmware.ino
sketch to your VCON device
using remote OTA.
This sketch implements a simple LED control and simulates periodic sensor data upload:
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
// Handle serial input: switch LED on/off
if (Serial.available() > 0) {
int ch = Serial.read();
if (ch == '0') digitalWrite(LED_BUILTIN, LOW); // '0' switches LED off
if (ch == '1') digitalWrite(LED_BUILTIN, HIGH); // '1' switches LED on
}
// Print current status to serial every 5 seconds
// A status message is a JSON string like this: {"led": 0, "sensor": 27}
static unsigned long prev;
unsigned long curr = millis();
if (curr - prev > 5000) {
char buf[100];
snprintf(buf, sizeof(buf), "{\"led\": %d, \"sensor\": %d}",
(int) digitalRead(LED_BUILTIN), (int) random(20, 30));
Serial.println(buf);
prev = curr;
}
}
Step 4. Install Node.js, or skip this step if you have it installed
Step 5. Install ws
node package for Websocket support:
$ npm i -g ws
Step 6. Edit backend/config.json
file, change vcon_api_key
value
and save the file. You can get you API key by logging to https://dash.vcon.io
dashboard on the Account page.
Step 4. Run your backend
$ node backend/main.js
That's it! Now point your browser to https://localhost:8000 and login as test/test. Here are the links for the core pieces of functionality:
Here are some possible choices for where you can host your custom node.js backend, once the customisation is complete:
Service | Price from | Payment type |
---|---|---|
https://www.heroku.com/ | 0 | usage based, with freebie quota |
https://aws.amazon.com/ | 0 | usage based, with freebie quota |
https://cloud.google.com/ | 0 | usage based, with freebie quota |
https://azure.microsoft.com/ | 0 | usage based, with freebie quota |
https://www.digitalocean.com/ | $5 / month | fixed price, run on a virtual instance |
VCON cloud provides management API access to your devices, and an easy to use Web UI with device dashboard:
Cloud API access can be authenticated via a Basic HTTP authorisation, or
by setting a Authorization: Bearer APIKEY
header. For a Basic auth,
use your user/password. For a Bearer auth, take your APIKEY from the
"Account" tab in the cloud UI.
NOTE: you can use APIKEY in a Basic auth. Set user to an empty string, and password to your APIKEY. Curl examples for all three methods are listed below:
Method | Description |
---|---|
Basic |
|
APIKEY |
|
Basic + APIKEY |
|
dash.vcon.io provides a test account with the following credentials:
Password | APIKEY | |
---|---|---|
test | test | test |
It is open to everyone for demonstration purposes. Anyone can login to dash.vcon.io using test/test as an email/password. And below is an example of an API usage:
Get all devices registered under a test account:
curl -su :test https://dash.vcon.io/api/v3/devices
List all available functions of device 42:
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/rpc.list
Call device 42, function sys.reboot
:
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/sys.reboot
NOTE: URI in a table below must be prefixed with https://dash.vcon.io/api/v3
.
Method | URI | Description |
---|---|---|
GET | /user | Get user |
POST | /user | Set user. Parameters: JSON object with new user data: {"fullname":"...","company":"...","email":"...","address":"...","phone":"...","settings":{...}} |
GET | /devices | Get device list |
POST | /devices | Create new device. Parameters: none |
GET | /devices/:id | Get device with ID :id |
POST | /devices/:id | Update device with ID :id . Parameters: JSON object with device data: {"labels":{"board":"nano33"}, "state":{...}} |
GET | /devices/:id/fs | List files on device :id |
GET | /devices/:id/fs/:name | Get file :name on device :id |
POST | /devices/:id/fs/:name | Write file :name on device :id . File data is the HTTP POST body, sent varbatim - e.g. using curl --data-binary |
POST | /devices/:id/rpc/:name | Call RPC function :name on device :id . Parameters: none, or a JSON object specific to that RPC function. If no parameters are given, a GET request can be used too |
POST | /devices/:id/ota | Update firmware over the air. By default, a Host MCU gets updated. If you want to OTA a VCON module, append ?target=vcon to the URL. HTTP body must be a raw .hex file data with a new firmware. Example: curl -su :test ttps://dash.vcon.io/api/v3/devices/42/ota?hex=1 --data-binary @PATH_TO_FIRMWARE.hex |
GET | /devices/:id/bye | Disconnect device. If, for various reasons, a device gets stuck, force reconnection |
A Host MCU can receive commands remotely using a technique called RPC bridge. RPC stands for Remote Procedure Call.
VCON module and a cloud communicate with each other using a standard JSON-RPC 2.0 protocol over the secure Websocket. Also, by default, a Host MCU and VCON module talk to each other using JSON-RPC. JSON-RPC is a very simple protocol: client and server exchange JSON strings with the following format:
The rules are simple:
method
attribute, and optional params
id
attribute, then a server must send
a response frame. Otherwise, frames without id
are considered notification
frames. Server does not respond on notification frames{"id":1, "error": {"code": 500, "message": doh""}}
For example, VCON sends a status notifications to the Host MCU. This frame does
not have an id
attribute, thus Host does not send a response:
{"method": "status", "params": "cloud_down"} // VCON -> Host
When Host receives a JSON-RPC frame, it parses the frame and calls a
corresponding handler function. A handler function processes the request and
produces a reply, which is sent back to the VCON. In the following
communication example, VCON calls a custom function sum
that adds two
numbers:
{"id": 12, "method": "Sum", "params": [2,3]} // VCON -> Host
{"id": 12, "result": 5} // VCON <- Host
And here is the example when VCON calls a non-existent function:
{"id": 13, "method": "This_Function_Does_Not_Exist", "params": true} // VCON -> Host
{"id": 13, "error": {"code": -32601, "message": "method not found"}} // Host -> VCON
VCON cloud exposes JSON-RPC interface as a RESTful endpoints, implementing a so-called RPC bridge. Commands are sent to the cloud via HTTP/REST, and the cloud sends commands to devices via JSON-RPC wrapped into a TLS-secured WebSocket connection:
The REST URL should be https://dash.vcon.io/api/v3/devices/:id/rpc/:name
,
where :id
is a device ID, and :name
is an RPC function name that must
be implemented by a device. Note that dash.vcon.io does not know which
functions are implemented by a device - whatever :name
is specified in the
URL, it triggers RPC function :name
on a device. Devices,
however, implement rpc.list
function that enumerates all functions that
device exports. This gives an ability to easily see what functions are
implemented by a device. For example, this curl command lists all functions
implemented by device with ID 42
under a test account:
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/rpc.list
An RPC function may be without parameters, like rpc.list
, fs.list
,
sys.info
or sys.reboot
. Other RPC functions take parameters. Parameters
are set in a JSON string, passed as HTTP POST body. For example,
RPC function fs.read
reads a file on a VCON filesystem, and it expects
a file
and offset
parameters. A function returns base64-encoded
file data from given offset, and also tells how many bytes are left to read:
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/fs.read -d "{\"file\":\"ca.pem\",\"offset\":123}"
Response:
{"file":"ca.pem","offset":123,"left":28127,"data":"MjAyMS...."}
If VCON module has a host microcontroller attached in an RPC (non-transparent) bridge mode, then an additional link is added between VCON and Host MCU. They also talk using JSON-RPC.
NOTE: to stress it again, the following applies only when a bridge setting set to RPC mode. See Configuration Reference section for details.
There is a wildcard RPC function on VCON, host.*
that passes all RPC calls
that start with host.
to the Host microcontroller. So, for example,
RPC function rpc.list
lists all functions on VCON module, whereas RPC
function host.rpc.list
lists all functions exported by a Host MCU. For
example, the following session lists all function on a Host
microcontroller on device 42
, and then reads a temperature an Host MCU.
Note the host.
prefix in those API calls:
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/host.rpc.list
["rpc.list","sensor.read"]
curl -su :test https://dash.vcon.io/api/v3/devices/42/rpc/host.sensor.read
{"value":42}
Below is a detailed diagram describing the whole sequence of calls for the
host.sensor.read
request:
Host MCU can use mjson client library
to implement JSON-RPC. Note that mjson.h
is a completely stand-alone
piece of software, independent of VCON. It helps to turn a microcontroller
into a JSON-RPC client and server using any available transport, for example
an UART connection. Let's demonstrate it.
Take any Arduino board. Install mjson library if it is not already installed.
Note: in order to install an
mjson
library, start Arduino IDE, choose "Sketch" → "Include Library" → "Manage libraries". In the dialog search field, enter "mjson". You should see a "mjson" library. Click on "Install" button to install that library.
Create a new sketch. Copy/paste the following code:
#include "mjson.h"
// RPC handler for "Sum". Expected RPC frame is like this:
// {id: 1, "method": "Sum", "params": [40, 2]}
static void sum(struct jsonrpc_request *r) {
double a = 0, b = 0;
mjson_get_number(r->params, r->params_len, "$[0]", &a);
mjson_get_number(r->params, r->params_len, "$[1]", &b);
jsonrpc_return_success(r, "%d", (int) (a + b));
}
void setup() {
Serial.begin(115200); // Setup serial port
jsonrpc_init(NULL, NULL); // Initialise library
jsonrpc_export("Sum", sum, NULL); // Export "Sum" function
}
static int wfn(const char *frame, int frame_len, void *privdata) {
return Serial.write(frame, frame_len);
}
void loop() {
if (Serial.available() > 0) jsonrpc_process_byte(Serial.read(), wfn, NULL);
}
The sketch above registers a custom RPC function Sum
. In the loop()
,
we read serial input. An expected input is a series of JSON-RPC strings,
delimited by the new line characters.
The jsonrpc_process_byte()
function buffers serial input into an internal
buffer, and when a new line character is read, jsonrpc_process_byte()
calls
jsonrpc_process()
function which parses the received frame and calls a
registered handler function, if any.
Therefore, in order to "talk" to the Arduino using JSON-RPC protocol, we need to open a Serial Monitor, type in a valid JSON-RPC frame in it and press "Enter" to emit a new line character.
Let's call our custom function Sum
we've just created.
Start Arduino Serial Monitor, choose port speed 115200, and type
{"id": 1,"method": "Sum", "params": [2,3]}
. Hit enter.
You should see an answer frame:
Note that mjson.h
library provides one built-in RPC function rpc.list
,
which returns a list of all registered RPC functions. If you are unsure which
functions are provided by the mjson-enabled microcontroller, call rpc.list
:
{"id": 1,"method": "rpc.list"} // Request
{"id": 1,"result": ["rpc.list","Sum"]} // Response
Now you should get an idea. Any microcontroller that implements mjson.h
becomes controlled using a standard JSON-RPC over UART - which you can easily
test manually using Serial Monitor, or in your lab by setting up an integration
test. VCON module just makes that serial line accessible via cloud.
VCON cloud provides a special secure WebSocket endpoint
wss://dash.vcon.io/api/v3/notify
. This is a read-only notifications endpoint.
Each notification is a JSON object with three keys:
name
- a notification name, e.g. online
, offline
, and so ondid
- an ID of a device that generated the eventdata
- an optional notification-specific data.Below is the list of the events:
{"name":"online","did":ID}
- generated by the cloud when a device becomes
online{"name":"offline","did":ID}
- generated by the cloud when a device becomes
offline{"name":"updated","did":ID,"data":{...}}
- generated by the cloud when a
device changes its state
attribute{"name":"ble.adv","did":ID,"data":{...}}
- generaged by the VCON module
in the BLE bridge mode, when ble.mode=2
. The data
contains BLE beacon
data.{"name":"CUSTOM","did":ID,"data":CUSTOM}
- a custom event that could be
generated by a Host microcontroller in the RPC bridge mode. A Host
micro must call the cloud.CUSTOM
RPC function with any params
you want.
For example, this is how a Host microcontroller can send some sensor data:jsonrpc_printf(fn, NULL, "{%Q:%Q,%Q:{%Q:%g}}",
"method", "cloud.temperature",
"params", "value", 123.456)
This call generates {"name":"temperature","did":ID,"data":{"value":123.456}}
NOTE: the cloud UI uses notification endpoint to catch device changes, like online/offline, and dispays that respectively. You can create your custom dashboard using cloud API and notifications.
You can catch all notifications sent by the cloud.
Below is a simple NodeJS application that catches all incoming notifications
and prints them on a console. Create file catcher.js
, copy/paste the
folowing content:
const Websocket = require('ws'); // npm install -g ws
const addr = 'wss://dash.vcon.io/api/v3/notify?access_token=APIKEY';
const reconnect = function() {
const ws = new Websocket(addr, {origin: addr});
ws.on('error', msg => console.log('Got error:', msg.toString()));
ws.on('message', msg => console.log('Got message:', msg.toString()));
ws.on('close', () => setTimeout(reconnect, 1000));
};
reconnect();
Change APIKEY
to your API key which could be copied from the "Account" tab in
the cloud UI. Run this file and see how arriving notifications are printed:
$ node catcher.js
Got message: {"name":"online","did":1}
NOTE: by bringing up a custom notification catcher, you can integrate with any other 3rd party service - for example, store data into a Google/AWS/Azure, dump data in your custom dashboard, send SMS, run analytics, et cetera.
In the BLE bridge mode, VCON module receives BLE beacon advertisements from nearby BLE beacons, and forwards them to the cloud as device notifications. You can catch those notifications by using a notification catcher, and do whatever you want with the data - store in a database, do real-time processing, etc.
Also VCON provides a BLE.Advertise
RPC function, which you can call
from anywhere via the cloud and trigger VCON module to send a BLE advertisement
with configurable payload.
To configure BLE bridge, see ble
section in the
Configuration Reference.
VCON module can update Host MCU remotely. You can securely pass a new firmware over the VCON cloud, then VCON reprograms an attached microcontroller. It works because VCON knows respective flashing protocols for different architectures. Network communication is over TLS, encrypted and authenticated.
See Configuration Reference for exact details on how to setup OTA for your microcontroller.
VCON can also update itself, using the same cloud API. Just add an extra
?target=vcon
query string to the ota endpoint, and send a VCON firmware,
not your MCU firmware:
curl -su :API_KEY 'https://dash.vcon.io/api/v3/devices/ID/ota?target=vcon&hex=1' --data-binary @4m.hex
Useful links:
A VCON module, in order to operate, requires two crucial settings in its configuration file:
wifi.sta.ssid
and wifi.sta.pass
.
For ethernet and cellular, these are not requireddash.pass
Thus this section explains different methods of setting these configuration parameters.
Method 1. Flashing VCON configuration file using a flasher CLI tool
Method 2. Using WiFi access point
A VCON module with unconfigured wifi.sta.ssid
automatically starts
a WiFi network vcon-????
. Join that network, and talk to a VCON module
on IP address 192.168.4.1
. VCON runs an HTTP server which accepts
configuration command:
curl http://192.168.4.1/rpc/config.set -d "{\"config\":{\"wifi\":{\"sta\":{\"ssid\":\"X\",\"pass\":\"Y\"}}},\"save\":true}"
Method 3. Using Bluetooth Low Energy (BLE), described at Quick Start
The app also provides a handy UI for calling RPC services over BLE.
On the following screenshot, a sys.info
RPC service is called.
Note that the available list of RPC services is restricted, and filtered by the
$.rpc.safe
configuration variable. This is the same list that the Host MCU
can call:
The following table summarises built-in RPC functions that are exposed by a VCON module. These functions can be called remotely via REST, or locally via UART (for example, Host MCU can call any of the VCONs RPC function).
This is an example of the remote function call using curl
utility.
APIKEY
is your API key you can get from the Account tab on dash.vcon.io,
ID
is a device ID, and NAME
is an RPC name:
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/NAME
This is an example calling sys.info
function on device 1
:
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/1/rpc/sys.info
{"id":1,"result":{"version":"4m-1.3.3-v3.3","built":"2020-05-09 20:03:20","uptime":9,"ram_free":104360,"reboot_reason":"other watchdog"}}
Some API calls require parameters, a JSON string in HTTP POST body:
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/1/rpc/fs.read -d "{\"file\":\"config.json\"}"
{"id":2,"result":{"file":"config.json","offset":0,"left":0,"data":"ewogICJ3aWZp..."}}
Method | Parameters | Description |
---|---|---|
rpc.list | - | Show all RPC functions available |
sys.info | - | Show general information about a device, like build timestamp, firmware version, uptime in seconds |
sys.log | - | Show the current console log buffer. Every console log message is stored in a fixed-size, 1K buffer. New messages are appended to the end, older messages are removed to keep the space fixed. This RPC returns the log buffer and cleans it. Call this function repeatedly to get devices's console log |
sys.reboot | - | Reboot device |
config.get | - | Show devices configuration |
config.set |
|
Update device configuration. config attribute specify configuration changes. save is an optional parameter (default false ) that tells to make changes permanent by storing them in a config.json file on a device. Note: config.json file keeps only configuration overrides. If you set configuration option to its default value, it disappears from the "config.json" file |
gpio.write |
|
Set a given VCON pin to a given value |
fs.list | - | List files on VCON filesystem |
fs.read |
|
Get file data from given offset. Returned file data is base64-encoded |
fs.write |
|
Write data to a file. Data must be base64-encoded. Data is appended to the end of the file. A written file gets truncated before write, unless an optional append parameter is set to true (it is false by default) |
swd.exec |
|
Send a list of SWD commands to the Host MCU. Available SWD commands:rst - send SWD reset sequencerd0 ,rd1 ,rd2 ,rd3 - read debug registerwd0 ,wd1 ,wd2 - write debug registerra0 ,ra1 ,ra2 ,ra3 - read access registerwa0 ,wa1 ,wa2 ,wa3 - write access registerrm,ADDR - read memory at ADDR (hex)rm,ADDR,MASK,VAL - read memory at ADDR until MASK equals to VALwm,ADDR,VAL - write memoryNote: ADDR, VAL, MASK values must be lowercase hexadecimal numbers without the 0x prefix, e.g.: 2ef . Example - a standard MCU init sequence is rst rd0 wd0,1f wd2,0 wd1,50000f00 wa0,23000012 |
swd.rmem |
|
Read Host MCU memory over SWD |
mcu.reset | - | Reset Host MCU |
spi.txn |
|
Execute SPI transaction on Host MCU. $host.spi must be configured. COMMANDS is a space-separated list of SPI commands which has format ADDRESS HEX_WRITE.READ_SIZE. For example, reading AVR fuses is the following command: ac530000.04 50000000.04 58080000.04 50080000.04 |
wifi.scan | - | Scan WiFi and return a list of available WiFi networks |
mqtt.pub |
|
Publish message to MQTT topic. Note: $.host.mode must be set to 2, and $.mqtt section configured |
ws.send |
|
Send data to the Websocket server. Note: $.host.mode must be set to 3, and $.ws section configured |
host.* | any | Forward RPC frame to the Host MCU, stripping host. prefix from the method name |
cloud.* | any | Forward RPC frame to the cloud, stripping cloud. Use this RPC to send custom notifications to the cloud. The following method names are treated specially by the cloud:{"method":"cloud.state.set","data":...} - This notification changes device's state attribute on the cloud. The behavior is similar to AWS/Azure device shadow - setting an attribute touches only that attribute and leaves the rest intact. To delete an attribute, set it to null |
VCON module configuration is a JSON string. It is stored on the root filesystem
in "config.json" file. It could be viewed by calling config.get
RPC function:
curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/config.get
{"wifi":{"sta":{"ssid":"MyWiFiNetwork","pass":"mypassword"}},"dash":{...},...}
In order to change one or more configuration option, call config.set
RPC function and pass
a JSON object with 2 parameters: required config
with your changes, and optional
boolean save
to save your changes to "config.json".
For example, this command changes an architecture of Host microcontroller to
200 (STM32 with 1k flash pages), and saves:
curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/config.set -d "{\"config\":{\"host\":{\"arch\":200}},\"save\":true}"
The following table describes all configuration options. Every option name is specified by its JSON path.
Name | Type | Description |
---|---|---|
$.wifi.mode | int | WiFi mode. Default: 1 (enabled). Possible values: 0 - disabled, 1 - enabled |
$.wifi.sta.ssid | string | WiFi network name. Default: "" (unset) |
$.wifi.sta.pass | string | WiFi network password. Default: "" (unset) |
$.wifi.ap.ssid | string | WiFi Access Point name for an unconfigured device . Default: "vcon-????". Note: ? characters in the AP name will be replaced with the MAC address hex values |
$.dash.url | string | Cloud address. Default: "wss://dash.vcon.io/api/v3/rpc". Override it if you have a dedicated/private cloud instance |
$.dash.pass | string | Device cloud auth token. Default: "" (unset) |
$.debug.level | string | Console log level. Default: "2". Possible values: "0" - disable console log "1" - log errors only "2" - log errors and info messages "3" - log erros, info and debug messages "4" - log everything Per-file overrides are supported, e.g. "2,tcp:3" |
$.debug.tlslevel | int | Console log level for TLS. Default: 1. Possible values: 0-5 |
$.debug.udpaddr | string | UDP log address "IP:PORT". Default: "". If set, then console log messages are also sent to the specified HOST:PORT |
$.mqtt.url | string | MQTT server URL. Required only when $.host.mode is 2 . Default: NULL (not set). The format of the URL is PROTO://USER:PASS@HOST:PORT , where PROTO is mqtt or mqtts , USER and PASS are MQTT credentials (could be omitted). NOTE: VCON sets MQTT client ID equal to the $.device.id |
$.mqtt.rx | string | MQTT topic for VCON to subsribe to. All data read from this topic, VCON will forward to the Host MCU UART RX |
$.mqtt.tx | string | MQTT topic for VCON to publish to. All data VCON reads from the Host MCU UAET, VCON publishes to that topic |
$.mqtt.cert | string | Client TLS certificate file name. Required only for 2-way TLS. Must be prefixed with /spiffs , e.g. /spiffs/my_cert.pem |
$.mqtt.key | string | Client TLS certificate key file name. Required only for 2-way TLS. Must be prefixed with /spiffs , e.g. /spiffs/my_key.pem |
$.ws.url | string | Websocket server URL. Required only when $.host.mode is 3 . Default: NULL (not set) |
$.ws.hdrs | string | Websocket extra HTTP headers for WS handshake. Required only when $.host.mode is 3 . Default: NULL (not set). Example: Authorization: Bearer foobar\r\n |
$.ws.cert | string | Client TLS certificate file name. Required only for 2-way TLS. Must be prefixed with /spiffs , e.g. /spiffs/my_cert.pem |
$.ws.key | string | Client TLS certificate key file name. Required only for 2-way TLS. Must be prefixed with /spiffs , e.g. /spiffs/my_key.pem |
$.device.id | string | An arbitrary device identification. Used only in the BLE adv forwarding, and passed as a gw BLE adv frame parameter |
$.rpc.safe | string | List of comma-separated RPC functions available to Host MCU, and over the HTTP server. Default: config.set,sys.*,rpc.*,gpio.*,cloud.* |
$.host.arch | int | A Host MCU arhitecture. Default: 0 (unset). Required for reprogramming. Possible values: 100 - Microchip SAMD200 - STM32 with 1k flash pages201 - STM32 with 2k flash pages202 - STM32 with 16,16,16,16,64,128,128,128k pages203 - STM32 with 32,32,32,32,128,256,256,256k pages300 - AVR (e.g. classic Arduinos) |
$.host.rst | int | VCON pin to which Host MCU's RESET pin is connected. Default: -1 (unset). Required for reprogramming / rebooting an attached MCU if that cannot be done via SWD, for example for attached AVR |
$.host.mode | int | Host MCU communication mode. Default: 0 (disabled). Possible values:0 - disabled1 - RPC bridge. Serial link is exchanging JSON-RPC frames, delimited by newline characters2 - transparent UART/MQTT bridge. Serial data is uninterpeted, sent to/from mqtt.rx and mqtt.tx topics. Make sure to configure the mqtt section 3 - transparent UART/Websocket bridge. Serial data is uninterpeted, sent to/from $.ws.url Websocket server. Make sure to configure the $.ws section |
$.host.swd.io | int | VCON pin to which Host MCU's SWDIO pin is connected. Default: -1 (unset). Required for reprogramming ARM MCUs |
$.host.swd.clk | int | VCON pin to which Host MCU's SWDCLK pin is connected. Default: -1 (unset). Required for reprogramming ARM MCUs |
$.host.swd.dur | int | SWD clock duration in VCON's nop instructions. Default: 50 |
$.host.uart.tx | int | VCON pin to which Host MCU's UART TX pin is connected. Default: -1 |
$.host.uart.rx | int | VCON pin to which Host MCU's UART RX pin is connected. Default: -1 |
$.host.uart.cts | int | VCON pin to which Host MCU's UART CTS pin is connected. Default: -1 (unset) |
$.host.uart.rts | int | VCON pin to which Host MCU's UART RTS pin is connected. Default: -1 (unset) |
$.host.uart.baud | int | A Host MCU's UART baud rate. Default: 115200 |
$.ble.mode | int | BLE gateway mode. Default: 0 (disabled). Possible values:0 - BLE disabled1 - BLE enabled, configurator is starged2 - BLE enabled. Beacon data is forwarded to the cloud as a ble.adv notification. NOTE: this mode disables BLE device configurator 3 Beacon data is forwarded to the $.mqtt.tx topic of the configured MQTT server. NOTE: this mode disables BLE device configurator |
$.ble.filter | string | A comma separated list of glob patterns. BLE advertisements whose MAC addresses or names do not match all patterns, are ignored. Default: empty, which means all BLE advertisements match. Example: MyName*,aabbcc* - match devices whose names start with MyName or MAC address start with aabbcc |
$.ble.lowat | int | A RAM threshold. If free RAM is lower than this value, then ignore an incoming BLE advertisement, do not forward it to the cloud to preserve RAM. Default: 0 |
$.eth.mode | int | Ethernet mode. Default: 0 (disabled). Possible value:0 - Ethernet disabled1 - Ethernet enabled |
$.eth.addr | int | Ethernet PHY address. Default: -1 |
$.eth.clk | int | Ethernet clock source. Default: 0 . Possible values: CONFIG_ETH_RMII_CLK_* constants |
$.eth.mdc | int | Ethernet MDC pin. Default: -1 |
$.eth.mdio | int | Ethernet MDIO pin. Default: -1 |
$.eth.pwr | int | Ethernet PHY power pin. Default: -1 |
$.pins.status | int | Status LED pin. Default: 2 (enabled). To disable, set to -1 . If enabled, it is assumed that is an LED, and does the following:fast 75 ms blink - when network is not connected, slow 750 ms blink - when network is connected, but cloud is not, steady light - when cloud is connected |
$.pins.factory | int | Factory reset pin. Default: 4 (enabled). To disable, set to -1 . If enabled, it is assumed that this pin is connected to a (pulled up) button. Pressing this button for more than 3 seconds (pulling low) triggers factory reset, which copies factory.json to `config.json |
If any quick start guide step fails, or the VCON module behaves in an unexpected way, please follow these steps:
In this section, we learn:
If any step of this guide fails, please follow the Troubleshooting section.
Step 1. Get an Arduino Nano 33 IoT board and connect it to your workstation
over the USB cable. A serial port must appear on your workstation,
e.g. COM3
on windows or /dev/cu.usbmodem14101
on Mac.
Step 2. Flash a Nina passthrough sketch to the board. Start Arduino IDE, choose File → Examples → WiFiNINA → Tools → SerialNINAPassthrough. Choose Tools → Board → Arduino Nano 33 IoT. Choose Tools → Port. Upload the sketch.
Step 3. Download and unzip VCON flashing tool from https://vcon.io/downloads/cli/cli.zip
Step 4. In the unzipped folder, edit fs/config.json
file - set
WiFi network name, WiFi password, and cloud password:
{
"wifi": {"sta": {"ssid": "WIFI_NETWORK", "pass": "WIFI_PASSWORD"}},
"dash": {"pass": "CLOUD_DEVICE_PASSWORD"},
"host": {
"arch": 100,
"uart": {"rx": 23, "tx": 12,"cts": 19, "rts":22},
"swd": {"io": 32, "clk": 14}
}
}
To obtain device's cloud password, login to https://dash.vcon.io, add a new device if needed, then choose "action" → "Copy device password to clipboard". Save edited file.
Step 5. Download https://vcon.io/downloads/fw/2m.full.hex into the unzipped cli/
directory
Step 6. Start command prompt (or terminal on Mac/Linux). Run cd PATH/TO/cli
to go into the unzipped cli/
directory. After that, run
the following command (change COMPORT
to the board's serial port):
OS | Command |
---|---|
Windows |
|
Linux |
|
MacOS |
|
Step 7. A device on dash.vcon.io must become online:
Step 8. Update firmware on the SAMD21 microcontroller remotely. In the Arduino IDE, copy/paste the following sketch. This is a standard Blink sketch with the only extra addition that enables ublox NINA module:
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
// Important: enable NINA module! Otherwise cloud connection drops.
pinMode(NINA_GPIO0, OUTPUT); digitalWrite(NINA_GPIO0, HIGH);
pinMode(NINA_RESETN, OUTPUT); digitalWrite(NINA_RESETN, HIGH);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
Choose Sketch → Export compiled Binary, to compile this sketch. Choose
Sketch → Open Sketch folder. You should see a
Blink.ino.nano_33_iot.bin
file there, which is a binary firmware.
In order to remotely flash it, we need
to convert this binary file into a .hex file. In your Arduino installation,
find Nano 33 IoT bootloader file, samd21_sam_ba_arduino_nano_33_iot.bin
.
Copy both the bootloader and compiled firmware to the cli/
folder.
Run the following command from the command prompt to create nano33.hex
:
./OS/cli mkhex 0 samd21_sam_ba_arduino_nano_33_iot.bin 0x2000 Blink.ino.nano_33_iot.bin > nano33.hex
NINA module updates SAMD21 microcontroller over the SWD interface. To make
that possible, we need to physically wire two pins together, on the
bottom of the Nano 33 IoT board. The SAMD21 SWDIO pad needs to be connected
to the PA6 pin, and SWDCLK pad - to PA5 pin. See on the picture:
And now, a remote firmware update (set APIKEY
and ID
):
curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/ota?hex=1 --data-binary @nano33.hex
An LED on a board must start blinking. Congratulations, now we could remotely update Nano 33 IoT firmware at any time!
Below, see a demo video that demonstrates how VCON is used to update Arduino Nano:
Step 9. Remotely control an Arduino board. Blinking LED is exciting, but how about remote commands? We want to control our Nano 33 IoT from a command line, from Web UI or from a mobile phone. That is no problem.
Let's teach our Arduino to turn any GPIO pin low or high. In the Arduino IDE, choose "Sketch" → "Include Library" → "Manage libraries". In the dialog search field, enter "mjson". You should see a "mjson" library. Click on "Install" button to install that library.
Create a new sketch with the following content:
#include <mjson.h>
// Expected params: {"pin":13, "value": 1}
static void gpio_write_rpc(struct jsonrpc_request *r) {
double pin, val;
if (mjson_get_number(r->params, r->params_len, "$.pin", &pin) &&
mjson_get_number(r->params, r->params_len, "$.val", &val)) {
pinMode(pin, OUTPUT);
digitalWrite(pin, val);
jsonrpc_return_success(r, "true");
} else {
jsonrpc_return_error(r, JSONRPC_ERROR_BAD_PARAMS, "pin, val required", 0);
}
}
void setup() {
// Important: enable NINA module! Otherwise cloud connection drops.
pinMode(NINA_GPIO0, OUTPUT); digitalWrite(NINA_GPIO0, HIGH);
pinMode(NINA_RESETN, OUTPUT); digitalWrite(NINA_RESETN, HIGH);
SerialHCI.begin(115200); // NINA and SAMD21 communicate over SerialHCI, see https://github.com/arduino/ArduinoCore-samd/blob/master/variants/nano_33_iot/variant.cpp
jsonrpc_init(NULL, NULL);
jsonrpc_export("gpio.write", gpio_write_rpc, NULL);
}
static int wfn(const char *frame, int frame_len, void *userdata) {
(void) userdata; // Unused
return SerialHCI.write(frame, frame_len);
}
void loop() {
if (SerialHCI.available() > 0) jsonrpc_process_byte(SerialHCI.read(), wfn, NULL);
}
This Arduino sketch implements a JSON-RPC server over the serial line
SerialHCI, which is connected to the NINA module. Arduino firmware
reads JSON frames from the serial, process them, and writes replies back.
We could implement any number of any management functions we want. This
example firmware implements a gpio.write
function that could turn any
GPIO pin on or off. Since SAMD21 microcontroller, our Host MCU, is connected
to the VCON module (ublox NINA), we can call these functions over the cloud.
Compile this sketch, convert to .hex, and remotely update your Arduino
Nano 33 IoT using the procedure described in Step 8.
In your terminal, enter the following command to list all RPC functions
exported by the Arduino firmware (change APIKEY
and ID
):
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/host.rpc.list
{"id":4,"result":["gpio.write","rpc.list"]}
We see that two functions are available: a built-in rpc.list
, and our
custom gpio.write
. Let's turn pin 13 (a built-in LED) on:
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/host.gpio.write -d "{\"pin\":13,\"val\":1}"
{"id":5,"result":true}
An LED turns on! Congratulations - now we can control Arduino from anywhere. We can read sensor values, drive motors, or send any other custom commands. And those commands are available over the standard authenticated REST API.
Step 10. Send data. Now we can remotely update Arduino firmware, and remotely control it - the only one missing bit left - how would we send data from the Arduino to the outside world? That is done by means of Notifications. A device can send a JSON-RPC notification frame with any adata to the cloud. A cloud has a special WebSocket endpoint, which receives all notifications from all devices. Let's demonstrate that!
We'll modify the firmware from Step 9 by sending a custom notification peiodically - every second. In the notification, we'll send the current device uptime in milliseconds:
#include <mjson.h>
// Expected params: {"pin":13, "value": 1}
static void gpio_write_rpc(struct jsonrpc_request *r) {
double pin, val;
if (mjson_get_number(r->params, r->params_len, "$.pin", &pin) &&
mjson_get_number(r->params, r->params_len, "$.val", &val)) {
pinMode(pin, OUTPUT);
digitalWrite(pin, val);
jsonrpc_return_success(r, "true");
} else {
jsonrpc_return_error(r, JSONRPC_ERROR_BAD_PARAMS, "pin, val required", 0);
}
}
void setup() {
// Important: enable NINA module! Otherwise cloud connection drops.
pinMode(NINA_GPIO0, OUTPUT); digitalWrite(NINA_GPIO0, HIGH);
pinMode(NINA_RESETN, OUTPUT); digitalWrite(NINA_RESETN, HIGH);
SerialHCI.begin(115200); // NINA and SAMD21 communicate over SerialHCI, see https://github.com/arduino/ArduinoCore-samd/blob/master/variants/nano_33_iot/variant.cpp
jsonrpc_init(NULL, NULL);
jsonrpc_export("gpio.write", gpio_write_rpc, NULL);
}
static int wfn(const char *frame, int frame_len, void *userdata) {
(void) userdata; // Unused
return SerialHCI.write(frame, frame_len);
}
void loop() {
if (SerialHCI.available() > 0) jsonrpc_process_byte(SerialHCI.read(), wfn, NULL);
static unsigned long prev;
unsigned long now = millis();
if (now - prev > 1000) {
// This will generate the following event by dash.vcon.io:
// {"name": "myevent", "data": MILLISECONDS}
mjson_printf(wfn, NULL, "{%Q:%Q,%Q:%lu}\n", "method", "cloud.myevent", "params", now);
prev = now;
}
}
Remotely update your Arduino with that firmware.
The board now sends custom notification events to the cloud. Let's
catch them using a simple NodeJS application. Create a new file catcher.js
,
copy/paste the folowing content:
const Websocket = require('ws'); // npm install -g ws
const addr = 'wss://dash.vcon.io/api/v3/notify?access_token=APIKEY';
const reconnect = function() {
const ws = new Websocket(addr, {origin: addr});
ws.on('error', function(msg) { console.log('Got error:', msg.toString()) });
ws.on('message', function(msg) { console.log('Got message:', msg.toString()) });
ws.on('close', function() { setTimeout(reconnect, 1000) });
};
reconnect();
Change APIKEY
to your API key which could be copied from the "Account" tab in
the cloud UI. Run this file and see how arriving notifications are printed:
$ node catcher.js
Got message: {"name":"myevent","did":4,"data":263901}
Got message: {"name":"myevent","did":4,"data":263902}
Got message: {"name":"myevent","did":4,"data":263903}
Congratulations! You've just got your custom data reported from your board. Your catcher.js application could save data in any database - for example, Google Firebase, or do any other custom data processing. This is the way to integrate VCON with your own cloud infrastructure. Obviously, you could use any other language to implement a catcher - for example, Python or Go. For example, if your cloud infrastructure reside on AWS, host your catcher application on AWS too, and you can directly save messages to the Dynamo DB or any other part of AWS cloud. Similarly for Microsoft Azure, Google Cloud, or your own private cloud infrastructure - the approach is the same.
In this guide, we are going to flash VCON firmware to the Olimex ESP32 POE board, and configure it as a BLE bridge. Expected result:
If any step of this guide fails, please follow the Troubleshooting section.
Step 1. Get a Olimex ESP32 POE board and connect it to your workstation
over the USB cable. IMPORTANT: make sure that Ethernet cable is unplugged,
otherwise you can burn your board. A serial port must appear on your
workstation, e.g. COM3
on windows or /dev/cu.wchusbserial1420
on Mac.
Step 2. Download and unzip VCON flashing tool from https://vcon.io/downloads/cli/cli.zip
Step 3. In the unzipped folder, edit fs/config.json
file.
Make it look like this:
{
"dash": {"pass": "CLOUD_DEVICE_PASSWORD"},
"wifi": {"mode": 0},
"host": {"mode": 0},
"ble": {"mode": 2},
"eth": {"mode": 1, "addr": 0, "clk": 3, "mdc": 23, "mdio": 18, "pwr": 12}
}
Change CLOUD_DEVICE_PASSWORD
to the actual password. To get it,
login to https://dash.vcon.io, add a new device if needed, then choose
"action" → "Copy device password to clipboard".
Save config.json
when done.
Step 4. Download https://vcon.io/downloads/fw/4m.full.hex into the unzipped cli/
directory
Step 5. Start command prompt (or terminal on Mac/Linux). Run cd PATH/TO/cli
to go into the unzipped cli/
directory. After that, run
the following command (change COMPORT
to the board's serial port):
OS | Command |
---|---|
Windows |
|
Linux |
|
MacOS |
|
Step 6. Unplug USB cable. Plug in Ethernet cable. The lights on the
board must switch on. If they not, then the POE (Power Over Ethernet)
is probably not enabled, and it is safe to plug in USB cable to power
the board. A device on dash.vcon.io must become online:
Step 7. The board now sends BLE notification events to the cloud. Let's
catch them using a simple NodeJS application.
Create file catcher.js
, copy/paste the folowing content:
const Websocket = require('ws'); // npm install -g ws
const addr = 'wss://dash.vcon.io/api/v3/notify?access_token=APIKEY';
const reconnect = function() {
const ws = new Websocket(addr, {origin: addr});
ws.on('error', function(msg) { console.log('Got error:', msg.toString()) });
ws.on('message', function(msg) { console.log('Got message:', msg.toString()) });
ws.on('close', function() { setTimeout(reconnect, 1000) });
};
reconnect();
Change APIKEY
to your API key which could be copied from the "Account" tab in
the cloud UI. Run this file and see how arriving notifications are printed:
$ node catcher.js
Got message: {"name":"ble.adv","did":1,"data":{"rssi":-71,"addr":"ac233fa14bdd","adv":"0201060303e1ff0d16e1ffa1026401dd4ba13f23ac","rsp":""}}
Got message: {"name":"ble.adv","did":1,"data":{"rssi":-72,"addr":"ac233fa14bdd","adv":"0201060303e1ff0d16e1ffa1026401dd4ba13f23ac","rsp":""}}
Got message: {"name":"ble.adv","did":1,"data":{"rssi":-76,"addr":"ac233fa14bdd","adv":"0201060303e1ff0d16e1ffa1026401dd4ba13f23ac","rsp":""}}
Congratulations! You've just got your beacon data via the cloud.
In this section, we learn how to remotely update STM32F103C8T6 BluePill board, how to control it remotely, and how to send data from the BluePill. The same approach would work for any other STM32 board.
If any step of this guide fails, please follow the Troubleshooting section.
Step 1. Get any ESP32-based board and connect it to your workstation
over the USB cable. A serial port must appear on your workstation,
e.g. COM3
on windows or /dev/cu.usbmodem14101
on Mac.
Step 2. Download and unzip VCON flashing tool from https://vcon.io/downloads/cli/cli.zip
Step 3. In the unzipped folder, edit fs/config.json
file - set
WiFi network name, WiFi password, and cloud password:
{
"wifi": {"sta": {"ssid": "WIFI_NETWORK", "pass": "WIFI_PASSWORD"}},
"dash": {"pass": "CLOUD_DEVICE_PASSWORD"},
"host": {
"arch": 200,
"uart": {"rx": 25, "tx": 26},
"swd": {"io": 21, "clk": 22}
}
}
To obtain device's cloud password, login to https://dash.vcon.io, add a new device if needed, then choose "action" → "Copy device password to clipboard". Save edited file.
Step 4. Download https://vcon.io/downloads/fw/4m.full.hex into the unzipped cli/
directory
Step 5. Start command prompt (or terminal on Mac/Linux). Run cd PATH/TO/cli
to go into the unzipped cli/
directory. After that, run
the following command (change COMPORT
to the board's serial port):
OS | Command |
---|---|
Windows |
|
Linux |
|
MacOS |
|
Step 6. A device on dash.vcon.io must become online:
Step 7. Wire STM32 to the VCON.
VCON pin | BluePill pin |
---|---|
3.3V | 3.3V |
GND | GND |
IO 21 | SWDIO |
IO 22 | SWDCLK |
IO 25 | PA9 (UART TX) |
IO 26 | PA10 (UART RX) |
As an example, this picture shows the wiring for ESP32 PICOD4-Kit V3 and Bluepill:
Step 9. Update STM32 Bluepill firmware remotely.
Download a prebuilt STM32 firmware from
https://github.com/cesanta/stm32-bluepill/releases/download/1.0/bluepill.hex
into the cli/
directory. Run the following command to update STM32
(substitute APIKEY
with your API key from the Account tab, and ID
with
your device ID):
curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/ota?hex=1 --data-binary @bluepill.hex
NOTE: if you want to rebuild the STM32 firmware yourself, please use https://github.com/cesanta/stm32-bluepill
Step 8. Send control commands to the Bluepill board.
The bluepill firmware we use is very simple - see
main.c.
It blinks, and also it
implements JSON-RPC over UART using the
mjson client library.
We have configured VCON module in the RPC bridge mode, and that means we
can call any RPC function that Bluepill implements, remotely. Let's do that,
call a built-in RPC function rpc.list
to see what functions are
implemented on a Bluepill (change APIKEY
and ID
):
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/host.rpc.list
{"id":4,"result":["SetCycles","rpc.list"]}
We see that there are rpc.list
and SetCycles
. Let's call
SetCycles
to change the blinking interval to blink much faster:
$ curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/host.SetCycles -d "{\"period\":30000}"
{"id":5,"result":null}
The board must start to blink faster. This demonstrates how we can remotely trigger absolutely any piece of functinality on the Host MCU.
In this section, we learn how to remotely update a classic Arduino Nano, how to control it remotely, and how to send data from it. The same approach would work for any other AVR board - like Uno, Pro Mini, Mega, etc.
If any step of this guide fails, please follow the Troubleshooting section.
Step 1. Get any ESP32-based board and connect it to your workstation
over the USB cable. A serial port must appear on your workstation,
e.g. COM3
on windows or /dev/cu.usbmodem14101
on Mac.
Step 2. Download and unzip VCON flashing tool from https://vcon.io/downloads/cli/cli.zip
Step 3. In the unzipped folder, edit fs/config.json
file - set
WiFi network name, WiFi password, and cloud password:
{
"wifi": {"sta": {"ssid": "WIFI_NETWORK", "pass": "WIFI_PASSWORD"}},
"dash": {"pass": "CLOUD_DEVICE_PASSWORD"},
"host": {
"arch": 300,
"rst": 25,
"uart": {"rx": 33, "tx": 32},
"spi": {"mosi": 23, "miso": 19, "clk": 18, "freq": 100000}
}
}
To obtain device's cloud password, login to https://dash.vcon.io, add a new device if needed, then choose "action" → "Copy device password to clipboard". Save edited file.
Step 4. Download https://vcon.io/downloads/fw/4m.full.hex into the unzipped cli/
directory
Step 5. Start command prompt (or terminal on Mac/Linux). Run cd PATH/TO/cli
to go into the unzipped cli/
directory. After that, run
the following command (change COMPORT
to the board's serial port):
OS | Command |
---|---|
Windows |
|
Linux |
|
MacOS |
|
Step 6. A device on dash.vcon.io must become online:
Step 7. Wire Arduino to the VCON
NOTE: Pay attention to the voltage level. If you're using 3v3 Arduino, there is nothing to worry about. If you're using 5v Arduino, make sure to avoid 5V signals on ESP32 pins.
VCON pin | Arduino pin |
---|---|
5V | RAW |
GND | GND |
IO 33 | RX |
IO 32 | TX |
IO 23 | SPI MOSI |
IO 19 | SPI MISO |
IO 18 | SPI CLOCK |
IO 25 | RESET |
As an example, this picture shows the wiring for ESP32 PICOD4-Kit V3 and
Arduino Pro Mini clone:
Step 8. Reprogram Arduino remotely. Start Arduino IDE. Choose a standard
Blink example: File → Examples → Basics → Blink.
Choose Tools → Board → Arduino Uno.
Now, compile: choose Sketch → Export compiled binary.
When done, choose Sketch → Open Sketch Folder - you should see a
compiled .hex file in your Arduino folder, Blink.ino.standard.hex
.
Open https://dash.vcon.io, and choose action → Update firmware.
In a dialog, set Target to Host MCU
, and Method to .hex file upload
.
Click on choose .hex file
, and in the file selection dialog,
navigate to your Arduino sketch folder, click on your compiled .hex
file and click Open.
Your should see a firmware update progress bar appear, and when done, your Arduino should start blinking.
Step 9. Remote control. Let's teach our Arduino to turn any GPIO pin low or high using remote command. In the Arduino IDE, choose "Sketch" → "Include Library" → "Manage libraries". In the dialog search field, enter "mjson". You should see a "mjson" library. Click on "Install" to install that library. When installed, create a new sketch with the following code:
#include <mjson.h>
// Expected params: {"pin":13, "value": 1}
static void gpio_write_rpc(struct jsonrpc_request *r) {
double pin, val;
if (mjson_get_number(r->params, r->params_len, "$.pin", &pin) &&
mjson_get_number(r->params, r->params_len, "$.val", &val)) {
pinMode(pin, OUTPUT);
digitalWrite(pin, val);
jsonrpc_return_success(r, "true");
} else {
jsonrpc_return_error(r, JSONRPC_ERROR_BAD_PARAMS, "pin, val required", 0);
}
}
void setup() {
Serial.begin(115200);
jsonrpc_init(NULL, NULL);
jsonrpc_export("gpio.write", gpio_write_rpc, NULL);
}
static int wfn(const char *frame, int frame_len, void *userdata) {
(void) userdata; // Unused
return Serial.write(frame, frame_len);
}
void loop() {
if (Serial.available() > 0) jsonrpc_process_byte(Serial.read(), wfn, NULL);
}
Compile this firmware and remotely update your Arduino just like
described in a previous step. When done, your Arduino must stop blinking.
But we can now turn an LED on and off remotely! Let's turn in on
(change APIKEY
to your API key, and ID
to your device ID):
curl -su :APIKEY https://dash.vcon.io/api/v3/devices/ID/rpc/host.gpio.write -d "{\"pin\":13,\"val\":1}"
You should see an LED turning on. Repeat the previous command, setting val
to 0
. An LED should go off. This demonstrates how you can remotely control
your Host MCU - like, get sensor data, drive motors, or perform any other action.
In this section, we learn how to:
If any step of this guide fails, please follow the Troubleshooting section.
Step 1. Prepare the VCON + Arduino setup.
We will use an exact the same wiring as in the previous guide, VCON + Arduino Nano. Therefore, complete steps 1 to 9 inclusive of the VCON + Arduino Nano, then return back here.
At this point, we have a classic Arduino board connected to the cloud. We can remotely update the Arduino board to any firmware of our choice.
Step 2. Flash an Arduino firmware.
Create a new sketch with the following contents:
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (Serial.available() > 0) {
int character = Serial.read();
if (character == '0') digitalWrite(LED_BUILTIN, LOW); // '0' switches LED off
if (character == '1') digitalWrite(LED_BUILTIN, HIGH); // '1' switches LED on
if (character == '2') Serial.write(digitalRead(LED_BUILTIN) ? "1\n" : "0\n");
}
}
Load this firmware to the Arduino remotely. Let us take a closer look at what this firmware does.
In the loop()
function, we read characters from the UART port:
"0"
, we turn the built-in LED off,"1"
, we turn the built-in LED on,"2"
, we print the current status of the LED to the UART.To summarise, this simple sketch implements a very simple Arduino LED control over the UART. In the following steps, we make Arduino serial data available over the AWS IoT MQTT:
Therefore, by writing data into the RX topic, we'll write data to the Arduino serial input, and we can read Arduino serial output by subscribing and reading the TX topic.
Let's go ahead and configure AWS IoT.
Step 3. Configure AWS IoT.
Login to the AWS IoT console. In the left menu, click on "Manage" / "Things".
Click on "Create" button to create a new thing. Chooose "Create a single thing".
Give it a name "vcon". Click "Next". Click on "Create certificate".
Download a "certificate for this thing", and rename it to cert.pem
.
Download a private key, and rename it to key.pem
. Click on "Activate" to
activate this certificate. Click on "Attach a policy". Select a policy
for the new thing, and click to "Register thing".
If you don't have a policy yet, create a new permissive policy:
{
"Statement": [
{
"Effect": "Allow",
"Action": "iot:*",
"Resource": "*"
}
],
"Version": "2012-10-17"
}
Step 4. Configure VCON for AWS IoT.
Copy cert.pem
and key.pem
to the cli/fs/
directory.
Open cli/fs/config.json
file with VCON configuration. Add an extra
section in it, mqtt
, and set the $.host.mode
attribute to 2
, which
means "use transparent MQTT/UART bridge". Your config.json
file should look like this:
{
"wifi": {"sta": {"ssid": "WIFI_NETWORK", "pass": "WIFI_PASSWORD"}},
"dash": {"pass": "CLOUD_PASSWORD"},
"mqtt": {"rx":"vcon/rx", "tx": "vcon/tx","url": "mqtts://cert.pem:key.pem!AWSADDRESS:8883"},
"host": {
"arch": 300,
"mode": 2,
"rst": 25,
"uart": {"rx": 33, "tx": 32},
"spi": {"mosi": 23, "miso": 19, "clk": 18, "freq": 100000}
}
}
You need to find out an AWSADDRESS
- an address of AWS IoT server.
In AWS IoT console, click on "vcon" thing, and choose "Interact" on the left
hand side menu. The address will be printed in the HTTPS
section, and look like this:
xxxxxxxxx-xxxxx.iot.xxxxxx.amazonaws.com
Copy it, and replace AWSADDRESS
in the config.json
file. Save config file
and reflash your VCON module using a command outline in Step 6 of the
previous guide.
Congrats! Now VCON creates an extra connection to AWS IoT, forwarding the
data to/from Arduino UART port to the vcon/rx
and vcon/tx
MQTT topics.
Step 5. Remote control Arduino via Aws IoT MQTT.
Go to AWS IoT Console, choose "Test" in the left hand side menu.
Type vcon/#
in the "Subscription topic" textbox, click on "Subscribe to topic".
Type vcon/rx
into the "Publish" textbox, and type 1
into the text area.
Click on "Publish to topic" to send a character 1
into the Arduino UART:
You should see an LED turning on.
Now publish character 2
in the same way. This way we ask Arduino, what
is the status of an LED. We should see Arduino replying with message 1
,
telling us that the LED is on.
Congratulations! Now we can easily control a classic Arduino board via AWS IoT, and remotely update it at any time.