Hello IOT-Kit, Introduction By Example
February 12, 2018 at 01:54 AM | categories: iotkit, python, iot, C++, definitions, iotoy | View CommentsIOT-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.