02 Blinkr Client in Python

The first tutorial, 01 CoAP Blink, introduced into slyft, the specification of a RESTful API for constrained devices using CoAP as a procotol and MessagePack as a data serialization format, and shows how to implement it on an embedded device such as an ESP8266.

It left us with a programmed device capable of handling CoAP requests to the /led endpoint, understanding data structures such as the following (expressed in JSON):

{
  "on_d": 100,
  "off_d": 200,
  "t": 25
}

This would cause our device to blink the builtin LED for t=25 times, with an "on duration" of on_d=100 milliseconds, and an "off duration" off_d=200 milliseconds. What's left now is a client that is capable to form such requests and send them to the device using CoAP and MessagePack.

Luckily, there's a Slyftlet for that :) In this tutorial we'll introduce to the Multilanguage client MessagePack slyftlet and show to to build a client app in Python, which can run on a notebook or a RaspberryPi.

Prerequisites

This tutorial assumes, that you have completed 01 CoAP Blink and flashed to a device. This includes having the slyft client installed. Furthermore, you need a python 2.7 installation with pip to complete this tutorial.

Consider using a tool such as virtualenv to install libraries in a separate python environment.

Using Slyft

Since we're using the same data structures as in the first tutorial, it makes sense to reuse them. Let's create a project directory next to slyft-tut-1, calling it slyft-tut-2. Copy over the blinkr-schema.json from the first tutorial:

$ mkdir slyft-tut-2
$ cp slyft-tut-1/blinkr-schema.json slyft-tut-2/
$ cd slyft-tut-2

The file stays as-is, since we want to have the same data structure understood on both embedded and client device:

$ cat blinkr-schema.json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
    "id": "blinkr",
    "title": "Making a blinky API out of this",
    "description": "Specify blink mode, duration etc. for an embedded device LED",
    "type": "object",
    "properties" : {
    "on_d" : {
          "type": "integer",
          "description": "msec, how long to have LED turned on"
        },
    "off_d" : {
          "type": "integer",
          "description": "msec, how long to have LED turned off"
        },
    "t" : {
          "type": "integer",
          "description": "times of repetitions. Optional. If not given, run forever"
    }
    },
    "additionalProperties": false,
    "required": ["on_d", "off_d"],
    "minProperties": 2
}

This time, we want another slyftlet to be triggered instead, because we need to generate client-side code instead of device code. The Slyftlet name is multilang-client-msgpack, and its configuration will reside in a file name multilang-client-msgpack.json. Create this file and put the following contents it:

{
    "class_prefix": "SlyftTut2"
}

Using the slyft command line client we can slyft-create a new project:

$ slyft project create --name slyft-tut-2
Project Name: slyft-tut-2
Details to the project (optional):

---- Project Details ----
     KEY    |             VALUE
+-----------+-------------------------------+
  Name      | slyft-tut-2
  Details   |
  CreatedAt | 2017-01-26 12:19:42 +0000 UTC
  UpdatedAt | 2017-01-26 12:19:42 +0000 UTC
  Settings  | {}

and upload both files:

$ slyft asset add --project slyft-tut-2 --file blinkr-schema.json
$ slyft asset add --project slyft-tut-2 --file multilang-client-msgpack.json

$ slyft asset list --project slyft-tut-2

  NUMBER |             NAME              | PROJECT NAME | ORIGIN
+--------+-------------------------------+--------------+--------+
       1 | blinkr-schema.json            | slyft-tut-2  | user
       2 | multilang-client-msgpack.json | slyft-tut-2  | user

This is how it should look like. Using the project validate subcommand we can run a validation on the schema and the configuration, to see if it would pass a build:

$ slyft project validate --project slyft-tut-2

---- Job Details ----
       KEY      |             VALUE
+---------------+-------------------------------+
  Id            |                           120
  Kind          | validate
  Status        | new
  ProjectId     |                            25
  ProjectName   | slyft-tut-2
  CreatedAt     | 2017-01-26 12:34:37 +0000 UTC
  UpdatedAt     | 2017-01-26 12:34:37 +0000 UTC
  ResultMessage |
  ResultStatus  |                             0

$ slyft project status -p slyft-tut-2

---- Job Details ----
       KEY      |             VALUE
+---------------+-------------------------------+
  Id            |                           120
  Kind          | validate
  Status        | processed
  ProjectId     |                            25
  ProjectName   | slyft-tut-2
  CreatedAt     | 2017-01-26 12:34:37 +0000 UTC
  UpdatedAt     | 2017-01-26 12:34:40 +0000 UTC
  ResultMessage | Successful
  ResultStatus  |                             0

Looks good. Let's build it:

$ slyft project build --project slyft-tut-2

---- Job Details ----
       KEY      |             VALUE
+---------------+-------------------------------+
  Id            |                           121
  Kind          | build
  Status        | new
  ProjectId     |                            25
  ProjectName   | slyft-tut-2
  CreatedAt     | 2017-01-26 12:35:55 +0000 UTC
  UpdatedAt     | 2017-01-26 12:35:55 +0000 UTC
  ResultMessage |
  ResultStatus  |                             0

$ slyft project status -p slyft-tut-2
  ---- Job Details ----
         KEY      |             VALUE
  +---------------+-------------------------------+
    Id            |                           121
    Kind          | build
    Status        | processed
    ProjectId     |                            25
    ProjectName   | slyft-tut-2
    CreatedAt     | 2017-01-26 12:35:55 +0000 UTC
    UpdatedAt     | 2017-01-26 12:36:04 +0000 UTC
    ResultMessage | Successful
    ResultStatus  |                             0

That worked out, too. Now there should be an all.tar result file, which can be downloaded using the asset command:

$ slyft asset list --project slyft-tut-2

 NUMBER |             NAME              | PROJECT NAME |  ORIGIN
+--------+-------------------------------+--------------+----------+
      1 | blinkr-schema.json            | slyft-tut-2  | user
      2 | multilang-client-msgpack.json | slyft-tut-2  | user
      3 | all.tar                       | slyft-tut-2  | slyftlet

$ slyft asset get --project slyft-tut-2 --file all.tar
Downloaded all.tar

Building the python client

This tarball gives us two files per schema, one .py and one .rb:

$ tar xfv all.tar
blinkr.py
blinkr.rb

Taking a look at blinkr.py we see a class named SlyftTut2__blinkr with instance variables matching our schema definition, as well as some methods for serialization. We will import this class from our main file, which we need to compose.

The multilang-client-msgpack Slyftlet took only care of the "msgpack" data part, and not of the CoAP-part, because there are a number of generic CoAP libraries around, which we can reuse as-is. In our case, we're using https://github.com/Tanganelli/CoAPthon, and install it from source:

$ git clone https://github.com/Tanganelli/CoAPthon.git
$ cd CoAPthon
$ python setup.py sdist
$ pip install dist/CoAPthon-4.0.0.tar.gz -r requirements.txt
(...)
Successfully installed CoAPthon-4.0.0 Sphinx-1.2.2 cachetools-2.0.0 docutils-0.12 flextls-0.3
$ cd ..

The last command (pip install) may vary, depending on your python installation and environment settings. Furthermore, we need a library for handling MessagePack (msgpack-python), and for debugging purposes, hexdump:

$ pip install msgpack-python
$ pip install hexdump

The main script is based upon the coap client script from Tanganelli/CoAPthon. It instantiates a HelperClient object, then constructs a SlyftTut2__blinkr object with parameters read from the command line.

After serialization to msgpack, it prints out a hexdump of the message body, then sends it off to the device using the client.post method. It waits for the response and prints it out.

Put the following script contents in a file named coap_client.py:

import threading
from coapthon import defines
from coapthon.client.coap import CoAP
from coapthon.client.helperclient import HelperClient
from coapthon.messages.message import Message
from coapthon.messages.request import Request
from coapthon.utils import parse_uri
import socket
import hexdump
from blinkr import SlyftTut2__blinkr
import sys

client = None

def main():
    global client

    if len(sys.argv) < 5:
        print("Usage: coap_post.py COAP-URL ON_DURATION OFF_DURATION REPETITIONS")
        sys.exit(1)

    uri = sys.argv[1]

    host, port, path = parse_uri(uri)
    try:
        tmp = socket.gethostbyname(host)
        host = tmp
    except socket.gaierror:
        pass

    client = HelperClient(server=(host, port))

    'construct Data object from parameters'
    blinkr = SlyftTut2__blinkr(int(sys.argv[2]),int(sys.argv[3]),int(sys.argv[4]))

    'serialize to msgpack and dump'
    payload = blinkr.slyft_serialize()
    hexdump.hexdump(payload);

    'POST and print response'
    response = client.post(path, payload)
    print response.pretty_print()
    client.stop()

if __name__ == '__main__':
    main()

Restart the embedded device from the first Tutorial. It will print out its IP address once connected to the WiFi AP configured:

$ pio device monitor            
--- Miniterm on /dev/cu.SLAB_USBtoUART  9600,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
.....
WiFi connected
IP address:
172.20.10.8

Now we can use the python client to control the LED:

$ python coap_client.py coap://172.20.10.8/led 200 30 10

00000000: 83 A4 6F 6E 5F 64 CC C8  A5 6F 66 66 5F 64 1E A1  ..on_d...off_d..
00000010: 74 0A                                             t.

Source: ('172.20.10.8', 5683)
Destination: None
Type: ACK
MID: 6683
Code: CHANGED
Token:
Content-Type: 65535
Payload:
None

The above output may contain debug message from the CoAPthon library, depending on logging settings. Try varying the command line parameters. Format is:

$ python coap_client.py
Usage: coap_post.py COAP-URL ON_DURATION OFF_DURATION REPETITIONS

Summary

The first tutorial introduced to slyft basics and showed how to implement a RESTful API on an embedded device, using CoAP as the protocol suitable for constrained devices and MessagePack as an efficient binary serialiation scheme.

This second tutorial showed how to build a client from the same schema definitions.

Now, try some more slyftification :-)