QuickStart

Overview

VCON is a hardware module that provides secure IoT connectivity for any microcontroller-based product. Below is the summary of the provided functionality:

  • Zero-configuration AWS IoT provisioning
  • Sending data to AWS IoT using MQTT
  • Remote device control over AWS IoT shadow
  • Update device's firmware over-the-air

Eval kit setup

The eval kit contains the following components:

  • VCON devboard
  • STM32 NUCLEO-F303K8 devboard - a Host MCU
  • breadboard, jumpers, USB cable, battery, cellullar antenna

Unpack the kit and wire components on a breadboard - see wiring details in the included instruction. Insert BICS SIM into the VCON cellular module.

Important: make sure to connect an antenna and a battery. Cellular modules consume a lot of power, thus powering the module from USB is not enough. The result should be like it is shown below: eval kit breadboard wiring

Cloud setup

Report data over MQTT

The evaluation kit uses STM32F1 Nucleo development board as a device microcontroller. The firmware for that board is developed in Keil development environment and published at https://github.com/cesanta/ccm-test-fw/blob/master/common/app.c.

The firmware uses the following snippet to send metrics over to the cloud, by triggering an MQTT.Pub() RPC function on the VCON module:

#include "mjson.h"
...
  jsonrpc_call(
    "{\"method\": \"MQTT.Pub\", \"params\": "
      "{\"topic\": %Q, \"qos\": 0, "
      "\"message\": {\"name\": %Q, \"version\": %Q, \"uptime_ms\": %u, \"blink_period_ms\": %u}}}",
    s_mqtt_topic, s_app_name, s_app_version, now, s_blink_period_ms);

Login to the AWS IoT console, choose "Test" and subscribe to the /app/topic topic. You should see reported metrics appearing periodically.

Remote control over shadow

The firmware uses the following snippet to synchronise the device state with the device shadow:

#include "mjson.h"
...
void shadow_delta_handler(struct jsonrpc_request *r) {
  int bpms = mjson_get_number(r->params, r->params_len, "$.app.blink_period_ms", -1);
  if (bpms >= 0) {
    s_blink_period_ms = bpms;
    s_next_blink = 0;
  }
}
...
  // Report shadow state
  jsonrpc_call(
    "{\"method\": \"Shadow.Report\", \"params\": "
      "{\"app\": {\"name\": %Q, \"version\": %Q, \"uptime_ms\": %u, \"blink_period_ms\": %u}}",
    s_app_name, s_app_version, now, s_blink_period_ms);

Specifically, the device fetches LED blinking frequency from the shadow.desired section. When a device boots up, it uses a default blinking interval of 100ms, then it reports its current shadow state, and AWS sends a shadow delta. A device obeys, sets the blinking frequency to what AWS says, and reports a new state back. AWS then clears the delta.

Login to AWS console, click on Manage, choose Shadow. Click on Edit to edit the shadow. Change blinking frequency value, click Save. See how device reacts.

Device firmware update

This is the most powerful feature of the VCON module. From the VCON console UI, it looks like the following (note that REST API is available, which allows to fully automate the OTA process):

If VCON module is connected to the RESET and BOOT pins of the Host microcontroller, it can reset the Host MCU into the boot loader mode and then transfer a new firmware image to it. This is possible due to the fact VCON knows programming protorols of various architectures. As a result, VCON can reflash a Host MCU at any time.

The binary firmware file of the Host MCU must be in the ".hex" format. That output formwat is used by the most development enviroments. For VCON, it is irrelevant what development environment was used to create the Host MCU firmware. For example, that could be a TI's Code Composer studio, or Arduino, or Keil IDE using Cube MX for STM32 - whatever it is. That gives an unprecedented flexibility: the product logic could be built on the Host MCU using familiar tools, and reliable OTA is provided by the VCON with no assistance from the Host MCU side.

Thus, if a device is killed by a bad update, you can always reflash it and restore back to life.

VCON module

Overview

VCON module is produced by Pycom, and its characteristics are described at https://pycom.io/product/g01-oem-module/. The module has a cellular LTE-CAT M1/NB1 radio.

Wiring

VCON pin Host MCU pin Notes
IO32 UART RX
IO34 UART TX
GND GND
VCC 3.3V VCC 3.3V
IO14 RST (reset) Optional. Used for Host MCU OTA
IO12 BOOT0 Optional. Used for Host MCU OTA

Client library

For the Host MCU, a compact C/C++ JSON-RPC library called mjson is available. mjson is an open source, MIT-licensed library located at https://github.com/cesanta/mjson. mjson makes it easy to define RPC services on the Host MCU, and call RPC services on VCON.

The usage pattern is as follows:

  1. Initialise the library by calling jsonrpc_init()
  2. Export any number of custom functions using jsonrpc_export()
  3. In the event loop, read the UART, feed each read byte into json_process_byte()
  4. At any time, you can call jsonrpc_call() to send a JSON-RPC frame to the VCON

That pattern works on any Host MCU, regardless the architecture and development environment: on Nordic nRF, TI, PIC, Atmel, STM32, NXP, or any other.

Host MCU communicates with VCON over the UART. The communication protocol is JSON-RPCv2, where JSON-RPC frames are delimited by newline characters. Per JSON-RPC standard, JSON frames with the id attribute require response, whereas frames without the `id` attribute are notifications that do not trigger any response.

For example, VCON sends a device shadow delta to the Host. This frame does not have an id attribute, thus Host does not send a response:

{"method": "Shadow.Delta", "params": {"on": true}}   // VCON -> Host

When Host receives a JSON-RPC frame, it must parse the frame and call 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, a VCON calls a custom function `Sum` that adds two numbers:

{"id": 12007, "method": "Sum", "params": [2,3]}    // VCON -> Host
{"id": 12007, "result": 5}                         // Host -> VCON

And here is the example when VCON calls a non-existent function:

{"id": 12008, "method": "This_Function_Does_Not_Exist", "params": true} // VCON -> Host
{"id": 12008, "error": {"code": -32601, "message": "method not found"}} // Host -> VCON

The communication is two-way. This is an example where Host sends an MQTT message to the cloud:

{"method": "MQTT.Pub", "params": {"topic": "t1", "message": "hello"}} // Host -> VCON

Below is an example on the Arduino platform. The Arduino sketch below shows how to create a `Sum` RPC service for adding two numbers:

#include "mjson.c" // Sketch -> Add file -> add mjson.c

// Gets called by the RPC engine to send a reply frame
static int sender(const char *frame, int frame_len, void *privdata) {
  return Serial.write(frame, frame_len);
}

// RPC handler for "Sum". Expect an array of two integers in "params"
static void sum(struct jsonrpc_request *r) {
  int a = mjson_get_number(r->params, r->params_len, "$[0]", 0);
  int b = mjson_get_number(r->params, r->params_len, "$[1]", 0);
  jsonrpc_return_success(r, "%d", a + b);
}

void setup() {
  jsonrpc_init(sender, NULL, NULL, "1.0"); // Initialise the library
  jsonrpc_export("Sum", sum, NULL); // Export "Sum" function
  Serial.begin(115200); // Setup serial port
}

void loop() {
  if (Serial.available() > 0) jsonrpc_process_byte(Serial.read());
}

The more advanced Arduino example that shows device shadow integration is part of the mjson repository, see shadow.ino.

API reference

RPC Services provided by the VCON

Shadow.Report

Update device's reported state on the cloud. Parameters: arbitraty JSON value - a string, number, array, or an object of an arbitrary complexity.

Example: update shadow reported state on the cloud: set reported.on to true:

jsonrpc_call("{\"method\":\"Shadow.Report\",\"params\":{%Q:%s}}", "on", "true");

Corresponsing JSON-RPC frame generated by the above call (note the absence of the id):

{"method": "Shadow.Report", "params": {"on": true}}

MQTT.Pub

Send MQTT message to the cloud. Parameters: a JSON object {"topic": xx, "message": xx, "qos": xx}`. Both topic and message must be strings, and qos must be a number 0 or 1.

Example - send "hello" message to the "t1" topic:

jsonrpc_call("{\"method\":\"MQTT.Pub\",\"params\":{\"topic\":%Q,\"message\":%Q,\"qos\":%d}}",
             "t1", "hello", 1);

Corresponsing JSON-RPC frame generated by the above call (note the absence of the `id`):

{"method": "MQTT.Pub", "params": {"topic": "t1", "message": "hello", "qos": 1}}

MQTT.Sub

Subscribe to the MQTT topic. Parameters: a JSON object {"topic": "..."}`. Note that topic must be a string. In order to receive MQTT messages from the cloud, Host must define MQTT.Message method, which is going to receive {"topic": "...", "message": "..."} input string. Example:

static void mqtt_cb(struct jsonrpc_request *r) {
  char topic[100] = "", message[100] = "";
  mjson_get_string(r->params, r->params_len, "$.topic", topic, sizeof(topic));
  mjson_get_string(r->params, r->params_len, "$.message", message, sizeof(message));
  printf("Got %s -> %s\n", topic, message);
}

...
jsonrpc_export("MQTT.Message", mqtt_cb, NULL);
jsonrpc_call("{\"method\":\"MQTT.Sub\",\"params\":{\"topic\":%Q}}", "t/#");

### RPC Services provided by the Host MCU #### RPC.List Return a list of all RPC services exported by the Host. Parameters: none. NOTE: This is a built-in service. Serial communication example: ```javascript {"id": 1, "method": "RPC.List"} // CCM -> Host {"id":1,"result":["Shadow.Delta","RPC.List","Sys.Info"]} // Host -> CCM ``` #### Sys.Info Return information about the Host. Parameters: none. NOTE: This is a built-in service. Serial communication example: ```javascript {"id":1,"method": "Sys.Info"} // CCM -> Host {"id":1,"result":{"fw_version":"1.0", "arch":"unknown", "fw_id":"Nov 24 2018 09:10:27", "app":"posix_device"}} // Host -> CCM ``` #### Shadow.Delta React on the device shadow change. This is a notification service, meaning that the handler function must not send any return value. Instead, it could send a `Shadow.Report` notification to the cloud, informing the cloud about the changed state. NOTE: This service should be implemented by the user if user wants the device to be shadow-controlled. Parameters: arbitraty JSON value - a string, number, array, or an object of an arbitrary complexity. Example serial communication (note the absence of the `id`): ```javascript {"method": "Shadow.Delta", "params": {"on": true}} // CCM -> Host {"method": "Shadow.Report", "params": {"on": true, "uptime": 132}} // Host -> CCM ```