Overview

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 like VCON-D51, or 3rd party hardware like Arduino Nano 33 Iot
  • VCON device management cloud (shared at https://dash.vcon.io or dedicated)

VCON architecture

VCON module is, in turn:

  • Any device or module based on an ESP32 microcontroller,
  • With a special pre-built firmware loaded on ESP32

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.

Quick Start Guide

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:

BLE app

Step 4. Select a device and click "Pair":

BLE app chooser

Step 5. Go to https://dash.vcon.io and copy device password to the clipboard by by selecting "action" -> "Copy device password to clipboard:

Copy device password

Step 6. Fill in WiFi network name, WiFi password, device password and save settings:

BLE app chooser

Your device on a dashboard should become online:

Copy device password

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.

Hardware boards

VCON-328

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.

BLE app chooser

Below is the pinout. Pin names are the same as for the Arduino Uno:

VCON-328

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

VCON-D51

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.

VCON-D51

Top and bottom views:

D51 top view D51 bottom view

Below is the pinout. Pin names are per SAMD51 datasheet:

VCON-D51

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

Recipes

Send data to MQTT

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.

Send data to anything

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.

Remote control

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.

Advanced remote control

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.

Complete dashboard

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:

  1. A device, which
    • can be controlled remotely by switching LED on or off
    • sends data to the cloud that simulates sensor readings
  2. A custom dashboard, which
    • is authenticated, requires users to log in
    • stores sensor data in a local database (to a JSON file for simplicity)
    • could be modified, and hosted anywhere - on a development laptop or on online hosting platform like AWS. The dashboard looks like this:

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

Management Cloud

Overview

VCON cloud provides management API access to your devices, and an easy to use Web UI with device dashboard:

Cloud dashboard

Authentication

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
curl -su EMAIL:PASSWORD https://dash.vcon.io/api/v3/devices 
APIKEY
curl -X 'Authorization: Bearer APIKEY' https://dash.vcon.io/api/v3/devices
Basic + APIKEY
curl -su :APIKEY https://dash.vcon.io/api/v3/devices

Test account

dash.vcon.io provides a test account with the following credentials:

Email 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

Cloud API Reference

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

Connectivity Chip

RPC (remote control)

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:

JSON-RPC

The rules are simple:

  • Request frames have a mandatory method attribute, and optional params
  • If request frame has an optional 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
  • On error, server replies with an error frame, e.g.: {"id":1, "error": {"code": 500, "message": doh""}}
  • For serial communication, frames are delimited by newline characters. There should not be newline characters inside frames
  • Communication is bidirectional: Host MCU can call VCON's functions, and VCON can call Host's functions

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:

RPC to REST

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:

RPC call diagram

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:

Arduino RPC tutorial

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.

Notifications

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 on
  • did - an ID of a device that generated the event
  • data - an optional notification-specific data.

Notifications diagram

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.

BLE gateway

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.

OTA updates

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.

OTA diagram

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:

Provisioning process

A VCON module, in order to operate, requires two crucial settings in its configuration file:

  • WiFi network name + password, wifi.sta.ssid and wifi.sta.pass. For ethernet and cellular, these are not required
  • Cloud password, dash.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:

BLE app RPC call

RPC API Reference

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
{"config":{...},"save": false}
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
{"pin": 4,"val": 0}
Set a given VCON pin to a given value
fs.list - List files on VCON filesystem
fs.read
{"file": "NAME","offset": 0}
Get file data from given offset. Returned file data is base64-encoded
fs.write
{ "file": "NAME","data": "BASE64","append": true}
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
{"req": "rst rd0"}
Send a list of SWD commands to the Host MCU. Available SWD commands:
rst - send SWD reset sequence
rd0,rd1,rd2,rd3 - read debug register
wd0,wd1,wd2 - write debug register
ra0,ra1,ra2,ra3 - read access register
wa0,wa1,wa2,wa3 - write access register
rm,ADDR - read memory at ADDR (hex)
rm,ADDR,MASK,VAL - read memory at ADDR until MASK equals to VAL
wm,ADDR,VAL - write memory
Note: 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
{"size": 128,"addr": 0}
Read Host MCU memory over SWD
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

Configuration Reference

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
$.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 SAMD
200 - STM32 with 1k flash pages
201 - STM32 with 2k flash pages
202 - STM32 with 16,16,16,16,64,128,128,128k pages
203 - STM32 with 32,32,32,32,128,256,256,256k pages
300 - 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 - disabled
1 - RPC bridge. Serial link is exchanging JSON-RPC frames, delimited by newline characters
2 - transparent UART/MQTT bridge. Serial data is uninterpeted, sent to/from mqtt.rx and mqtt.tx topics. Make sure to configure the mqtt 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 disabled
1 - BLE enabled, configurator is starged
2 - 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 disabled
1 - 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

Troubleshooting

If any quick start guide step fails, or the VCON module behaves in an unexpected way, please follow these steps:

  • Attach a serial console to the ESP32 (ERX and ETX pins)
  • Physically power cycle the device
  • Post a message to https://forum.cesanta.com/c/vcon/5 with the following:
    • Which hardware you're using
    • Which guide you are following
    • Which exactly step (or command) fails, include the command and an output
    • The full console log, including all boot messages

3rd party hardware

Arduino Nano 33 IoT

In this section, we learn:

  • How to remotely update firmware on Arduino Nano 33 IoT board,
  • How to remotely control it by switching an LED on/off,
  • How to report custom data to the cloud

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
.\windows\cli -p COMPORT -fs fs flash 2m.full.hex
Linux
./linux/cli -p COMPORT -fs fs flash 2m.full.hex
MacOS
./macos/cli -p COMPORT -fs fs flash 2m.full.hex

Step 7. A device on dash.vcon.io must become online: Online device

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: Soldering SWDIO - PA6, SWDCLK - PA7

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.

Olimex ESP32 POE

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:

  • Catch BLE advertisements from a nearby beacons and forward them to the cloud
  • Collect BLE data from the cloud in real time and print it as its arrives
  • Use cloud API to command a POE board to send a custom BLE beacon

Olimex POE BLE bridge diagram

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
.\windows\cli -p COMPORT -fs fs flash 4m.full.hex
Linux
./linux/cli -p COMPORT -fs fs flash 4m.full.hex
MacOS
./macos/cli -p COMPORT -fs fs flash 4m.full.hex

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: POE dashboard

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.

VCON + STM32 Bluepill

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
.\windows\cli -p COMPORT -fs fs flash 4m.full.hex
Linux
./linux/cli -p COMPORT -fs fs flash 4m.full.hex
MacOS
./macos/cli -p COMPORT -fs fs flash 4m.full.hex

Step 6. A device on dash.vcon.io must become online: Online device

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: PICO-D4 Kit + 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.

VCON + Arduino Nano

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
.\windows\cli -p COMPORT -fs fs flash 4m.full.hex
Linux
./linux/cli -p COMPORT -fs fs flash 4m.full.hex
MacOS
./macos/cli -p COMPORT -fs fs flash 4m.full.hex

Step 6. A device on dash.vcon.io must become online: Online device

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: PICO-D4 Kit + AVR

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.

VCON + Nano + AWS IoT

In this section, we learn how to:

  • Make Arduino Nano communicate with AWS IoT over the serial port
  • Use AWS IoT MQTT messages to turn LED on, off, and get LED status

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:

  • If we receive "0", we turn the built-in LED off,
  • If we receive "1", we turn the built-in LED on,
  • If we receive "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:

  • We'll send the UART output data (TX) to one MQTT topic,
  • We'll read another MQTT topic and write data to the UART input (RX)

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:

AWS test

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.