# Overview VCON is a framework to connect microcontrollers online. It provides secure IoT connectivity, remote management, and OTA update. VCON consists of two parts: - a pre-built, ready-to-go firmware for ESP32 / ESP8266 - an online device management service Wire your existing MCU to the ESP32 or ESP8266 module with UART for data, BOOT/RESET for OTA firmware update. Flash the VCON firmware to the provided ESP32/ESP8266 module, configure networking and cloud login, and you can update your MCU firmware over the [mDash IoT Cloud service](https://mdash.net). You can communicate to your MCU's UART over Websocket, MQTT or REST. ![](arch-diagram.svg) Functionality included, but not limited, to: - Attached microcontroller can make outbound HTTP requests - Attached microcontroller can do MQTT pub/sub to your MQTT server - Attached microcontroller can export its UART port to a Websocket or MQTT endpoint (transparent UART ↔ Websocket or UART ↔ MQTT bridge) - Attached microcontroller can read, write, delete files on the VCON module - Attached microcontroller can create custom functions (like `GetSensorData` or `TurnOnMotor`), callable via REST from anywhere (protocol bridge) - You can OTA an attached microcontroller from anywhere (STM32 or AVR) - You can OTA ESP32 module itself from anywhere - You can set up automatic updates of the attached microcontrollers - ESP32 can catch BLE beacon advertisements and forward them to MQTT (act as a BLE gateway) - ESP32 can act as a BLE beacon Firmware OTA and transparent UART mode require no change to the Host MCU firmware, meaning that an existing product could be retrofitted in a most unintrusive way. # QuickStart In this quick start guide, we'll make a simple sketch on a classic Arduino to switch LED on/off via a simple serial command. Then we connect that Arduino to VCON and make this LED control over the Internet by using UART/MQTT bridge mode. First, using direct MQTT commands; and then, using a free phone app. Then, we add some more functionality to the Arduino sketch and update an Arduino over the Internet. All tools that are used in this tutorial are free. An Arduino is remotely controlled and updated without being aware of the remote control, so this tutorial demonstrates an unintrusive retrofit of an AVR-based device. **Step 1.** Get a classic Arduino board, such as Arduino Uno, or Nano, or Pro Mini, or other AVR-based board. Compile and flash the following sketch on your Arduino board: ```c++ 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 } } ``` This is a very simple sketch that reads characters from the serial port. If a received character is `'0'`, an LED is switched off. If a received character is `'1'`, an LED is switched on. **Step 2.** Open Serial Monitor in an Arduino IDE. Set port speed to 115200. Enter `1` - make sure an LED is turned on. Enter `0` - make sure an LED is turned off. So, now we have an Arduino board that is controlled over UART, and we'll bridge an UART to MQTT using VCON. **Step 3.** We'll use an ESP32 or ESP8266 board as a VCON module. If you use ESP8266, make sure that the board has at least 4M flash ont it. Follow [VCON setup](#esp32--esp8266-setup) instructions to register your board on mDash cloud. When you finish registration, click on a browser's back button to return back here and continue on the next step. **Step 4.** Wire Espressif board to the Arduino. | ESP32 pin | ESP8266 pin | AVR pin | | --------------- | ----------------- | --------- | | TX (IO 17) | TX (IO 1) | RX | | RX (IO 16) | RX (IO 3) | TX | | SPI MISO (IO 34) | SPI MISO (IO 12) | SPI MOSI | | SPI MOSI (IO 32) | SPI MOSI (IO 13) | SPI MISO | | SPI CLOCK (IO 33) | SPI CLOCK (IO 14) | SPI CLOCK | | IO 21 | IO 5 | RESET | Example wiring: | ESP32 Adafruit Huzzah32 to Arduino Pro Mini | ESP8266 NodeMCU to Arduino Pro Mini | | ---- | ---- | | <img src="mini-huzzah32-wiring.png" width="400"/> | <img src="mini-nodemcu-wiring.png" width="400"/> | **Step 5.** Configure VCON module. In the following command, substitute `API_KEY` by your valid mDash API key (get the API key on a Keys tab in mDash UI), and `DEVICE_ID` by an actual device ID, like `device1`. The following configuration command makes VCON module to forward its UART to the public MQTT server `broker.hivemq.com`. The configuration command differs slightly for ESP32 and ESP8266, so be careful to use the correct command. ESP32 configuration: ```sh mos --port wss://API_KEY@mdash.net/api/v2/devices/DEVICE_ID/rpc config-set mqtt.enable=true mqtt.server=broker.hivemq.com:1883 uart.uart_no=2 uart.tx_gpio=17 uart.rx_gpio=16 tu.mqtt.enable=true tu.mqtt.rx_topic=uart/rx tu.mqtt.tx_topic=uart/tx ccm.host.ota.chip_type=AVR tcp.client.remote_addr= ccm.host.ota.avr.miso_pin=34 ccm.host.ota.avr.mosi_pin=32 ccm.host.ota.avr.sclk_pin=33 ccm.host.ota.avr.rst_pin=21 ``` ESP8266 configuration: ```sh mos --port wss://API_KEY@mdash.net/api/v2/devices/DEVICE_ID/rpc config-set mqtt.enable=true mqtt.server=broker.hivemq.com:1883 uart.uart_no=0 tu.mqtt.enable=true rpc.uart.uart_no=-1 tcp.client.remote_addr= debug.stdout_uart=1 debug.stderr_uart=1 tu.mqtt.rx_topic=uart/rx tu.mqtt.tx_topic=uart/tx ccm.host.rpc.enable=false ccm.host.ota.chip_type=AVR ccm.host.ota.avr.miso_pin=12 ccm.host.ota.avr.mosi_pin=13 ccm.host.ota.avr.sclk_pin=14 ccm.host.ota.avr.rst_pin=5 ``` > If you're using ESP8266, you might need to push and hold Arduino's reset > button when you're rebooting or reflashing ESP8266. **Step 6.** Control LED over MQTT. Go to http://www.hivemq.com/demos/websocket-client/, click connect. Subscribe to topic `uart/#`. Send message `1` to topic `uart/rx`, make sure LED is turned on. Send message `0` to topic `uart/rx`, make sure LED is turned off: ![HiveMQ client MQTT control](hivemq.png) **Step 7.** Now let's enhance the firmware by adding an extra UART command that reports LED status. If we send `2` to the UART, Arduino will reply `on` or `off` back. Add an extra line to the sketch: ```c++ if (character == '2') Serial.write(digitalRead(LED_BUILTIN) ? "on\n" : "off\n"); ``` In Arduino IDE, select Sketch → Export Compiled Binary. Open a sketch folder, and make sure a new `.hex` file appears there. **Step 8.** Upload hex firmware file to the Arduino over the Internet. In mDash UI, your VCON device icon must be "green". Click on the download icon in the top right corner of a device icon. A file dialog must appear. Navigate to your new firmware .hex file, and choose it. You'll see an OTA progress bar for a second or two, then it disappears - done! ![OTA via mDash UI](ota-hex.png) > NOTE: you could also OTA using API. First, convert .hex file into a .zip > file suitable for VCON, and then OTA: > > ```sh > mos create-fw-bundle -o fw.zip --name fw --platform ccm_host host_fw:type=AVR,src=PATH_TO_YOUR_HEX > mos --port wss://API_KEY@mdash.net/api/v2/devices/DEVICE_ID/rpc ota fw.zip > ``` **Step 9.** Make sure new command works. In the HiveMQ dashboard, send message `2` to the `uart/rx` topic. Make sure you receive a message `on` or `off`, depending on the LED status. **Step 10.** Configure phone app for a visual LED control. Install https://www.iot-onoff.com/ free app on your phone. Configure MQTT broker to be `broker.hivemq.com`, port 1883, no SSL/TLS. Add a dashboard, add a toggle button. Configure Publishing, to topic `uart/rx`, use no device prefix. You'll be able to use a toggle button on your phone to toggle an LED on/off: <img src="onoff.png" alt="On OFF LED control" class="w-50 border" /> Congratulations! You've made your Arduino remotely controllable and updatable. # ESP32 / ESP8266 setup In this section, we describe how to flash and configure an Espressif ESP32 or ESP8266 module. **Step 1.** Get an ESP32 or ESP8266 development board **Step 2.** Connect your board to your workstation via USB **Step 3.** Download and install `mos` tool in order to flash and configure your board: | OS | Instructions | | ----------- | ----------- | | Windows | Create `C:\mos` folder. Right-click on this [mos.exe](https://mdash.net/downloads/mos-release/win/mos.exe) link, choose "Save link as", save `mos.exe` to the `C:\mos` folder. Start command prompt, enter the following commands: <pre class="language-bash">cd c:/mos<br/>mos help</pre> | | MacOS,Linux | Start terminal and enter the following commands: <pre class="language-bash">curl -fsSL https://mdash.net/downloads/mos/install.sh \| /bin/bash<br/>mos help</pre>| **Step 4.** Flash your board (change `SERIAL_PORT` to the board's serial port). If you use ESP32, execute: ```bash mos flash https://mdash.net/downloads/ccm/ccm.esp32.zip --port SERIAL_PORT ``` If you use ESP8266, execute: ```bash mos flash https://mdash.net/downloads/ccm/ccm.esp8266.zip --port SERIAL_PORT ``` > To avoid specifying `--port SERIAL_PORT` parameter for every `mos` command, > set up an environment variable `MOS_PORT`. Therefore in further `mos` commands, > the `--port SERIAL_PORT` is omitted. However if you specify `--port`, then > it overrides the `MOS_PORT` environment variable value: ```bash export MOS_PORT=SERIAL_PORT ``` **Step 5.** Configure WiFi: ```bash mos wifi WIFI_NETWORK WIFI_PASSWORD ``` **Step 6.** Login to https://mdash.net, click on "add device" to register new device, and click on the "lock" icon to copy device password to the clipboard: <img src="copydevtoken.png" class="w-50" /> **Step 7.** Provision device to mdash.net: ```bash mos config-set dash.token=COPIED_DEVICE_PASSWORD ``` **Step 7.** When done, a device on mDash dashboard should turn "green", which means it is online and accessible. Notice the device ID in the top left corner. That device ID will be used in the REST API calls to this device: <img src="device-online.png" class="w-50" /> > Note the `mos config-set dash.token=COPIED_DEVICE_PASSWORD` above. The > `mos config-set param1=XXX param2=YYY ...` can be used to set up any number > of VCON tunable parameteres - and there are hundreds of them! > You can see all tunable parameters by running `mos config-get` command. > One of the most useful tunables are debug tunables, which allow to increase > the debug level of the VCON firmware. Below is a typical sequence, which > increases the debug level and starts a serial monitor: > > ```sh > mos config-set debug.level=3 > mos console > ``` # Firmware OTA ESP32 chip running VCON firmware can update attached microcontrollers remotely. You can securely pass a new firmware over the VCON cloud, then ESP32 reflashes an attached microcontroller. It works because ESP32 knows respective flashing protocols. ![OTA diagram](ota.svg) Network communication is over TLS, encrypted and authenticated. VCON firmware currently supports all lines of STM32 microcontrollers, and all AVR microcontrollers. Below are examples for particular boards, in order to demonstrate the functionality and provide a handy reference point. ## STM32 In this section, we learn how to remotely update STM32F103C8T6 Blue Pill board. The same approach would work for any other STM32 board. **Step 1.** Setup ESP32 by following the [ESP setup section](#esp32--esp8266-setup) **Step 2.** Wire STM32 to the ESP32. | ESP32 pin | BluePill pin | | --------- | -------------- | | IO 34 | PA9 (UART RX) | | IO 32 | PA10 (UART TX) | | IO 14 | RST (reset) | | IO 12 | BOOT0 | Below is an example wiring for ESP32 Devkit-C development board and STM32 BluePill: <img src="bluepill-wiring.png" alt="Wiring for ESP32 and STM32 Blue Pill" class="w-75" /> **Step 3.** Install ARM GCC toolchain: | OS | Installation command | | ------ | ----------------------------------------------------------------------------------------- | | Ubuntu | `sudo apt-get install gcc-arm-none-eabi binutils-arm-none-eabi gdb-arm-none-eabi openocd` | | MacOS | `brew tap osx-cross/arm ; brew install arm-gcc-bin` | **Step 4.** Clone [firmware repo](https://github.com/cesanta/stm32-bluepill-ccm) and build firmware file `bluepill.zip`: ```bash git clone https://github.com/cesanta/stm32-bluepill-ccm cd stm32-bluepill-ccm make zip ``` **Step 5.** Copy mDash management key Login to https://mdash.net, click on Keys tab, click on a API key link to copy it to the clipboard **Step 6.** Reflash BluePill board remotely Now we will flash the BluePill board over the network. That works for any STM32 chip, not just that particular STM32F1. Notice that the BluePill firmware does not have any OTA functionality. VCON module uses STM32's built-in ROM boot loader present on all STM32s to reflash the chip. Replace `API_KEY` with the copied key. If your device ID is not `device1`, replace `device1` with a correct device ID: ```bash curl -i -F file=@bluepill.zip https://mdash.net/api/v2/devices/device1/ota?access_token=API_KEY ``` The BluePill board must start blinking an LED. > Note: you can upload a new firmware over mDash GUI. > In mDash UI, your VCON device icon must be "green". > Click on the download icon in the top right corner of a green device card. > A file dialog must appear. Navigate to your new > firmware bluepill.hex file, and choose it. You'll see an OTA progress bar for a second > or two, then it disappears - done! **Step 6.** Remote control the BluePill board BluePill firmware implements remote control using the Protocol Bridge method - see [Protocol Bridge](#protocol-bridge) for details. That means that we can register any number of custom functions on our microcontroller and call them over the network. List RPC functions exported by the BluePill board: ```bash curl https://mdash.net/api/v2/devices/device1/rpc/Host.RPC.List?access_token=API_KEY ``` Output: ```javascript [ "SetCycles", "RPC.List" ] ``` Change blinking interval. After this call is executed, BluePill starts to blink faster: ```bash curl -d '{"period": 50000}' -H 'Content-Type: application/json' https://mdash.net/api/v2/devices/device1/rpc/Host.SetCycles?access_token=API_KEY ``` See `main.c` file in the firmware source code - how trivial is to add custom management functions using `mjson` API. This demonstrates how any STM32 device can get remote management and OTA using VCON. ## AVR (Arduino) In this section, we learn how to remotely update Arduino Nano board. The same approach would work for any other AVR board. **Step 1.** Setup ESP32 by following the [ESP setup section](#esp32--esp8266-setup) **Step 2.** Wire Nano to the ESP32. | ESP32 pin | AVR pin | | --------- | -------------- | | SPI MISO (IO 19) | SPI MISO | | SPI MOSI (IO 23) | SPI MOSI | | SPI CLOCK (IO 18) | SPI CLOCK | | RESET (IO 21) | RESET | Below is an example wiring for the ESP32 PICO-D Kit board and Arduino Nano 3.3v: ![ESP32 PicoD Kit - Arduino Nano wiring](nano-ota-wiring.png) **Step 4.** Configure ESP32 for AVR OTA: ```bash mos config-set ccm.host.ota.chip_type=AVR spi.enable=true ccm.host.ota.avr.miso_pin=19 ccm.host.ota.avr.mosi_pin=23 ccm.host.ota.avr.sclk_pin=18 ccm.host.ota.avr.rst_pin=21 ``` **Step 3.** Create new Arduino blink sketch in Arduino IDE: ```c++ void setup() { pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); delay(500); } ``` **Step 4.** In Arduino IDE, choose Arduino Nano board. Choose Sketch → Export Compiled Binary. This will produce a .hex binary firmware file in the sketch folder **Step 5.** Pack Arduino .hex firmware into a .zip, suitable for VCON (change FIRMWARE.HEX to your .hex firmware path): ```bash mos create-fw-bundle -o fw.zip --name fw --platform ccm_host host_fw:type=AVR,src=FIRMWARE.HEX ``` **Step 6.** Copy mDash management key Login to https://mdash.net, click on Keys tab, click on a API key link to copy it to the clipboard **Step 7.** Flash Arduino remotely. Replace `API_KEY` with the copied key. If your device ID is not `device1`, replace `device1` with a correct device ID: ```bash curl -i -F file=@fw.zip https://mdash.net/api/v2/devices/device1/ota?access_token=API_KEY ``` Your Arduino Nano must start blinking. Congratulations - you've updated an AVR board over the air. ## Automatic OTA VCON firmware could be configured to update automatically by polling a specified URL periodically. If a firmware .zip bundle on a specified URL changes, ESP32 applies it. ```bash mos config-set update.url=http://YOUR_SITE/YOUR_FIRMWARE.ZIP update.interval=3600 ``` ## Config reference ```javascript { "host": { "ota": { "avr": { // OTA settings for AVRs "miso_pin": 19, // MISO pin "mosi_pin": 23, // MOSI pin "rst_act": 0, // Reset pin action: 0->1 or 1->0 to reset "rst_pin": 21, // Reset pin "sclk_pin": 18, // SPI clock pin "spi_freq": 1000000 // SPI frequency, Herz }, "chip_type": "STM32", // Host MCU architecture. AVR or STM32 "enable": true, // Enable Host OTA "staging_dir": "/data", // Where Host firmware is stored "stm32": { "boot0_act": 1, // Boot0 action: 0->1 or 1->0 to enter boot loader "boot0_pin": 12, // Boot0 pin "boot1_act": -1, // Boot1 action "boot1_pin": -1, // Boot1 pin "intf_type": "UART", // OTA interface type "rst_act": 0, // Reset pin action. 0->1 or 1-> to reset "rst_pin": 14, // Reset pin "uart": { "baud_rate": 115200, // Programming baud rate "rx_pin": 34, // UART RX pin "tx_pin": 32, // UART TX pin "unit_no": 1 // UART unit number on ESP32 } } } } } ``` # UART Bridge The "UART bridge" communication mode is the simplest connection method. It implements a transparent bridge between an attached microcontroller's UART and an Internet server (MQTT or Websocket). Everything that your device sends to the UART, ESP32 forwards to the Internet server (MQTT or Websocket). Likewise, everything that is sent to the ESP32, gateway forwards to the UART. UART gateway does not implement any logic whilst forwarding data to and from the UART. Data is transferred as-is, no modification happens in any direction. Message boundaries are determined by timing. ## UART / Websocket In this mode, UART is exposed as a Websocket endpoint: ![UART Websocket bridge](uart-websocket.svg) In a guide below, we'll use Arduino board which runs a simple serial echo sketch. We'll expose that UART echo server to a Websocket URL, connect to that URL using a command line utility, send some data and receive an echo back. **Step 1.** Setup ESP32 by following the [ESP setup section](#esp32--esp8266-setup) **Step 2.** Get an Arduino board, build and flash the following sketch on it: ```c++ void setup() { Serial.begin(115200); } void loop() { if (Serial.available() > 0) Serial.write(Serial.read()); } ``` **Step 3.** Wire an Arduino to the ESP32 in a following way: | ESP32 pin | Arduino pin | | --------- | -------------- | | IO 16 | RX | | IO 17 | TX | **Step 4.** Configure ESP32's UART/Websocket bridge. Substitute TOKEN with a device password (ESP32 setup Step 6): ```bash mos config-set tcp.client.ws.enable=true tcp.client.ws.headers="Authorization: Bearer TOKEN" mos config-set uart.uart_no=2 uart.tx_gpio=17 uart.rx_gpio=16 mos console ``` After this, ESP32 makes an additional data connection to mdash.net and opens a Websocket URL for this particular device. The last command starts a debug console, where you can see ESP32's debug messages printed. You can stop it by pressing Ctrl-C. **Step 4.** Download and install `websocat` utility from https://github.com/vi/websocat/releases **Step 5.** Connect to our device's Websocket (substitute `API_KEY` and `DEVICE_ID`): ```bash websocat -H='Authorization: Bearer API_KEY' \ --origin wss://mdash.net/api/v2/devices/DEVICE_ID/uart \ wss://mdash.net/api/v2/devices/DEVICE_ID/uart ``` Type something and press enter - you'll see an echo back. Your message got transferred to the ESP32, ESP32 sent it to the Arduino's UART, Arduino echoed that back, and ESP32 forwarded the reply to us. An attached microcontroller may implement something far more sophisticated - for example, it can be driven by the commands from a UART port. In this case, VCON would allow to drive an attached microcontroller using a simple Websocket client, for example from a web page or a mobile app. ## UART / MQTT The UART / MQTT transparent bridge mode is similar to Websocket, but the transport protocol is MQTT. MQTT server parameters, like host/port, authentication, TLS CA certs, and TX / RX topics, should be configured: ![UART MQTT bridge](uart-mqtt.svg) The following configuration commands configure VCON for the UART/MQTT bridge mode: ```bash mos config-set mqtt.enable=true mqtt.server=broker.hivemq.com:1883 mos config-set uart.uart_no=2 uart.tx_gpio=17 uart.rx_gpio=16 mos config-set tu.mqtt.enable=true tu.mqtt.rx_topic=uart/rx tu.mqtt.tx_topic=uart/tx ``` In comparison to a Websocket bridge, MQTT bridge is more "wide-open". As soon as some client can connect to your MQTT server, it can "see" all your devices, unless MQTT server provides some sort of ACL policy mechanism to restrict access. Thus, if you want to give your end-customers an ability to drive their devices, Websocket bridge might be a better option because it provides more fine-grained authentication by default. ## Config reference ```javascript { "uart": { "baud_rate": 115200, // Baud rate of the attached microcontroller "cts_gpio": -1, "rts_gpio": -1, "rx_buf_size": 1460, // Recevice buffer size "rx_fc_ena": false, // Enable RX flow control "rx_fifo_alarm": 20, "rx_fifo_fc_thresh": 110, "rx_fifo_full_thresh": 100, "rx_gpio": -1, // RX GPIO number, -1 to disable "rx_linger_micros": 20, // Microseconds to wait while reading "rx_throttle_when_no_net": true, "status_interval_ms": 1000, // Debug log period, milliseconds "tx_buf_size": 2048, // TX buffer size "tx_fc_ena": false, // Enable TX flow control "tx_fifo_empty_thresh": 20, "tx_gpio": -1, // TX GPIO number, -1 to disable "uart_no": 1 // UART unit number on ESP32 } } ``` # Protocol Bridge Protocol bridge is an advanced and flexible communication method. It turns an attached microcontroller into a globally available online HTTPS/REST server. You can define an arbitrary set of custom functions on your microcontroller, and call them remotely via REST, with or without parameters. Protocol bridge mode works like this: Host MCU and ESP32 are talking JSON-RPC protocol over the UART. Host MCU is a JSON-RPC server and client at the same time. Host can register RPC functions which could be called via cloud REST API. And, Host can call VCON's functions that are exported by ESP32 to read/write/delete files, send HTTP(S) request, and publish/subscribe to MQTT. ![Protocol Bridge diagram](protocol-bridge.svg) Host MCU can implement JSON-RPC by using a compact, single-header, MIT-licensed C/C++ library https://github.com/cesanta/mjson. 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 what have you. See [JSON-RPC reference](#json-rpc-reference) section for a detailed description. ## Custom functions As mentioned above, Host MCU can register custom functions which are exposed as a cloud REST endpoints. Below is an example how Arduino can use mjson library to register a custom function `Sum` that adds two numbers, and called over the cloud. **Step 1.** Setup ESP32 by following the [ESP setup section](#esp32--esp8266-setup) **Step 2.** Flash the following sketch to the Arduino board. Create a new sketch, copy/paste the code below. Save sketch. Download [mjson.h](https://raw.githubusercontent.com/cesanta/mjson/master/mjson.h) file and then add it to your sketch via menu Sketch / Add File. Compile and upload to your board. ```c++ #include "mjson.h" // Sketch -> Add file -> add mjson.h // 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". 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 } void loop() { if (Serial.available() > 0) jsonrpc_process_byte(Serial.read(), sender, NULL); } ``` **Step 3.** Test JSON-RPC server using Arduino Serial Monitor. Start Arduino Serial Monitor, choose port speed 115200, type `{"id": 1, "method": "Sum", "params": [2,3]}` and hit enter. You should see an answer frame: ![VCON arduino sketch RPC example](arduino-sketch-example.png) **Step 4.** Close Serial Monitor. Wire an Arduino to the ESP32 over a serial link: | ESP32 pin | Arduino pin | | --------- | -------------- | | IO 16 | RX | | IO 17 | TX | **Step 5.** Configure ESP32 to use protocol bridge mode: ```bash mos config-set ccm.host.rpc.uart.tx_pin=17 ccm.host.rpc.uart.rx_pin=16 ``` **Step 5.** Call Arduino's `RPC.List` function, which is a built-in function registered by the mjson libary. It lists all registered functions: ```bash curl http://mdash.net/api/v2/devices/DEVICE_ID/rpc/Host.RPC.List?access_token=API_KEY ``` > Note the `Host.` prefix in the `Host.RPC.List` method name. We need to > specify `Host.` in order to tell ESP32 to forward the call to the attached > microcontroller. If we omit `Host.` prefix, ESP32 would treat that request as > coming to itself. For example, callist `RPC.List` would return RPC functions > registered on ESP32, not on an attached microcontroller. **Step 5.** Call a custom `Sum` function, passing parameters `a` and `b`: ```bash curl -H 'Content-Type: application/json' -d '{"a":1.23,"b":4.56}' http://mdash.net/api/v2/devices/DEVICE_ID/rpc/Host.Sum?access_token=API_KEY ``` This way, it is possible to register any number of custom functions that read sensor data, control actuators, and so on. ## MQTT Pub/Sub MQTT bridge allows attached microcontroller to publish MQTT messages to arbitrary MQTT server, and subscribe to MQTT topics. In the example below, we configure ESP32 to connect to broker.hivemq.com MQTT server. Unlike transparent UART bridge, protocol bridge requires to change attached microcontroller's firmware. **Step 1.** Configure your MQTT server. We'll use public HiveMQ MQTT server: ```bash mos config-set mqtt.enable=true mqtt.server=broker.hivemq.com:1883 --port SERIAL_PORT ``` > Note: you can use your own private MQTT server, > or one of the big cloud providers. > Below is the list of setup commands for different cloud services: ```bash mos aws-iot-setup --aws-iot-region REGION # AWS IoT mos watson-iot-setup # IBM Watson mos azure-iot-setup --azure-hub-name YOUR_AZURE_HUB_NAME # Microsoft Azure mos gcp-iot-setup --gcp-project PROJECT_ID --gcp-region REGION --gcp-registry REGISTRY # Google IoT ``` **Step 2.** Setup Host MCU. In this Quick Start, we use Arduino Nano. Connect Arduino Nano, create a new sketch, copy/paste sketch code from the [mjson MQTT example](https://github.com/cesanta/mjson/blob/master/ArduinoMqttExample/ArduinoMqttExample.ino), and flash it to your Arduino: > Note: if your MCU is not Arduino but something else, like STM32, NXP, > Texas Instruments, or any other - just copy the respective mjson parts > into your MCU's firmware **Step 3.** Wire Arduino Nano to the ESP32 via serial: | ESP32 pin | Host MCU pin | | --------- | ------------ | | IO 32 | UART RX | | IO 34 | UART TX | ![ESP32 wiring example and Arduino Nano](nano-wiring.png) **Step 4.** Connect to the MQTT server using Web interface at http://www.hivemq.com/demos/websocket-client - In Web UI, Subscribe to the `ccm/#` topic - Notice periodic messages to the topic `ccm/data` from the Arduino - In Web UI, publish messages `0` or `1` to topic `ccm/led`. Notice how and LED on Arduino switches on or off <img src="mqtt-ui.png" class="w-75" alt="MQTT UI" /> Congratulations! You have implemented MQTT remote control and data publishing on Arduino Nano using ESP32, two wires, and VCON firmware. ## HTTP Request ## mDash Notification It is possible to send notification to mdash.net service. Notifications could be caught via Websocket. Some notifications are handled by mDash automatically. Learn more about notifications at https://mdash.net/docs/#notifications. Learn more about automatically handled notifications at https://mdash.net/docs/#mdashnotify An Arduino example below implements periodic data report and saving data into the built-in mDash database: ```c++ #include "mjson.h" #define REPORT_PERIOD_MS 3000 static int uart_sender(const char *frame, int frame_len, void *privdata) { return Serial.write(frame, frame_len); } void setup() { Serial.begin(115200); jsonrpc_init(NULL, NULL); } void loop() { if (Serial.available() > 0) jsonrpc_process_byte(Serial.read(), uart_sender, NULL); // Send data to mDash periodically. See https://mdash.net/docs/#mdashnotify static unsigned long wakeup; unsigned long now = millis(); if (wakeup < now || wakeup > now + REPORT_PERIOD_MS) { wakeup = now + REPORT_PERIOD_MS; jsonrpc_call(uart_sender, NULL, "{%Q:%Q,%Q:{%Q:%Q,%Q:%lu}}", "method", "Dash.Notify", "params", "name", "DB.Save", "data", now); } delay(1); } ``` ## Config reference ```javascript { "host": { "rpc": { // Host RPC channel settings "enable": true, // Enable RPC channel to host "intf_type": "UART", // Type of the host communication interface. "uart": { // RPC UART communication settings "baud_rate": 115200, // UART baud rate used for RPC "rx_pin": 34, // UART RX pin (connected to host TX) "tx_pin": 32, // UART TX pin (connected to host RX) "fc_type": 0, // Flow control: 0 - none, 1 - CTS/RTS, 2 - XON/XOFF "cts_pin": -1, // UART CTS pin (connected to host RTS) "rts_pin": -1 // UART RTS pin (connected to host CTS) } } } } ``` # BLE bridge BLE bridge mode works only on ESP32! In the BLE bridge mode, ESP32 can catch BLE beacon advertisements and forward them to the MQTT server of your choice: ![BLE MQTT bridge](ble-mqtt.svg) ## Gateway mode **Step 1.** Setup ESP32 by following the [ESP setup section](#esp32--esp8266-setup) **Step 2.** Configure ESP32 to enable BLE/MQTT mode. The MQTT server we'll use in this example is broker.hivemq.com:1883: ```sh mos config-set bt.enable=true bt.gw.enable=true mqtt.enable=true mqtt.server=broker.hivemq.com:1883 ``` **Step 3.** Go to http://www.hivemq.com/demos/websocket-client/ , subscribe to topic `ble-bridge`, and see published BLE beacon messages. ## Config Reference ```sh mos config-get bt # See all BLE related configuration mos config-set bt.enable=false # Example - how to change some BLE setting ``` Below is the annotated output of the BLE related config: ```javascript // "mos config-get bt" command output { "adv_enable": false, // Advertise BLE services "allow_pairing": true, // Allow pairing "beacon": { "data": "", // Hex-encoded beacon data "enable": false, // Enable beacon mode "interval_ms": 1000 // Beacon interval, milliseconds }, "dev_name": "ccm-??????", // Device name. ?? are substituted by MAC addr "enable": false, // Enable BLE functionality "gatt_mtu": 500, // GATT MTU - maximum packet size "gatts": { "min_sec_level": 0, "require_pairing": false }, "gw": { "active_scan": false, "cleanup_timeout_sec": 300, // If a beacon is silent, clean it "enable": false, // Enable BLE gateway mode "mac_filter": "", // Filter beacons by MAC address "name_filter": "", // Filter beacons by name "pub_topic": "bb", // Publishing topic "qos": 0, // Publishing QoS "report_interval_ms": 1000, // Report interval. If 0, report immediately. // If > 0, report at interval only if the adv // data has been changed "sub_topic": "bb_flush" // Topic that triggers a full resend }, "keep_enabled": true, // Keep BLE enabled after WiFi connect "max_paired_devices": -1, // Maximum paired devices "random_address": false, // Generate random BLE MAC address "scan_rsp_data_hex": "" // Hex-encoded active scan response } ``` # Factory reset VCON provides a way to factory-reset the module. Factory reset clears WiFi configuration, and opens an AP after boot for further WiFi provisioning. Factory reset is triggered by the "long button press", i.e. setting the factory reset GPIO pin low and holding it in the low state for some time, defined by the timeout value. The configuration for the factory reset is defined in the `mos config-set provision`: ```javascript // Annotated output of the "mos config-get provision" command: { "btn": { "hold_ms": 0, // Milliseconds to hold the reset button "pin": 0, // GPIO pin "pull_up": true }, "led": { // LED status indicator "active_high": true, // If GPIO is high, LED is on "pin": -1 // Set to valid LED GPIO to enable LED status }, "max_state": 3, // Provisioning state "stable_state": 3, "timeout": 300 // Timeout in milliseconds } ``` Example to enable LED indicator for the ESP8266 NodeMCU dev board: ``` mos config-set provision.led.pin=2 provision.led.active_high=false ``` # Changing configuration The configuration settings outlined below could be changed via several methods: 1. **mos command line tool**: `mos config-set debug.level=3` 2. **Host MCU RPC call**: `jsonrpc_call("Config.Set", "{%Q:true,%Q:true,%Q:{%Q:{%Q:%d}}}", "save", "reboot", "config", "debug", "level", 3);` 3. **mDash UI**: Login to mdash.net, click on device settings, edit & save config 4. **REST call**: When ESP32 is not yet configured, it opens a WiFi network CCM_XXXXXX. Join this network and issue a REST call equivalent to: `curl -H 'Content-Type: application/json' -d '{"save":true,"reboot":true,"config":{"debug":{"level":3}}}'` For example, when an unprovisioned VCON-enabled device is shipped to a customer, a mobile app can configure its WiFi settings by joining a WiFi network CCM_XXXXX and making a REST call equivalent to the following: ```bash curl -H 'Content-Type: application/json' \ -d '{"save":true,"reboot":true,"config":{"wifi":{"sta":{"enable":true,"ssid":"NAME","pass":"PASS"}}}}' \ ``` In order to see all available configuration settings, run: ```bash mos config-get ``` The command above assumes that your Espressif board is connected over the UART, and `MOS_PORT` environment variable is set to the respective serial device. It is possible, however, to talk to a VCON device remotely via mDash: ```bash mos --port wss://API_KEY@mdash.net/api/v2/devices/DEVICE_ID/rpc COMMAND ``` In the command above, substitute `API_KEY`, `DEVICE_ID` and `COMMAND`. For example, you can view all configuration settings of `device1` using this command: ```bash mos --port wss://API_KEY@mdash.net/api/v2/devices/device1/rpc config-get ``` # Debugging If you need to see a debug output, consider UDP logging: ```bash mos config-set debug.udp_log_addr=IP_ADDR:PORT ``` To catch UDP debug logs, you can use netcat utility in UDP listening mode: ```bash nc -l -u PORT ``` # Security An ESP32 running VCON firmware provides strong security: - First, all the cloud communication uses industry-standard TLS protocol - Second, VCON allows to encrypt ESP32 flash, securing both code and data. An attached microcontroller can store files on VCON module in a secure manner. - Third, VCON provides a built-in support for the ATECC608A crypto chip ## Flash encryption By default, VCON firmware is flashed in plain-text form. It is easy to verify that fact using a `mos flash-read` command: ```sh mos flash-read --arch esp32 0x190000 2000 - ``` The `flash-read` command dumps the flash memory into a file, and the output can show that the contents is not encrypted. Therefore, sensitive information like TLS private keys could be easily stolen from the flash. In this case, we see a part of the device's file system, not encrypted. In order to enable flash encryption, use `esp32-gen-key` command. It enables flash encryption for the next flashing, and sets efuses: ```sh mos -X esp32-gen-key flash_encryption_key fe.key --esp32-enable-flash-encryption --dry-run=false mos flash esp32 --esp32-encryption-key-file fe.key ``` Beware: that command is irreversible - once flash encryption is enabled, you cannot go back. Note the extra flag `--esp32-encryption-key-file fe.key` for the `flash` command. From now on, a key file is required to re-flash the device. If the key file is lost, the module can't be reflashed. After flash encryption is enabled, the very first boot performs an encryption, which takes a while - up to a minute. Subsequent boots will be normal, not doing any encryption. Once the flash is encrypted, one can verify it using `flash-read` command to ensure there no plain-text parts are present: ```sh mos flash-read --arch esp32 0x190000 2000 - ``` It is recommended to use a separate key for each device. The efuses must be set to enable encryption (this is done automatically by `esp32-gen-key`) and can be verified by running: ```sh mos esp32-efuse-get ``` A device with encryption enabled should show `flash_crypt_cnt : 0x01` ## ATECC608A crypto chip If an IoT module provides no built-in flash protection mechanism, anyone with a physical access to the device can read the whole flash, including any sensitive information like TLS private keys. Crypto chips are designed to mitigate that. Their main function is provide storage for private keys, which cannot be read. Private keys are stored inside the crypto chip, and all the crypto operations that require private key, are offloaded to the crypto chip which performs the operation and gives the result back. [ATECC608A crypto chip](https://www.microchip.com/wwwproducts/en/ATECC608A) is designed with additional hardware protection mechanisms to make key extraction difficult. VCON firmware has a built-in, transparent support for ATECC608A crypto element. There is no extra configuration required. As soon as VCON firmware detects ATECC608A presence at startup, VCON automatically initialises and uses it. Some hardware modules, specifically [ESP32-WROOM-32SE](https://www.espressif.com/en/products/hardware/esp-wroom-32/overview) by Espressif Systems has ATECC608A on-board. Thus we highly recommend using that module. # JSON-RPC reference JSON-RPC frames that are flowing over the UART line in a Protocol Bridge mode, are delimited by a newline characters. The communication between an attached microcontroller and ESP32 is bi-directional, meaning that Host MCU can call VCON's functions, and VCON can call Host MCU's functions. Below is an example how Host MCU asks VCON to publish MQTT message: ![CCM RPC example](rpc-example.png) 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 MCU. This frame does not have an `id` attribute, thus Host does not send a response: ```javascript {"method": "Shadow.Delta", "params": {"on": true}} // 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: ```javascript {"id": 12, "method": "Sum", "params": [2,3]} // VCON -> Host {"id": 12, "result": 5} // Host -> VCON ``` And here is the example when VCON calls a non-existent function: ```javascript {"id": 13, "method": "This_Function_Does_Not_Exist", "params": true} // VCON -> Host {"id": 13, "error": {"code": -32601, "message": "method not found"}} // Host -> VCON ``` ## VCON RPC functions ### 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 ```c 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`): ```javascript {"method": "Shadow.Report", "params": {"on": true}} ``` ### MQTT.Pub Send MQTT message to the cloud. Parameters: a JSON object `{"topic": xx, "message": xx, "qos": xx}`. `topic` and `message` must be strings, and `qos` must be a number 0 or 1. Example - send "hello" message to the "t1" topic: ```c jsonrpc_call("{\"method\":\"MQTT.Pub\",\"params\":{%Q:%Q,%Q:%Q,%Q:%d}}", "topic", "t1", "message", "hello", "qos", 1); ``` Corresponsing JSON-RPC frame generated by the above call (note the absence of the `id`): ```javascript {"method": "MQTT.Pub", "params": {"topic": "t1", "message": "hello", "qos": 1}} ``` ### MQTT.Sub Subscribe to the MQTT topic. Parameters: a JSON object `{"topic": "..."}`. `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": "..."}`. Example: ```c 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/#"); ``` ## Host RPC functions ### RPC.List Return a list of all RPC services exported by the Host. Parameters: none. > NOTE: This is a built-in function provided my the mjson library. > All other functions must be implemented by you! Serial communication example: ```javascript {"id": 1, "method": "RPC.List"} // VCON -> Host {"id":1,"result":["Shadow.Delta","RPC.List","Sys.Info"]} // Host -> VCON ``` ### 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}} // VCON -> Host {"method": "Shadow.Report", "params": {"on": true, "uptime": 132}} // Host -> VCON ``` # Contact Need our assistance with VCON integration, or require specific features? [Contact us](/contact.html) today!