Hello IOT-Kit, Introduction By Example

February 12, 2018 at 01:54 AM | categories: iotkit, python, iot, C++, definitions, iotoy | View Comments

IOT-Kit is a toolkit for enabling the creation of IOT devices, by people who can make simple arduino based devices. Rather than waffle on about why, how, etc, I think the best way of explaining what this does, is by example.

Specifically, this post covers:

  • Suppose you can make little arduino based robots
  • Suppose you want to remote control your robots over the local network, trivially from something like python. (Really over HTTP in fact!)

What do you as a device maker need to do to make this happen?

IOT-Kit - Make your Arduino Device an IOT Device, easily

So the really short version is this: you can make a simple robot, but you want to make it usable as an IOT device too. You don't want to build the entire stack. You don't want to build everything yourself.

You want your users to be able to type something like this program and have it search for the robot on the network, and have it control the robot.

from iotoy.local import simplebot
import time
import random

simplebot.lights = 0
while True:
    choice = random.choice(("forward", "backward", "left", "right", "blink", "stop"))
    if choice == "forward":
        simplebot.forward()
    if choice == "backward":
        simplebot.backward()
    if choice == "left":
        simplebot.left()
    if choice == "right":
        simplebot.right()
    if choice == "blink":
        for i in range(3):
            simplebot.lights = 1
            time.sleep(0.5)
            simplebot.lights = 0
            time.sleep(0.5)
    if choice == "stop":
        simplebot.stop()

    time.sleep(5)

NOTE: While this is python, this actually maps to a bunch of deterministic http web calls, and actually can be written in any langauge. iotoy/iotkit just provides a bunch of convenience functions to do these calls in a way that also maps cleanly to python. (It also would map cleanly in javascript, ruby, perl, etc)

How do we get to this?

Building the Robot - Hardware

These is the easy part. We could use a DAGU mini-driver. This can control a number of servos and also provides serial access over plain old hardware serial bluetooth.

Building the Robot - Software, No IOT

If we were just controlling the robot without any remote control, we could use Pyxie to program this. The Pyxie program you might use could look like this:

#include <Servo.h>

leftwheel = Servo()
rightwheel = Servo()

headlights_led_pin = 13
leftwheel_pin = 2
rightwheel_pin = 3

pinMode(headlights_led_pin, OUTPUT)
leftwheel.attach(leftwheel_pin)
rightwheel.attach(rightwheel_pin)

leftwheel.write(90)
rightwheel.write(90)

while True:
    leftwheel.write(180)
    rightwheel.write(180)
    delay(500)

    leftwheel.write(180)
    rightwheel.write(0)
    delay(500)

    leftwheel.write(0)
    rightwheel.write(0)
    delay(500)

    leftwheel.write(0)
    rightwheel.write(180)
    delay(500)

    leftwheel.write(90)
    rightwheel.write(90)
    delay(500)

    digitalWrite(headlights_led_pin, HIGH)
    delay(1000)
    digitalWrite(headlights_led_pin, LOW)
    delay(1000)

This program assume 2 continuous rotation servos, where the centre point 90 means stationary, 0 means full reverse, and 180 means full forward.

What this program means is "forward, right, backward, left, stop, blink headlights".

Pyxie generates C++ code, which we can use as a starting point for our code:

#include <Servo.h>

#include "iterators.cpp"

void setup() {
    int headlights_led_pin;
    Servo leftwheel;
    int leftwheel_pin;
    Servo rightwheel;
    int rightwheel_pin;

    leftwheel = Servo();
    rightwheel = Servo();
    headlights_led_pin = 13;
    leftwheel_pin = 2;
    rightwheel_pin = 3;
    pinMode(headlights_led_pin, OUTPUT);
    (leftwheel).attach(leftwheel_pin);
    (rightwheel).attach(rightwheel_pin);
    (leftwheel).write(90);
    (rightwheel).write(90);
    while (true) {
        (leftwheel).write(180);
        (rightwheel).write(180);
        delay(500);
        (leftwheel).write(180);
        (rightwheel).write(0);
        delay(500);
        (leftwheel).write(0);
        (rightwheel).write(0);
        delay(500);
        (leftwheel).write(0);
        (rightwheel).write(180);
        delay(500);
        (leftwheel).write(90);
        (rightwheel).write(90);
        delay(500);
        digitalWrite(headlights_led_pin, HIGH);
        delay(1000);
        digitalWrite(headlights_led_pin, LOW);
        delay(1000);
    };
}

void loop() {
}

Making a simple device abstraction layer for our device.

A device abstraction layer just means creating names for the key functionality we care about. At some point, pyxie will help here, but pyxie is currently very simple and can't create functions, so we take the C++ code we have so far and work from there.

The interators in the iterators.cpp file are not used, so we can ditch that too.

Creating functions for functionality

So our first step is to pull out and name all the functions. While we're at it, unlike pyxie, we'll split out the contents of what would normally be in a setup() and loop() in an arduino program.

#include <Servo.h>

int headlights_led_pin;

Servo leftwheel;
Servo rightwheel;

int leftwheel_pin;
int rightwheel_pin;

void forward() {
    leftwheel.write(180);
    rightwheel.write(180);
    delay(500);
}
void backward() {
    leftwheel.write(0);
    rightwheel.write(0);
    delay(500);
}
void left() {
    leftwheel.write(180);
    rightwheel.write(0);
    delay(500);
}
void right() {
    leftwheel.write(0);
    rightwheel.write(180);
    delay(500);
}
void stop() {
    leftwheel.write(0);
    rightwheel.write(0);
    delay(500);
}
void lights_on() {
    digitalWrite(headlights_led_pin, HIGH);
}
void lights_off() {
    digitalWrite(headlights_led_pin, LOW);
}

void setup() {
    leftwheel = Servo();
    rightwheel = Servo();
    headlights_led_pin = 13;
    leftwheel_pin = 2;
    rightwheel_pin = 3;

    pinMode(headlights_led_pin, OUTPUT);
    leftwheel.attach(leftwheel_pin);
    rightwheel.attach(rightwheel_pin);
    leftwheel.write(90);
    rightwheel.write(90);
}

void loop() {
    forward();
    right();
    backward();
    left();

    lights_on();
    delay(1000);
    lights_off();
    delay(1000);
}

Device abstraction for our robot

So the device abstraction layer for our device has the following signature:

void forward();
void backward();
void left();
void right();
void stop();
void lights_on();
void lights_off();

This is the what we need to build an IOT-Kit interface for.

Minimal IOT-Kit Interface

Our starting point for our IOT-Kit interface is something minimal. Initially we'll try to cover the following parts of our device abstraction:

void forward();
void stop();
void lights_on();
void lights_off();

We'll then add the rest in.

Changes to support minimal control API

We add the following include near the top of the file:

#include <CommandHostTiny.h>

In order to make our device introspectable and controllable, we need to add in a class which subclasses "CommandHostTiny".

The skeleton of this class looks like this:

class SimplebotHost : public CommandHostTiny {
private:
    char temp_str[128];   // needed for parsing input
    int lights;           // To store state of the headlight
public:
    SimplebotHost() : lights(0) { }
    ~SimplebotHost() { }

    const char *hostid(); // Returns the name of the device
    const char * attrs(); // Returns the list of attributes(+types) that can be changed
    const char * funcs(); // Returns the list of functions the device understands.

    bool has_help(char * name); // To allow us to find out whether a given name has help.

    void help(char * name); // Returns the help for a given name - usually a function
                            // Includes machine parsable type signature

    bool exists(char * attribute); // Returns true/false for an attribute existing.

    const char *get(char * attribute); // Gets the value for an attribute

    int set(char* attribute, char* raw_value); // Sets the value for attributes

    int callfunc(char* funcname, char* raw_args); // Calls the given function with given raw_args
};

So by way of example, hostid, attrs and funcs in this case look like this:

const char *hostid() {    return "simplebot";     }
const char * attrs() {    return "lights:int";    }
const char * funcs() {    return "forward,stop";  }

Note that the name returned as host id here - "simplebot" - is used as the name to advertise the robot on the network, and that is how this line of python is made to work:

from iotoy.local import simplebot

Help is implemented in two ways - firstly to note that help is available and then to return the help available:

bool has_help(char * name) {
    if (strcmp(name,"forward")==0) return true;
    if (strcmp(name,"stop")==0) return true;
    return false;
}

void help(char * name) {
    if (strcmp(name,"forward")==0) Serial.println(F("forward -> - Move forward for 1/2 second"));
    else if (strcmp(name,"stop")==0) Serial.println(F("stop -> - Stop moving"));
    else Serial.println(F("-"));
}

Attribute handling is then done as follows. Note we only have one attribute - lights. ANd here I choose to update the LED state whenever the lights value changes:

bool exists(char * attribute) {
    if (strcmp(attribute,"lights")==0) return true;
    return false;
}

const char *get(char * attribute) {
    if (strcmp(attribute,"lights")==0) { 
        itoa (lights, temp_str, 10); 
        return temp_str; 
    }
    return "-";
}

int set(char* attribute, char* raw_value) {
    if (strcmp(attribute,"lights")==0) {
        int value = atoi(raw_value);
        lights = value;
        if (lights) {
            lights_on();
        } else {
            lights_off();
        }
        return 200;
    }
    return 404;
}

Handling function calls is pretty simple:

int callfunc(char* funcname, char* raw_args) { 
    if (strcmp(funcname,"forward")==0) { forward(); return 200; }
    if (strcmp(funcname,"stop")==0) { backward(); return 200; }
    return 404; 
}

IOT-kit final step

At this stage, the command host isn't being used.

Our final step in our transformation boils down to:

  • Add the other functions from our device abstraction
  • Move the setup for the robot into a setup function in the class
  • Make sure that setup also sets up the command host
  • Make the arduino set up set up our robot
  • Remove the custom code from loop() and run the command host instead.

In practice this means that our final code looks like this:

#include <Servo.h>
#include <CommandHostTiny.h>

int headlights_led_pin;

Servo leftwheel;
Servo rightwheel;

int leftwheel_pin;
int rightwheel_pin;

void forward() {
    leftwheel.write(180);
    rightwheel.write(180);
    delay(500);
}
void backward() {
    leftwheel.write(0);
    rightwheel.write(0);
    delay(500);
}
void left() {
    leftwheel.write(180);
    rightwheel.write(0);
    delay(500);
}
void right() {
    leftwheel.write(0);
    rightwheel.write(180);
    delay(500);
}
void stop() {
    leftwheel.write(0);
    rightwheel.write(0);
    delay(500);
}
void lights_on() {
    digitalWrite(headlights_led_pin, HIGH);
}
void lights_off() {
    digitalWrite(headlights_led_pin, LOW);
}

class SimplebotHost : public CommandHostTiny {
private:

    char temp_str[128];
    int lights; //

public:
    SimplebotHost() : lights(0) { }
    ~SimplebotHost() { }

    const char *hostid() {    return "simplebot";     }
    const char * attrs() {    return "lights:int";    }
    const char * funcs() {    return "forward,backward,left,right,stop";  }

    bool has_help(char * name) {
        if (strcmp(name,"forward")==0) return true;
        if (strcmp(name,"backward")==0) return true;
        if (strcmp(name,"left")==0) return true;
        if (strcmp(name,"right")==0) return true;
        if (strcmp(name,"stop")==0) return true;
        return false;
    }

    void help(char * name) {
        if (strcmp(name,"forward")==0) Serial.println(F("forward -> - Move forward for 1/2 second"));
        else if (strcmp(name,"backward")==0) Serial.println(F("backward -> - Move backward for 1/2 second"));
        else if (strcmp(name,"left")==0) Serial.println(F("left -> - Spin left for 1/2 second"));
        else if (strcmp(name,"right")==0) Serial.println(F("right -> - Spin right for 1/2 second"));
        else if (strcmp(name,"stop")==0) Serial.println(F("stop -> - Stop moving"));
        else Serial.println(F("-"));
    }

    bool exists(char * attribute) {
        if (strcmp(attribute,"lights")==0) return true;
        return false;
    }

    const char *get(char * attribute) {
        if (strcmp(attribute,"lights")==0) { 
            itoa (lights, temp_str, 10); 
            return temp_str; 
        }
        return "-";
    }

    int set(char* attribute, char* raw_value) {
        if (strcmp(attribute,"lights")==0) {
            int value = atoi(raw_value);
            lights = value;
            if (lights) {
                lights_on();
            } else {
                lights_off();
            }
            return 200;
        }
        return 404;
    }

    int callfunc(char* funcname, char* raw_args) { 
        if (strcmp(funcname,"forward")==0) { forward(); return 200; }
        if (strcmp(funcname,"backward")==0) { backward(); return 200; }
        if (strcmp(funcname,"left")==0) { left(); return 200; }
        if (strcmp(funcname,"right")==0) { right(); return 200; }
        if (strcmp(funcname,"stop")==0) { backward(); return 200; }
        return 404; 
    }

    void setup(void) {
        // Setup the pins
        CommandHostTiny::setup();

        leftwheel = Servo();
        rightwheel = Servo();
        headlights_led_pin = 13;
        leftwheel_pin = 2;
        rightwheel_pin = 3;

        leftwheel.attach(leftwheel_pin);
        rightwheel.attach(rightwheel_pin);
        leftwheel.write(90);
        rightwheel.write(90);

        pinMode(headlights_led_pin, OUTPUT);
    }
};

SimplebotHost MyCommandHost;

void setup()
{
    MyCommandHost.setup();
}

void loop() {
    MyCommandHost.run_host();
}

Final notes

So, that's a whistle stop tour of the device layer. The fun thing now: assuming this robot has a hardware serial bluetooth (ala the dagu mini), then this is everything you need to do as an arduino based maker to make your device an IOT-able device. If you're not using bluetooth, then your device assumes it's doing serial down a cable.

Either way though, as a device maker, this is all the changes you need to do to enable the python program we started with to be able to control your robot over a network.

I'll explain how this works in a later blog post, but I thought this would make a good fun first example about how IOT-Kit gets implemented by a device maker to enable a very high level of abstraction to take place.

blog comments powered by Disqus