STM32F746G-DISCO Board with FreeRTOS + AppMyProduct

This is an updated version of a previous post incorporating our new AppMyProduct platform.

When working with STM32 ARM Cortex-M microcontrollers, the free embedded software STM32Cube from ST provides all necessary drivers and a collection of middleware components to reduce the initial development effort. One of the mentioned middleware components is the popular FreeRTOS real-time operating system Nabto is partnering with to create a powerful combined FreeRTOS+Nabto solution.

The launch of our new AppMyProduct application platform helps you quickly develop high-quality apps for remote control of your devices. This article explains the implementation of a demo heat-pump application using Nabto + FreeRTOS on a STM32F746G-DISCO board, which can be controller by our Heat Control Ionic starter app. (A previous blog post described the now deprecated HTML device driver approach.)

nabto+stm32

STM32F74G-DISCO board running the heat-pump demo

What you need

This demo is based and tested on the STM32F746G-DISCO board, but should be portable to similar STM32 boards.

Why Nabto and how does it work?

Have you ever thought about connecting to your STM32 device from outside your local network without nerve wracking router and firewall configurations or slow and intransparent cloud services? Running the uNabto server on your STM32 device you can establish a fast and secure Peer-to-Peer connection using only a static device ID – from everywhere, no matter what is in between.

So how does Nabto do this? The drawing below gives a brief overview. Your STM32 board represents the Device running the uNabto server. As soon as it connects to the internet it identifies itself at the Nabto Basestation, using its unique Device ID. If a Client (e.g. our Heat Control Ionic starter app) wants to connect to the STM32 board, a connect request with the ID is sent to the Basestation, and a direct connection to the device is established.

Screen Shot 2016-02-29 at 13.23.22

Nabto in the STM32Cube Architecture

As mentioned before, the STM32Cube provides a Hardware Abstraction Layer API and a selection of Middleware components. The uNabto server we want to implement builds on top of the STM32Cube middleware, as can be seen in the following diagram:

nabto+stm32cube-architecture.png

The uNabto server itself is divided into two layers:

  • The actual uNabto framework (uNabto SDK)
  • The uNabto Platform Adapter abstraction layer between the uNabto framework and the STM32Cube middleware

Hence, we only need to implement the uNabto Platform Adapter, in order to port the uNabto server and the demo application to the STM32 platform.

Implementing the Platform Adapter

The Platform Adapter acts as link between the generic uNabto framework and the STM32Cube middleware layer, including the LwIP TCP/IP stack which will be used for networking. The LCD Log Utility is used to display the Nabto log output on the screen.

The adapter is divided into single files, as suggested in the Nabto documentation (TEN023 Nabto device SDK guide, chapter 12):

  • unabto_config.h: Basic uNabto configuration
  • unabto_platform_types.h: Defines all necessary uNabto types
  • unabto_platform.h: Platform specific ad-hoc functions
  • network_adapter.c: Init, close, read and write functionality for network data
  • time_adapter.c: Time functions
  • dns_adapter.c: DNS resolving
  • random_adapter.c: Random generator
  • log_adapter.c: Logging

If you are interested in how the platform adapter is implemented in detail, check the adapter files in the Inc and Src directories of the demo on GitHub.

Implementing the Nabto Thread

After creating the Platform Adapter the uNabto Server is ready to use. uNabto is initialized and runs in it’s own FreeRTOS thread. The thread is defined in Src/unabto_main.c:

static void unabto_thread()
{
  const char* device_id = "<DEVICE ID>";
  const char* pre_shared_key = "<KEY>";

  // Init uNabto
  nabto_main_setup* nms = unabto_init_context();
  nms->id = strdup(device_id);

  nms->secureAttach = true;
  nms->secureData = true;
  nms->cryptoSuite = CRYPT_W_AES_CBC_HMAC_SHA256;

  if (!unabto_read_psk_from_hex(pre_shared_key, nms->presharedKey, 16)) {
    NABTO_LOG_ERROR(("Invalid cryptographic key specified", pre_shared_key));
    return;
  }

  if (!unabto_init()) {
    NABTO_LOG_FATAL(("Failed at nabto_main_init"));
  }

  // Init demo application
  demo_init(do_factory_reset);
  demo_application_set_device_name("STM32F746G-DISCO");
  demo_application_set_device_product("ACME 9002 Heatpump");
  demo_application_set_device_icon_("img/chip-small.png");

  // Main loop
  for (;;) {
    unabto_tick();
    osDelay(10);
    demo_application_tick();
  }
}

The code above initializes the server with your unique Device ID and the pre-shared encryption key from portal.appmyproduct.com (insert in highlighted lines). Then the heat-pump demo, which will be briefly described later, is initialized. Finally, an infinite loop repeatedly calls the unabto_tick() method. This triggers the framework to check for new UDP packets and send responses. The time between ticks should be around 10 milliseconds, which is achieved by a hard OS delay and some additional demo specific workload.
To start the Nabto thread we provide a unabto_start() method. It is called by the main program after initializing the TCP/IP stack and either setting a static IP address or obtaining a dynamic IP address from a DHCP server.

void unabto_start()
{
  sys_thread_new("uNabto", unabto_thread, NULL, DEFAULT_THREAD_STACKSIZE, NABTO_THREAD_PRIO);
}

The actual handling of received client requests is implemented in the application_event() callback function. The handler uses the interface definition shared with the client.

application_event_result application_event(application_request* request,
                                           unabto_query_request* query_request,
                                           unabto_query_response* query_response) {

    NABTO_LOG_INFO(("Nabto application_event: %u", request->queryId));
    debug_dump_acl();

    // handle requests as defined in interface definition shared with
    // client - for the default demo, see
    // https://github.com/nabto/ionic-starter-nabto/blob/master/www/nabto/unabto_queries.xml

    application_event_result res;

    switch (request->queryId) {
    case 10000:
        // get_public_device_info.json
        if (!write_string(query_response, device_name_)) return AER_REQ_RSP_TOO_LARGE;
        if (!write_string(query_response, device_product_)) return AER_REQ_RSP_TOO_LARGE;
        if (!write_string(query_response, device_icon_)) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_pair_allowed(request))) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_user_paired(request))) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_user_owner(request))) return AER_REQ_RSP_TOO_LARGE;
        return AER_REQ_RESPONSE_READY;

    case 10010:
        // set_device_info.json
        if (!fp_acl_is_request_allowed(request, REQUIRES_OWNER)) return AER_REQ_NO_ACCESS;
        res = copy_string(query_request, device_name_, sizeof(device_name_));
        if (res != AER_REQ_RESPONSE_READY) return res;
        if (!write_string(query_response, device_name_)) return AER_REQ_RSP_TOO_LARGE;
        return AER_REQ_RESPONSE_READY;

    case 11000:
        // get_users.json
        return fp_acl_ae_users_get(request, query_request, query_response); // implied admin priv check

    case 11010:
        // pair_with_device.json
        if (!fp_acl_is_pair_allowed(request)) return AER_REQ_NO_ACCESS;
        res = fp_acl_ae_pair_with_device(request, query_request, query_response);
        debug_dump_acl();
        return res;

        // [...]
    }
}

For more details on the Heat-Pump demo application please review the source in Src/unabto_application.c.

Hands-On!

Enough theory. Let’s try out the demo! It is available as System Workbench for STM32 (SW4STM32) project here on GitHub. Simply follow the instructions in the README.

The board should print the Nabto log on the LCD screen, as shown on the picture in the beginning of the article.

Now, connect to your device using the Heat Control Ionic starter app and see the parameters on the bottom of the LCD display change according to your app inputs. The simulated room temperature slowly converges to the target temperature. You can update the current room temperature in your app by pressing Refresh.

amp_stm32_app

The device settings are persistently stored in the STM32 flash memory. If you want to perform a “factory reset”, press the User button on the board during startup/reset. You should see FACTORY RESET printed to the display.

Java Agent for Cassandra Metrics Export to Prometheus

At Nabto we are using many technologies in our platform, one of them is Cassandra. We are monitoring Cassandra with the following setup setup: Cassandra + Prometheus JMX Exporter -> Prometheus -> Grafana. This setup has a component which does not meet our requirements for a production setup: The Prometheus JMX Exporter uses too much memory and CPU for the simple task it has to do.

To mitigate this problem we have created a new Java Agent which directly exports the Dropwizard metrics from the Cassandra core through the Prometheus Dropwizard Exporter and serves the metrics through a resource limited Jetty HTTP server.

The code and documentation can be found on GitHub https://github.com/nabto/cassandra-prometheus

 

ESP8266 WiFi Module + AppMyProduct

This is an updated version of a previous post incorporating our new AppMyProduct platform.

The ESP8266 is a low-cost WiFi module that can be programmed directly like a microcontroller. Already thinking of your next Internet of Things project?

While an available Arduino library allows a quick start, there is still one problem to overcome: How to access your ESP8266 from outside your home network without nerve-racking router and firewall configurations or heavy cloud services? Running the uNabto server on your ESP8266, you can establish a fast and secure Peer-to-Peer connection using only a static ID – from everywhere, no matter what is in between.

What hardware you’ll need

IMG_20160303_153803

Adafruit HUZZAH ESP8266 Breakout

IMG_20160303_153830

USB to TTL Serial Cable

This project is tested on an Adafruit ESP8266 board. It’s not the cheapest you can get, but very prototyping friendly. You can put it on a breadboard and it has a 3.3V voltage regulator onboard. Of course, you can also use a different ESP8266 module. Wikipedia provides an extensive list of available modules.

Regarding the USB to TTL Serial Cable (get it e.g. here), there are no special requirements when using the Adafruit module. If you use a different module, make sure the voltage levels match, since most boards only accept 3.3V.

Solder the pin header to your ESP8266 board and connect it to your USB to TTL adapter using 4 wires:

wiring

What software you’ll need

This project is tested on Ubuntu 16.04 but should work on any OS supported by the Arduino IDE.

We want to program the ESP8266 directly, instead of using any higher level interpreters available for the module. Adafruit provides a nice tutorial on how to setup your Arduino IDE accordingly.

If everything is running, you should be able to compile the following simple sketch and upload it to your ESP8266. This will make the onboard LED blink every second.

const int led = BUILTIN_LED;

void setup() {
  pinMode(led, OUTPUT);
}

void loop() {
  digitalWrite(led, HIGH);
  delay(500);
  digitalWrite(led, LOW);
  delay(500);
}

How the Nabto platform works

So far, so good. But how does the Nabto platform actually work? The drawing below gives a brief overview. Your ESP8266 module represents the Device running the uNabto server. As soon as it connects to the internet it identifies itself at the Nabto Basestation, using its unique ID. If a Client wants to connect to the ESP8266, a connect request with the ID is sent to the Basestation, and a direct connection to the device is established. A client can be a native app or use an abstraction framework like our Heat Control Ionic starter app used in this demo.

nabto-platform-basics

Get more information on the AppMyProduct platform and the Client/Device SDKs on portal.appmyproduct.com.

The uNabto Platform Adapter

The uNabto server is divided into two layers:

  • The actual uNabto framework (uNabto SDK)
  • The uNabto Platform Adapter abstraction layer between the framework and the Native Platform

unabto-platform-adapter

Hence, we only need to implement the uNabto Platform Adapter, in order to port the uNabto server to the ESP8266 module.

Implementing the Platform Adapter

The Platform Adapter acts as link between the generic uNabto framework and the Arduino platform, including the ESP8266 WiFi library. The adapter is divided into single files, as suggested in the Nabto documentation (TEN023 Nabto device SDK guide, section 12):

  • unabto_config.h: Basic uNabto configuration
  • unabto_platform_types.h: Define all necessary uNabto types
  • unabto_platform.h: Platform specific ad-hoc functions
  • network_adapter.cpp: Init, close, read and write functionality for network data
  • time_adapter.cpp: Time functions
  • dns_adapter.cpp: DNS resolving
  • random_adapter.cpp: Random generator
  • log.cpp: Logging

If you are interested in how the platform adapter is implemented in detail, check the adapter files in the src directory of the library on GitHub.

Using the Library

Get the Nabto ESP8266 Arduino library from https://github.com/nabto/unabto-esp8266-sdk and follow the installation instructions.

An example sketch can be found in

File -> Examples -> Nabto-ESP8266 -> HeatPump

The sample sketch includes the Nabto class, which encapsulates the Nabto setup. First, some settings are made. This includes the WiFi SSID and password, followed by the unique Device ID and preshared key of the device obtained from portal.appmyproduct.com

// Enter ssid and password of your WiFi network
const char* WIFI_SSID = "<SSID>";
const char* WIFI_PASSWORD = "<PASSWORD>";

// Enter device id and pre-shared key from portal.appmyproduct.com
const char* DEVICE_ID = "<DEVICE ID>";
const char* PRE_SHARED_KEY = "<PRE-SHARED KEY>";

The setup function is used to init the LED pin, the Serial module, and connect to the WiFi. In line 91 you can see how the Nabto module is initialised. After the Nabto version is printed our heatpump demo is initialized.

void setup() {
    // Initialize Serial
    Serial.begin(115200);

    // Wait 2s for button press to do factory reset
    pinMode(0, INPUT_PULLUP);
    bool factory_reset = false;
    while(millis() < 2000) {
      if(digitalRead(0) == LOW) {
        Serial.println("FACTORY RESET");
        factory_reset = true;
        break;
      }
    }

    // Initialize built-in led
    pinMode(LED_PIN, OUTPUT);
    analogWrite(LED_PIN, PWMRANGE);

    // Initialize WiFi
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.print("Connecting to WiFi..");
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print(".");
        delay(500);
    }
    Serial.println("done");

    // Initialize Nabto
    Serial.println("Init Nabto...");
    Nabto.begin(DEVICE_ID, PRE_SHARED_KEY);

    // Print Nabto version
    char versionString[10];
    Nabto.version(versionString);
    Serial.print("Nabto v");
    Serial.print(versionString);
    Serial.println(" running");

    // Initialize demo application
    demo_init(factory_reset);
    demo_application_set_device_name("ESP8266");
    demo_application_set_device_product("ACME 9002 Heatpump");
    demo_application_set_device_icon_("img/chip-small.png");
}

The only thing that needs to be done in the loop function is to call the tick() method of the Nabto class. This triggers the framework to check for new UDP packets and send responses. The time between ticks should be around 10 milliseconds. This is achieved by a hard delay, but you can also use the time to do application related stuff. For example, we use it to update the brightness of the LED and to simulate the room temperature in the demo application tick function.

void loop() {
    Nabto.tick();
    demo_application_tick();
    delay(10);
}

The actual handling of received Nabto messages from the client is implemented in the application_event(..) function. The handler uses the interface definition shared with the client.

pplication_event_result application_event(application_request* request,
                                           unabto_query_request* query_request,
                                           unabto_query_response* query_response) {

    //NABTO_LOG_INFO(("Nabto application_event: %u", request->queryId));
    //debug_dump_acl();

    // handle requests as defined in interface definition shared with
    // client - for the default demo, see
    // https://github.com/nabto/ionic-starter-nabto/blob/master/www/nabto/unabto_queries.xml

    application_event_result res;

    switch (request->queryId) {
    case 10000:
        // get_public_device_info.json
        if (!Nabto.write_string(query_response, device_name_)) return AER_REQ_RSP_TOO_LARGE;
        if (!Nabto.write_string(query_response, device_product_)) return AER_REQ_RSP_TOO_LARGE;
        if (!Nabto.write_string(query_response, device_icon_)) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_pair_allowed(request))) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_user_paired(request))) return AER_REQ_RSP_TOO_LARGE;
        if (!unabto_query_write_uint8(query_response, fp_acl_is_user_owner(request))) return AER_REQ_RSP_TOO_LARGE;
        return AER_REQ_RESPONSE_READY;

    case 10010:
        // set_device_info.json
        if (!fp_acl_is_request_allowed(request, REQUIRES_OWNER)) return AER_REQ_NO_ACCESS;
        res = Nabto.copy_string(query_request, device_name_, sizeof(device_name_));
        if (res != AER_REQ_RESPONSE_READY) return res;
        if (!Nabto.write_string(query_response, device_name_)) return AER_REQ_RSP_TOO_LARGE;
        return AER_REQ_RESPONSE_READY;

    case 11000:
        // get_users.json
        return fp_acl_ae_users_get(request, query_request, query_response); // implied admin priv check

        // [...]
    }
}

Test your device

After compiling and uploading your HeatPump sketch to the ESP8266, it establishes a connection to your WiFi network and starts the uNabto server. In your serial monitor you should see the following printout:

Connecting to WiFi........done
Init Nabto...
Device id: 'n7j4qebq.hyr7o.appmyproduct.com'
Program Release 123.456
Application event framework using SYNC model
SECURE ATTACH: 1, DATA: 1
NONCE_SIZE: 32, CLEAR_TEXT: 0
Nabto was successfully initialized
Nabto v123.456 running
SECURE ATTACH: 1, DATA: 1
NONCE_SIZE: 32, CLEAR_TEXT: 0
State change from IDLE to WAIT_DNS
Resolving dns: esp8266test1.demo.nab.to
State change from WAIT_DNS to WAIT_BS
State change from WAIT_BS to WAIT_GSP
########    U_INVITE with LARGE nonce sent, version: - URL: -
State change from WAIT_GSP to ATTACHED

Now, connect to your device using the Heat Control Ionic starter app and see the LED change its brightness according to the target heat.

amp_esp8266_app