About the idea.
Just a short story about this original idea I had that finally could make it possible with low cost hardware and now I can improve more for the extra days...
I was working on my Flexduino platform to connect it to Artik cloud, looking to expand the connectivity options but also to enter the contest with a fresh idea, among a few things I implemented connectivity to Artik cloud using websockets and REST. Websockets delay me a little bit but finally I have a working version of websockets client connected to Artik, which is nice to have for control.
Still I have nothing really cool in my mind to control, the idea of turning lights on and off by just the sole purpose of doing it makes no sense at all to me.
Then one night I was heavily working on this websockets library after my first connection succeed (I did get in trouble to make it work for a couple of weeks), when it comes to mind the idea of adding Multimedia data to Artik, but how, the packets are limited to 1024 bytes and you cannot do anything but text processing with it, then I just glue on this old concept of uploading a picture with IoT. Year ago we upload pictures to tinyPIC for paperwork stuff at the University, if you have use this service, after you successfully upload a picture it gives you an URL of the upload pic, that you can use to share or embed.
Well, how cool sounds to upload the url to Artik cloud and have an Android or iOS app to detect and notify/open the picture for you? Also yo can send action command to take picture from Cloud.
Abstract.
Using my Flexduino Platform this project aim to fill the gap between MultiMedia and IoT as mostly known these days by incorporating.
1. Connecting Artik cloud in different manners to provide real solutions for the real world.
2. A low cost hardware capable of MultiMedia with rich of C/C++ libraries (expandable from any Arduino Lib with little/medium work).
3. Stand-Alone operation - No raspberry Pi / Phone / Tablet or PC in the middle, making this a real embedded solution.
By incorporating the three points above, this project tries to not die in a simple idea of IoT that lies in a lab, as we are going to explain in the possible Usage Cases.
Smart MM-IOT Gateway.
The Flexduino Gateway has battery included for the Camera interface and processing power for the Gateway firmware, taking advantage of it an application can take pictures and send to Artik Cloud either by autonomous operation or commanded by Artik cloud action. The device is a gateway since it joins to words, the IoT cloud with a network of sensors, read further for details.
Artik Flexduino camera upload.
As stated before the project firmware has implementation for artik connectivity using either websockets or REST, let's introduce first a little example of REST implementation.
As mentioned in the introduction the pictures are stores in a external web service provider, I choose Imgur since it has a really nice API for what I am planning.
Reader can navigate to Imgur for more details about the API, for now is just enough to say here that project use JSON to upload the base64 jpg image from Flexduino Camera.
You can refer to Flexduino project for details about the image acquisition and jpg processing, since that release some improvements were made but basically it works in a similar way.
On that project the image rendering make uses of websocket server to show it in a canvas by means of a javascript worker, now at this time the interest is to upload an image to a server, but Imgur API need a normal HTTP request using JSON, so a few pieces were missing in order to do that, first the JSON library and secondly the base64 library. As reader might know the first rule here is never do something from scratch unless is really needed.
After including ArduinoJSON and a C++ base64 library to the project a little script for uploading the image to Imgur was done and shown below.
void sendImgurImageCb( void ){
boolean currentLineIsBlank = true;
boolean chase4JsonStart = false;
boolean chase4JsonEnd = false;
boolean postSuccess = false;
char rxBuf[5000];
char *rxBufPtr = &rxBuf[0];
memset(rxBuf, 0, sizeof(rxBuf));
char *jsonPtr = (char*)malloc(MAX_UPLOAD_RESPONSE_SIZE);
char *jsonAdderPtr = jsonPtr;
uint16_t idx = 0;
uint8_t nested;
Serial.println("trying to connect to server");
memset(jsonPtr, 0, MAX_UPLOAD_RESPONSE_SIZE);
if(client.connect(imgur_url, 443)){
Serial.println(" connected to Imgur - Send image");
// client POST to Imgur API version 3
client.println("POST /3/image.json HTTP/1.1");
client.println("Host: api.imgur.com");
client.println("Accept: */*");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println(AuthorizationDataImgur);
// Automated POST data section
client.print("Content-Length: ");
// add the length
int n = loadImageInJson();
client.println(n);
client.println();
char *ptr = (char*)&tmpBuf[0];
// transmit without overflowing the tx buffer
int k;
while(n){
if(n < 500)
k = client.write(ptr, n);
else
k = client.write(ptr, 500);
if(k == 0){
delay(1);
continue;
}else{
n = n - k;
ptr += k;
}
}
client.println();
}
Serial.println("Send Image done");
long start = millis();
for(;;){
if(millis() - start >= 5000)
break;
while (1) {
int rdy = client.available();
if (!rdy)
break;
start = millis();
client.read((uint8_t*)rxBufPtr, rdy);
rxBufPtr += rdy;
}
}
char *idxPtr = &rxBuf[0];
uint16_t len = rxBufPtr - idxPtr;
for(int j=0; j<len; j++){
char c = *idxPtr++;
Serial.write(c);
if(idx >= 100){
Serial.println();
idx = 0;
}
if(c == '{' && chase4JsonStart){
idx++;
*jsonAdderPtr++ = c;
chase4JsonStart = false;
chase4JsonEnd = true;
nested = 1;
continue;
}
if(chase4JsonEnd){
if(c == '}'){
nested--;
if(nested == 0){
chase4JsonEnd = false;
postSuccess = true;
}
}else if(c == '{'){
nested++;
}
idx++;
*jsonAdderPtr++ = c;
}
if (c == '\n' && currentLineIsBlank){
chase4JsonStart = true;
currentLineIsBlank = false;
continue;
}
if (c == '\n') {
currentLineIsBlank = true;
}else if (c != '\r') {
currentLineIsBlank = false;
}
}
parseImgurUpload(jsonPtr);
if (client.connected()) {
Serial.println("disconnecting from server");
client.stop();
}
free(jsonPtr);
uploadImgurImageDoneCb();
}
Some notes about above code.
First is that there is an issue with WINC1500 Arduino Firmware during reception (explained later on), it seems like a bug that need to be confirm by further tests. Uploading is really smooth.
The variable AuthorizationDataImgur is similar to the Artik Authentication scheme, you have to generate a token by going to their web page after creating an account. Is only needed for posting to your user account, for public post is not needed. It has the form of
String AuthorizationDataImgur = "Authorization: Bearer " + imgur_token;
The it comes the function loadImageInJson() where the interesting stuff is.
The function is here
int loadImageInJson(void){
using namespace ArduinoJson;
StaticJsonBuffer<6000> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
int encodedLen = base64_enc_len(camera.getImgSize());
char *encoded = (char*)malloc(encodedLen*sizeof(char));
char *input = (char*)&imgBuffer[0];
uint16_t imgSize = camera.getImgSize();
base64_encode(encoded, input, imgSize);
root["image"] = encoded;
root["album"] = "YOUR_ALBUM_HERE";
root["type"] = "base64";
root["name"] = "testFlexduino.jpg";
root["title"] = "Flex";
memset((void *)tmpBuf, 0, sizeof(tmpBuf));
root.printTo((char *)tmpBuf, sizeof(tmpBuf));
int length = root.measureLength();
free(encoded);
return (length);
}
In order to use the Json library of Arduino, for the KDS project the ArduinoJson namespace need to be include, that might differ from arduino usage, the project was some names conflict, after that outh modification the code compiles and works as expected.
image need to be contained in the JSON field "image", it can be binary or base64, but the problem with binary is that json library is not able to parse, so then use base64 by calling base64_encode function from Base64 library, it takes the binary bits of picture, the size and the output buffer, but wait a moment, the library also gives you the amount of buffer required for the output buffer and so you only need to allocate it, just need to remember to free it please.
Under your account just create an album, it will give you the ID that you must write where it says YOUR_ALBUM_HERE.
Returning to the function sendImgurImageCb as its name suggest is a callback function when the picture is ready (a new feature I add recently). After you send the header to imgur, you need to send the image, but a problem I experience with winc1500 arduino library is that you need to code smart the transmission in order to not overflow the buffer, I am doing a for loop and taking care if transmit actually took place of 500 bytes chunks, it could be more since winc1500 buffer is 1400.
After that I handle the reception, which is very important since it contains the image url. The response is something similar to below json data.
{
"data":{
"id":"ISKuuBx",
"title":"FlexduinoLiveTest4",
"description":null,
"datetime":1475193627,
"type":"image/jpeg",
"animated":false,
"width":160,
"height":120,
"size":1844,
"viefavorite":false,
"nsfw":null,
"section":null,
"account_url":null,
"account_id":40218858,
"is_ad":false,
"in_gallery":false,
"deletehash":"cy5QerZnDDSqDvB",
"name":"",
"link":"http://i.imgur.com/ISKuuBx.jpg"
},
"success":true,
"status":200
}
Now the easy part of parsing the url from it using json. Wait, did I say easy, well it took me a day to figure out there is a bug with winc1500 library, I was having problems to parse the data using ArduinoJson library, after I realize the problem.
In the reception routine, after the 1400 bytes received it start to miss some bytes, then buffer continues and json ends "ok", at least it looks ok, but an inspection and debug shows a few bytes of data are missing.
Meantime I figure out the library problem, a workaround for this first demo was to extract the ID which is always below the 1400 byte boundary and build the url from it.
Connect it to Artik.
So far we have covered the Imgur image upload, most of the readers are perhaps familiarized with Artik cloud so I will not bored you with to much of it in terms of screens unless really necessary. Here is an upload "datapoint" of Image captured by Flexduino.
The Manifest for this to work is rarely simple, only the picture URL called picUrl.
Here is how it's done.
void uploadImgurImageDoneCb(void){
char url[40];
memset(url, 0, sizeof(url));
strcat(url, "http://i.imgur.com/");
strcat(url, ID);
strcat(url, ".jpg");
#if ARTIK_REST_CLIENT
client.connect(server, port);
delay(1000);
if (!client.connected()) {
Serial.println(" error connecting");
} else {
Serial.println("Sending data to Artik Cloud!");
client.println("POST /v1.1/messages HTTP/1.1");
client.println("Host: api.artik.cloud");
client.println("Accept: */*");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println(AuthorizationData);
// Automated POST data section
client.print("Content-Length: ");
// add the length
client.println(build_simple_msg(String("picUrl"), url, Strng));
client.println();
client.println(buf);
long int start = millis();
for(;;){
if(millis() - start >= 5000)
break;
while (client.available()) {
char c = client.read();
Serial.write(c);
}
}
}
#endif
}
The first part is the ugly workaround to reception winc1500 issue.
The else clause contains the Artik upload. I modify some of the example code to make the function build_simple_msg more practical to use, it accepts different types of data and the name of the variable.
The Gateway solution.A really new added feature to Flexduino environment specially for this Artik project is related to the Gateway. It's supposed a gateway should translate some protocols or intercommunicate things. Instead of having a bunch of sensor nodes trying to speech with Artik cloud independently, the idea here is to have a local network of sensor nodes that communicate with the gateway. The nodes actually might not "know" anything about Artik.
To continue the Flexduino saga of using Arduino libraries, this time Firmata library is used to implement the interface with sensors. The WiFi firmata version was really useful since it works with winc1500 and our Flexduino Suite.
The idea is to have several low cost sensors connected to Flexduino Gateway. A decision was made to use ESP8266 hardware with Arduino Firmata library. For the demonstration below the StandardFirmataWiFi.ino was loaded on esp-12, only a minor debug change was done to include firmata command prints on serial console for debugging purposes.
Here is a picture of test setup for esp8266 sensor node.
The Flexduino firmata firmware.
The firmware can be compiled with or without firmata by using a
#define USE_FIRMATA
In order to make the example as close as the StandardFirmataWiFi.ino sketch, the code #define comes handly since firmata example start the WiFi connection to AP and Flexduino previous code is under same project which also start the WiFi, this way we avoid conflicts.
A C++ library was write to interface with Firmata.cpp library, it grow up to connect to sensor Nodes too. Here is the header.
#include "Firmata.h"
extern char ssid[];
extern char pass[];
extern void printWifiStatus(void);
extern "C" {
// callback function types
typedef void (*appCallbackFunction)(byte, bool);
typedef void (*initCallbackFunction)(void);
}
class espNode {
public:
espNode(){ };
void espNodeInit();
void appSetDigitalPinCb(byte pin, appCallbackFunction appCb);
void setDigitalPin(int pin, bool state);
bool isPinOutputInv(int pin);
void attachDigPinCb(byte pin, appCallbackFunction pinCb);
void detachDigPinCb(byte pin);
int initEspIO(void);
bool setEspPinMode(byte pin, int mode);
void digitalWriteCallback(byte port, int value);
bool getIoLastReportedValue(byte pin);
protected:
static int _mask; // assume we only have PORT0
static int _state;
static int _old_inputs;
static appCallbackFunction _digitalPinCb[6];
};
class firmataApp : public espNode {
public:
inline firmataApp(){ };
firmataApp(char *ssid, char *pass);
void setup(void);
void process(void);
void sysexCallback(byte command, byte argc, byte *argv);
void hostConnectionCallback(byte state);
void initTransport(void);
void stringCallback(char *myString);
bool isReady(void);
void attachInitCb(initCallbackFunction initCb);
private:
static initCallbackFunction _initNodesCb;
static bool _init_done;
static char *_ssid; // your network SSID (name)
static char *_pass; // your network password
uint32_t _start;
uint8_t _init;
uint8_t _retry;
};
firmataApp firmApp(ssid, pass);
Two C++ classes does the job, firmataApp easy the implementation by connecting to Firmata.cpp, if you are familiarized with Firmata, some of the function names will be obvious since I try to keep them. For example initTransport is used to start the WiFi connection to AP. The constructor uses those parameters that are actually defined in main.c as
char ssid[] = "YOUR_SSID_HERE"; // your network SSID (name)
char pass[] = "YOU_PASS_HERE*"; // your network password
It was a little time consuming to debug the firmata, for some reason, after Firmata.begin(stream) is called it's not possible to send commands, it might be related to the fact that in order to commands be transmitted a periodic call to this piece of code is needed.
while (Firmata.available()) {
Firmata.processInput();
}
Above code resides in process() function which is called periodically in main loop.
In order to help detect when firmata interface is ready to process commands (actually send them), a couple of modifications were done as follows.
Since Flexduino is interested to control I/O of sensor Nodes, it directly implement a function to set the pin mode of those devices.
bool espNode::setEspPinMode(byte pin, int mode){
bool res = Firmata.sendSetPinMode(pin, mode);
if(res && (mode == INPUT)){
_mask |= (0x01 << pin);
}
return res;
}
int espNode::initEspIO(void){
bool res = setEspPinMode(nD2, OUTPUT);
if(res){
// set the blue led to OFF state
setDigitalPin(D2, OFF);
return 1;
}
return 0;
}
The above function initEspIO() is called periodically in process() method until it succeed by returning 1.
Since I have conducted the tests using ESP-12, this little module has a blue LED connected to GPIO2, so I started by configuring it as output. In a future release of the library might change this for a more general firmata command like version command.
But the idea is there and is easy to modify.
Notice how Flexduino keep track of input pins using a variable _mask.
You might notice that _mask variable was defined as static. For this example and due to firmata library capabilities, is only possible to connect with one sensor node acting as firmata server. About this some background first.
FirmataWiFi can be started as server or as client by defining stream as WiFiServerStream or WiFiClientStream respectively.
This demo took the path to behave as client and let devices be a server. That might not be the desired configuration but it serves to speed development by letting me to focus on Flexduino Gateway with Artik rather than esp8266 Arduino stuff.
One problem to connect to more than one sensor Node firmata "server" is that library really need a modification, it was not conceived to behave this way by it's creators, perhaps because originally was a Serial line peer to peer MIDI protocol.
The firmataApp library then will need some modifications to allow multiple clients, nonetheless all the work here is envisioned with that in mind.
The method attachInitCb of firmataApp allow to configure the sensor when code starts, that is not practical and is there only for some tests, it's a callback due to the aforementioned issue with firmata initialization, for example in main.cpp before calling firmApp.setup(); you can do
firmApp.attachInitCb(init_firmata_nodes);
where init_firmata_nodes is defined before as
void init_firmata_nodes(void){
Flexartik.initCallbacks();
}
and initCallBacks is
void artikLand::initCallbacks(void){
attachDigPinCb(nD4, [](byte pin, bool value){ \
return Flexartik.espDigIOPinSensor(pin, value); });
}
Notice how we define the callback. Following lambda style we avoid having a static C function and a second C++ class method approach.
The real callbacks.
Since we should be more interested in building a framework (of course with example demo!) that can be expanded or improved rather than connecting a bunch of sensors with no sense from a maintainability point of view to artik cloud, the real callbacks should come from artik itself or better say, a way need to exist to connect sensors and artik by means of gateway callbacks. We will get back to some of the espNode methods but first let's introduce another new addition to Flexduino (you are correct, this one is from scratch!)
Artik Library.
Before we presented a demo of sending image URL to artik using REST, since we are interested in "real time" control, websockets again to the rescue.
A C++ class library allow us to do a couple of nice things from/to artik. Let's see how the header library looks like
void webSocketArtikEvent(WStype_t type, uint8_t * payload, size_t length);
class artikLand : public firmataApp {
public:
artikLand(void);
void initCallbacks(void);
void send_status(bool stat);
void webSocketArtikEvent(WStype_t type, uint8_t * payload, size_t length);
void toggleLED(uint8_t state);
int build_simple_msg(String param, void* value, dtype type = Boolean);
void espDigIOPinSensor(byte pin, bool value);
char _buf[500];
private:
#if (FIRMATA_MODE == FIRMATA_CLIENT)
WiFiNode _node; // let's do a demo that connects to only one firmata server
#elif (FIRMATA_MODE == FIRMATA_SERVER)
WiFiNode _node[MAX_NODES];
#endif
WiFiNode *_nodePtr;
void send_request(void);
void send_request(int len);
void handleSmartLightAction(int ioNum, bool state);
void handleSmartDigitalSensorCfg(int ioNum, bool active);
void sendDigSensorState(int pin, bool value);
void process_incoming_msg(uint8_t *msg);
int build_group_msg(String group, String param, void* value, dtype type = Boolean);
int build_group_params_msg(String groupName, String param[], void* value[], dtype type[], int numParams);
};
artikLand Flexartik;
Some explanations follow.
webSocketArtikEvent function is a callback from websocket library. It follows the old style of static + class method which also works.
This callback allow to proceed similar to the .on method of nodeJs or javascript webservers, in this case we might execute some code when connection is established, drooped or data is received as follows.
void artikLand::webSocketArtikEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println("[WSc] Disconnected!\n");
break;
case WStype_CONNECTED:
{
Serial.print("[WSSc] Connected to url: ");
Serial.println((char *)payload);
String registerMessage = String();
registerMessage += String("{\"type\":\"register\", \"sdid\": \"");
registerMessage += device_id;
registerMessage += String("\", \"Authorization\":\"bearer ");
registerMessage += device_token;
registerMessage += String("\", \"cid\": \"1234567890\"}");
Serial.println("Register Message to send:");
Serial.println(registerMessage);
webSockClient.sendTXT(registerMessage.c_str(), strlen(registerMessage.c_str()));
}
break;
case WStype_TEXT:
Serial.print("[WSc] get text: ");
Serial.println((char *)payload);
process_incoming_msg(payload);
break;
case WStype_BIN:
Serial.print("[WSc] get binary length: ");
Serial.println(length);
break;
default:
break;
}
}
Authorization variables are defined as
String device_id = String("YOUR_DEVICE_ID");
String device_token = String("YOUR_DEVICE_TOKEN");
String AuthorizationData = "Authorization: Bearer " + device_token;
The Artik library supports two devices or better said two features on a single device. You can turn On/Off a digital output pin from artik cloud and keep track of last state, but also you can read digital value on a Input defined pin.
On the image capture and upload example, I already introduce a function that sends the json data to artik called build_simple_msg.
This function has two variants here, the class method build_group_msg and build_group_params_msg. The second one is the more complete in the sense that allows to send data as group with parameters. To understand better let's see how it looks the current manifest.
Under the Fields section we can see the SmartDigitalSensor and the SmartLight, both with same internal parameters. Under Actions we have cfgSmartDigSensor and setSmartLight, very similar structures too.
The idea is that theSmart Light can be controlled and see current (of course logged too) states, and the Smart Sensor can be configured and recorded.
Remember the initCallBacks method seen before, after Artik library handle that json received data you can do the same whenever required and controlled by Artik action.
Artik Handlers.
The websocket callback seen before call the artik method that process incoming json data, the method is shown below.
void artikLand::process_incoming_msg(uint8_t *msg){
using namespace ArduinoJson;
StaticJsonBuffer<1000> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((const char*)msg);
const char* type = root["type"];
Serial.print("type is ");
Serial.println(type);
if(!strncmp(type, "ping", strlen("ping"))){
// ping received
Serial.println("ping cmd received");
}else if(!strncmp(type, "action", strlen("action"))){
// action received
if (root.containsKey("data"))
{
JsonObject& data = root["data"];
JsonArray& actions = data["actions"];
const char *action_name = actions[0]["name"];
Serial.println(action_name);
uint8_t newState;
if(!strncmp(action_name, SET_ON, strlen(SET_ON))){
newState = 1;
Serial.println("Turn Light On");
toggleLED(newState);
}else if(!strncmp(action_name, SET_OFF, strlen(SET_OFF))){
newState = 0;
Serial.println("Turn Light Off");
toggleLED(newState);
}else if(!strncmp(action_name, SET_SMART_LIGHT, \
strlen(SET_SMART_LIGHT))){
Serial.println("set parameter on a Smart Light appliance");
JsonObject& param = actions[0]["parameters"];
bool OnState = param[LIGHT_STATE].as<bool>();
int ioNum = param[IO_NUM].as<int>();
handleSmartLightAction(ioNum, OnState);
}else if(!strncmp(action_name, CFG_SMART_DIG_SENSOR, \
strlen(CFG_SMART_DIG_SENSOR))){
Serial.println("set Configuration of Smart Digital Sensor");
JsonObject& param = actions[0]["parameters"];
bool active = param[ACTIVE].as<bool>();
int ioNum = param[IO_NUM].as<int>();
handleSmartDigitalSensorCfg(ioNum, active);
}else{
return;
}
}
}else{
Serial.print("received unknown message ");
Serial.println(type);
}
}
There you can see how comparison takes place, looking for the json tree, decoding and comparing to finally call the corresponding artik handler.
The two handlersd of interested are handleSmartDigitalSensorCfg and handleSmartLightAction.
Let discuss handleSmartLightAction first. The code is shown below.
void artikLand::handleSmartLightAction(int ioNum, bool state){
int len;
int pin = 1 << ioNum;
String params[2];
params[0] = String(STATE);
params[1] = String(IO_NUM);
void *values[2];
values[0] = &state;
values[1] = &ioNum;
dtype type[2];
type[0] = Boolean;
type[1] = Integer;
// some debug here
Serial.print("set SmartLight ");
Serial.print("pin ");
Serial.print(pin, BIN);
Serial.print(" state to ");
state ? Serial.println("ON") : Serial.println("OFF");
// the real action taken by Flexduino Gateway
//send data to firmata node
setDigitalPin(pin, state);
// response to artik
// now I should build a more complex response for the SmartLight state back to artik
len = build_group_params_msg(SMART_LIGHT, params, values, type, 2);
send_request(len);
}
The first part set the values that should receive the function build_group_params_msg but also decoding the pin value represented as a I/O PORT byte value. To set the pin value we call the espNode function setDigitalPin, since we make public espNode class to firmataApp and the last we make public to Artik class.
Here is what it does
void espNode::setDigitalPin(int pin, bool value){
if(isPinOutputInv(pin))
value ? _state &= ~pin : _state |= pin;
else
value ? _state |= pin : _state &= ~pin;
Serial.print("Set Digital Pin ");
Serial.println(_state, BIN);
Firmata.sendDigitalPort(PORT0, _state);
}
Remember the blue LED of esp8266, the hardware mandates the logic to turn it on as active Low, to keep active High used in Artik cloud the function isPinOutputInv was implemented. The reason we need to convert GPIO2 I/O pin number of 2 in a I/O PORT representation such 0b100 is that firmata library sendDigitalPort like to be feed that way.
After processed the new state is returned to Artik for record or log purposes.
Now the turn of handleSmartDigitalSensorCfg handler.
Basically what it does is to configure Firmata to report the Digital Input pin. Library internals allow to configure any PORT0 pin that can be used on esp8266 since we pass the ioNum number in the json data. Let's see how it works.
If json parameter "active" is equal 1, a callback is attached to the ioNum parameter number. Here is the code.
void artikLand::handleSmartDigitalSensorCfg(int ioNum, bool active){
// if active, then enable push notifications
if(active && (ioNum < 6)){
// it call the callback where value will be sent
Serial.println("attaching Digital Sensor callback");
attachDigPinCb(ioNum, [](byte pin, bool value){ \
return Flexartik.espDigIOPinSensor(pin, value); });
}else{
Serial.println("Detaching Digital Sensor callback");
bool state;
state = getIoLastReportedValue(ioNum);
detachDigPinCb(ioNum);
sendDigSensorState(ioNum, state);
}
}
If active = 0 the callback is detached. In the background here are some implementation details.
A static array of callbacks function pointers under espNode class private member data allow us to keep track of the callback attach/detach feature.
static appCallbackFunction _digitalPinCb[6];
In order to receive a callback in Artik library (I will show you after this), you first need to register a callback for Digital Inout PORT change using Firmata library, our firmataApp class do it in setup as follows.
Firmata.attach(DIGITAL_MESSAGE, [](byte port, int value) { \
return firmApp.digitalWriteCallback(port, value); } );
An is firmataApp method digitalWriteCallback in charge to decode and call the correct Artik Input pin callback, here is how it's done.
void espNode::digitalWriteCallback(byte port, int value){
#if DEBUG_FIRMATA
Serial.print("port = ");
Serial.println(port, BIN);
Serial.print("value = ");
Serial.println(value, BIN);
#endif
// now the handler using callbacks
int newValue = value & _mask;
int mask = _old_inputs^newValue;
if(mask){
_old_inputs = newValue;
for(byte j=0; j<6; j++){
if(mask & 0x1){
if(_digitalPinCb[j] == NULL)
break; // do not call NULL please
(newValue&0x1) ? _digitalPinCb[j](j, true) : _digitalPinCb[j](j, false);
}
mask = mask >> 1;
newValue = newValue >> 1;
}
}
}
We do some magic by masking the old and new port value and xoring them to determine which input change, this way if esp8266 send a Digital Port change message with more than one pin change, we can detect it and call both callbacks. Isn't nice?
Play Ball!
Before doing anything further upper layer aka. Android, we should test this using simulator.
Assuming you have use it I will go directly into the point, if not please read the docs.
Two simulator instances allow to fully test by using one to send the command and the other to listen incoming messages.
On simulator instance that sends, you need to connect using the token of your account, like this
java -jar device-simulator.jar -token=<YOUR_TOKEN_HERE>
then using ld command you can find the device Id that you will use when issuing the tell command.
To send a Smart Light command you do
tell <YOUR_DEVICE_ID> setSmartLight {"OnState":false,"ioNum":2}
On the second simulator started the same way, you need to do the ll command using same device Id, after it start to listen to commands. Very soon you will know it's working by seeing the ping command artik send to Flexduino.
WS -> {"type":"ping", "ts":1476036727775}
After the above smart light command you should see something like
WS -> {"mid":"YOUR_DEVICE_ID","data":{"SmartLight":{"state":false}},"ts":1475900194067,"sdtid":"YOUR_SDTID","cts":1475900194067,"uid":"USER_ID","mv":7,"sdid":"YOUR_DEVICE_ID"}
I was afraid to share here the real numbers.
Now send a configuration command to Smart Sensor and see it's response. Notice that Firmata immediately upon configuration sends a Notification with current value.
Here is the tell command.
tell <YOUR_DEVICE_ID> cfgSmartDigSensor {"active":true,"ioNum":4}
and the respectively sensor simulator output
WS -> {"mid":"MID_HERE","data":{"SmartDigitalSensor":{"ioNum":4,"state":false}},"ts":1476036801577,"sdtid":"SDTID_HERE","cts":1476036801577,"uid":"UID_HERE","mv":10,"sdid":"SDID_HERE"}
The push button on ESP-12 GPIO4 notification to Artik Cloud is demonstrated in a video below (sorry I could not edited for better presentation).
Remember the communication scheme is
ESP-12 <----- (firmata protocol) -------> Flexduino <------- (Websockets) -----> ARTIK.
Finally I convert the example code for Image upload into a C++ class, the file is called mm_iot.cpp and a extract of header is shown below.
void sendImageCb(void);
class mm_iot : public artikLand {
public:
void uploadImageUrlDoneCb(void);
int loadImageInJson(void);
void parseImgurUpload(char *json);
void sendImgurImageCb(void);
private:
char _id[10];
};
The method that does the Job to upload to Imgur you can guess, sendImgurImageCb and the other relevant method that uploads to Artik cloud the URL is uploadImageUrlDoneCb. Instead of the very first example in which this last method uses REST, now this class has access to Artik websockets methods to do the upload and it's actually very short, it only calls the Artik cloud method sendImgUrl(url); which is shown below.
void artikLand::sendImgUrl(char url[]){
// push notification of Image Url
int len;
len = build_simple_msg(IMAGE_URL, url, Strng);
send_request(len);
}
Now a quick video demonstration. Important to notice is that Artik connected applications can configure whether the GPIO notification (Push Button in my case) trigger the image capture and url upload. After that they should just sit to detect it.
Conclusions.
This project proves that Artik can be use for Smart applications that need more than simple data sharing, been able to connect with Media servers. The Gateway is a low cost solution for Home applications as well as Smart Cities. For example, an application case for smart city would be to have simple device that can upload a picture of a person purchasing with a credit card. by means of Artik, it';s possible to have a phone app that almost immediately receive the notifications and picture.
Other application is a smart traffic monitoring, users can connect to live pictures from red lights or other strategic places in order to avoid traffic jams.
Happy Multimediartik!
Comments