Backend

Overview

Freshen is an IoT backend with focus on security and device management. Devices communicate JSON-RPC over secure Websocket connections: each device is a JSON-RPC server which can register any number of custom management functions. Freshen backend bridges JSON-RPC to RESTful, making your devices look like a collection of RESTful servers. For example, if a device exports a reboot function, you can call a /api/v1/devices/:id/rpc/reboot endpoint to reboot your device.

Users must authenticate with server prior to registering devices. A registered device gets a unique ID and access token. Then, devices can connect to Freshen using their access token.

Authenticated users see only traffic from their devices, meaning that device traffic is isolated - as if each user had a private backend. Devices are totally separated from each other and cannot directly observe other traffic, which provides good security. Compare that to MQTT where any device can subscribe to the "#" wildcard topic and see everything. MQTT services resort to non-standard ways of disallowing devices to subscribe and publish to arbitrary topics. That is completely unnecessary with Freshen.

In order to connect devices to Freshen, any JSON-RPC over Websocket client would work. Freshen client library (documented below) is such a client that provides some built-in functionality: file management and over-the-air updates.

Local Installation

Please contact us if you'd like to run Freshen server locally.

To start Freshen server locally, install Docker and run the following command:

docker run -v .:/data:rw freshen

In the current directory, Freshen will create a dash.db Sqlite3 database file with user and device information. The Web UI interface opens on port 8001.

Device Simulator

To start a device simulator, install mbedTLS library, download simulator.c and freshen.h, compile and run - see snippet below. NOTE: it'll ask you for a device ID and access token, so login to https://dash.freshen.cc and register a device first. Effectively, this device simulator is a UNIX/Linux example. You can use it to remotely manage devices like Raspberry PI, BeagleBone, etc.

(cd /tmp \
&& curl -O https://freshen.cc/downloads/freshen/simulator.c \
&& curl -O https://freshen.cc/downloads/freshen/freshen.h \
&& cc simulator.c -lmbedtls -lmbedcrypto -lmbedx509 -o simulator \
&& ./simulator)

REST API Overview

A diagram below shows data flow for the RESTful requests that are handled by devices. Freshen accepts a REST request and transforms it into the JSON-RPC request. When a device replies, Freshen forwards it back to the REST client:

Note that JSON-RPC v1 is used. Function argument is only one - an arbitrary string. The result is also an arbitrary string. Developer decides on how to format the input and output: Freshen simply forwards the input to the device, and delivers the result.

In order to call RESTful API via the curl utility, login to server, create an API key and run:

curl -H "Authorization: Bearer API_KEY" https://dash.freshen.cc/api/v1/devices

Notifications

Devices may not only serve incoming JSON-RPC requests, they also can send notification to the Freshen backend. Technically this is done by sending a JSON-RPC frame with no "id". With Freshen client library, this is done by calling freshen_core_notify(ctx, name, data) function.

Freshen backend provides a /api/v1/notify Websocket endpoint where is sends notifications from all devices. Also, backend generates some notifications on its own, like device online/offline notifications. In order to connect to that enpoint, generate an API key and create a Websocket connection to wss://dash.freshen.cc/api/v1/notify with the authentication HTTP header Authorization: Bearer YOUR_API_KEY.

REST API Reference

Method Endpoint Input Description
GET /devices Get list of all registered devices
POST /devices {"name": "my_device"} Register a new device
PUT /devices/:id {"name": "my_device"} Update a device
DELETE /devices/:id Delete device
POST /devices/:id/rpc/:func arbitrary string Call management function
POST /devices/:id/ota binary firmware Update firmware: curl -v -F file=@fw.bin URL
GET /keys Get list of API keys
POST /keys Create a new API key
DELETE /keys/:id Delete an API key

Client Library

Overview

Freshen client library is a single-header, Apache-2.0 licensed file:

Download fresheh.h

It works well with practically any framework: on Linux workstation, single-board computers like Raspberry PI or BeagleBone, and microcontrollers like ESP32, ESP8266, etc. The feature support matrix is below:

Architecture Framework OTA Function Call File Management SSL/TLS
UNIX/Linux *
ESP32 ESP-IDF , Arduino
ESP8266 Mongoose OS
* mbedOS

Arduino ESP32 Example

Create a new sketch, paste the following code into it. Note that this sketch exports a custom management function echo, which simply returns an input string. You can add as many custom management functions as you want, and let them do something more useful - for example, read sensor data or control hardware peripherals.

Important: don't forget to change WIFI_NETWORK, WIFI_PASSWORD and DEVICE_ACCESS_TOKEN.

#include <WiFi.h>
#include "freshen.h"

static int echo(const char *arg, char *buf, size_t len, void *userdata) {
  snprintf(buf, len, "%s", arg);
  return 0;
}

void setup() {
  WiFi.begin("WIFI_NETWORK", "WIFI_PASSWORD");  // MODIFY THIS!
  freshen_export("echo", echo, NULL);
}

void loop() {
  freshen_loop("ver.1.0", "DEVICE_ACCESS_TOKEN"); // MODIFY THIS!
}

Login to the dashboard, click on the "RPC" device tab and click on the dropdown button. You should see a list of all management functions exported by the device:

Select "echo" function. Write some random string in the "parameters" field, for example "12345". Click on the "call device" button. You should see the response from the "echo" function:

mbedOS Example

Load mbedOS Web IDE. Create a new project, based on the Blinky example. Remove mbed library. Import https://github.com/ARMmbed/mbed-os/ library. Import https://os.mbed.com/teams/Cesanta-Software/code/freshen/ library. Open main.cpp file and enter the following code:

Important: don't forget to change DEVICE_ACCESS_TOKEN.

Note: this example is using Ethernet network interface, and was tested with the NXP LPC 4088 Quickstart Board.

#include <stdio.h>
#include "mbed.h"
#include "EthernetInterface.h"

#include "freshen.h"

EthernetInterface net;
DigitalOut myled(LED1);

static int echo(const char *arg, char *buf, size_t len, void *userdata) {
  snprintf(buf, len, "%s", arg);
  return 0;
}

int main() {
    net.connect();
    freshen_net = &net;
    freshen_export("echo", echo, NULL);

    for (;;) {
        const char *ip = net.get_ip_address();
        printf("IP address is: %s\n", ip ? ip : "No IP");

        myled = 1;
        wait(0.5);
        myled = 0;
        wait(0.5);

        freshen_loop("1.0", "DEVICE_ACCESS_TOKEN");
    }
}

API reference

void freshen_export(
        const char *function_name,
        void (*handler)(const char *arg, char *buf, size_t len, void *userdata),
        void *userdata);

Export a function through the online dashboard.

Parameters:
function_name
(string). A unique function name.
handler
(function). A handler function. It should parse the input string arg and write a reply into the buf,len buffer. The reply should be a 0-terminated string.
userdata
(pointer). An arbitrary pointer. Passed to the handler function.
Return value:
0 for success, non-0 for error.

Some functions are exported by the Freshen library by default, for example info, rpc.list. Here is a quick example of a how to create a management function to switch on/off a GPIO pin, and call this function via the dashboard:

int my_func(const char *arg, char *buf, size_t len, void *ud) {
  int pin, val;
  if (sscanf(arg, "%d %d", &pin, &val) != 2) {
    snprintf(buf, len, "bad request");
    return 400;
  }
  gpio_set_level(pin, val);
  snprintf(buf, len, "ok");
  return 0;
}
...
freshen_core_export(&ctx, "set_gpio", my_func, NULL);
$ curl -d '2 0' -H AUTH .../rpc/set_gpio
ok

$ curl -d 'hello' -H AUTH .../rpc/set_gpio
{
  "error": {
    "code": 400,
    "message": "bad request"
  }
}

void freshen_loop(const char *version, const char *access_token);

Perform one cycle of the event loop, communicating with the device dashboard. Call this function in the infinite loop.

Parameters:
version
(string). An arbitrary string that tells the dashboard which firmware is currently running. Example: "ver_1.22.master"
access_token
(string). A device access token, generated by the dashboard for each registered device. Example: "4376fae5bd65"
Return value:
none.