added arduino, modified build

This commit is contained in:
2020-02-02 15:28:36 -08:00
parent 0189d519c6
commit 6480bc593f
3583 changed files with 1305025 additions and 247 deletions

View File

@@ -0,0 +1,32 @@
= Temboo Library for Arduino =
This library allows an Arduino to connect to the Temboo service.
== License ==
Copyright 2017, Temboo Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific
language governing permissions and limitations under the License.
This library includes elements of the Paho MQTT client, used
with permission under the Eclipse Public License - v1.0:
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License v1.0:
http://www.eclipse.org/org/documents/edl-v10.php
The Eclipse MQTT Paho client source is available here:
http://www.eclipse.org/paho/

View File

@@ -0,0 +1,115 @@
/*
GetYahooWeatherReport
Demonstrates making a request to the Yahoo! Weather API using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
// the address for which a weather forecast will be retrieved
String ADDRESS_FOR_FORECAST = "104 Franklin St., New York NY 10013";
int numRuns = 1; // execution count, so that this doesn't run forever
int maxRuns = 10; // max number of times the Yahoo WeatherByAddress Choreo should be run
void setup() {
Serial.begin(9600);
// for debugging, wait until a serial console is connected
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// while we haven't reached the max number of runs...
if (numRuns <= maxRuns) {
// print status
Serial.println("Running GetWeatherByAddress - Run #" + String(numRuns++) + "...");
// create a TembooChoreo object to send a Choreo request to Temboo
TembooChoreo GetWeatherByAddressChoreo;
// invoke the Temboo client
GetWeatherByAddressChoreo.begin();
// add your temboo account info
GetWeatherByAddressChoreo.setAccountName(TEMBOO_ACCOUNT);
GetWeatherByAddressChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
GetWeatherByAddressChoreo.setAppKey(TEMBOO_APP_KEY);
// set the name of the choreo we want to run
GetWeatherByAddressChoreo.setChoreo("/Library/Yahoo/Weather/GetWeatherByAddress");
// set choreo inputs; in this case, the address for which to retrieve weather data
// the Temboo client provides standardized calls to 100+ cloud APIs
GetWeatherByAddressChoreo.addInput("Address", ADDRESS_FOR_FORECAST);
// add an output filter to extract the name of the city.
GetWeatherByAddressChoreo.addOutputFilter("city", "/rss/channel/yweather:location/@city", "Response");
// add an output filter to extract the current temperature
GetWeatherByAddressChoreo.addOutputFilter("temperature", "/rss/channel/item/yweather:condition/@temp", "Response");
// add an output filter to extract the date and time of the last report.
GetWeatherByAddressChoreo.addOutputFilter("date", "/rss/channel/item/yweather:condition/@date", "Response");
// run the choreo
GetWeatherByAddressChoreo.run();
// when the choreo results are available, print them to the serial monitor
while(GetWeatherByAddressChoreo.available()) {
char c = GetWeatherByAddressChoreo.read();
Serial.print(c);
}
GetWeatherByAddressChoreo.close();
}
Serial.println("Waiting...");
Serial.println("");
delay(30000); // wait 30 seconds between GetWeatherByAddress calls
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,4 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,173 @@
/*
ReadATweet
Demonstrates retrieving the most recent Tweet from a user's home timeline
using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
In order to run this sketch, you'll need to register an application using
the Twitter dev console at https://dev.twitter.com. After creating the
app, you'll find OAuth credentials for that application under the "OAuth Tool" tab.
Substitute these values for the placeholders below.
This example assumes basic familiarity with Arduino sketches, and that your Yún
is connected to the Internet.
Want to use another social API with your Arduino Yún? We've got Facebook,
Google+, Instagram, Tumblr and more in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
const String TWITTER_ACCESS_TOKEN = "your-twitter-access-token";
const String TWITTER_ACCESS_TOKEN_SECRET = "your-twitter-access-token-secret";
const String TWITTER_CONSUMER_KEY = "your-twitter-consumer-key";
const String TWITTER_CONSUMER_SECRET = "your-twitter-consumer-secret";
int numRuns = 1; // execution count, so this doesn't run forever
int maxRuns = 10; // the max number of times the Twitter HomeTimeline Choreo should run
void setup() {
Serial.begin(9600);
// For debugging, wait until a serial console is connected.
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// while we haven't reached the max number of runs...
if (numRuns <= maxRuns) {
Serial.println("Running ReadATweet - Run #" + String(numRuns++));
TembooChoreo HomeTimelineChoreo;
// invoke the Temboo client.
// NOTE that the client must be reinvoked, and repopulated with
// appropriate arguments, each time its run() method is called.
HomeTimelineChoreo.begin();
// set Temboo account credentials
HomeTimelineChoreo.setAccountName(TEMBOO_ACCOUNT);
HomeTimelineChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
HomeTimelineChoreo.setAppKey(TEMBOO_APP_KEY);
// tell the Temboo client which Choreo to run (Twitter > Timelines > HomeTimeline)
HomeTimelineChoreo.setChoreo("/Library/Twitter/Timelines/HomeTimeline");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Twitter/Timelines/HomeTimeline/
// for complete details about the inputs for this Choreo
HomeTimelineChoreo.addInput("Count", "1"); // the max number of Tweets to return from each request
HomeTimelineChoreo.addInput("AccessToken", TWITTER_ACCESS_TOKEN);
HomeTimelineChoreo.addInput("AccessTokenSecret", TWITTER_ACCESS_TOKEN_SECRET);
HomeTimelineChoreo.addInput("ConsumerKey", TWITTER_CONSUMER_KEY);
HomeTimelineChoreo.addInput("ConsumerSecret", TWITTER_CONSUMER_SECRET);
// next, we'll define two output filters that let us specify the
// elements of the response from Twitter that we want to receive.
// see the examples at http://www.temboo.com/arduino
// for more on using output filters
// we want the text of the tweet
HomeTimelineChoreo.addOutputFilter("tweet", "/[1]/text", "Response");
// and the name of the author
HomeTimelineChoreo.addOutputFilter("author", "/[1]/user/screen_name", "Response");
// tell the Process to run and wait for the results. The
// return code will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = HomeTimelineChoreo.run();
// a response code of 0 means success; print the API response
if(returnCode == 0) {
String author; // a String to hold the tweet author's name
String tweet; // a String to hold the text of the tweet
// choreo outputs are returned as key/value pairs, delimited with
// newlines and record/field terminator characters, for example:
// Name1\n\x1F
// Value1\n\x1E
// Name2\n\x1F
// Value2\n\x1E
// see the examples at http://www.temboo.com/arduino for more details
// we can read this format into separate variables, as follows:
while(HomeTimelineChoreo.available()) {
// read the name of the output item
String name = HomeTimelineChoreo.readStringUntil('\x1F');
name.trim();
// read the value of the output item
String data = HomeTimelineChoreo.readStringUntil('\x1E');
data.trim();
// assign the value to the appropriate String
if (name == "tweet") {
tweet = data;
} else if (name == "author") {
author = data;
}
}
Serial.println("@" + author + " - " + tweet);
} else {
// there was an error
// print the raw output from the choreo
while(HomeTimelineChoreo.available()) {
char c = HomeTimelineChoreo.read();
Serial.print(c);
}
}
HomeTimelineChoreo.close();
}
Serial.println("Waiting...");
delay(90000); // wait 90 seconds between HomeTimeline calls
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,4 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,138 @@
/*
SendATweet
Demonstrates sending a tweet via a Twitter account using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
In order to run this sketch, you'll need to register an application using
the Twitter dev console at https://dev.twitter.com. Note that since this
sketch creates a new tweet, your application will need to be configured with
read+write permissions. After creating the app, you'll find OAuth credentials
for that application under the "OAuth Tool" tab. Substitute these values for
the placeholders below.
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Want to use another social API with your Arduino Yún? We've got Facebook,
Google+, Instagram, Tumblr and more in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
const String TWITTER_ACCESS_TOKEN = "your-twitter-access-token";
const String TWITTER_ACCESS_TOKEN_SECRET = "your-twitter-access-token-secret";
const String TWITTER_CONSUMER_KEY = "your-twitter-consumer-key";
const String TWITTER_CONSUMER_SECRET = "your-twitter-consumer-secret";
int numRuns = 1; // execution count, so this sketch doesn't run forever
int maxRuns = 3; // the max number of times the Twitter Update Choreo should run
void setup() {
Serial.begin(9600);
// for debugging, wait until a serial console is connected
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// only try to send the tweet if we haven't already sent it successfully
if (numRuns <= maxRuns) {
Serial.println("Running SendATweet - Run #" + String(numRuns++) + "...");
// define the text of the tweet we want to send
String tweetText("My Arduino Yun has been running for " + String(millis()) + " milliseconds.");
TembooChoreo StatusesUpdateChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked, and repopulated with
// appropriate arguments, each time its run() method is called.
StatusesUpdateChoreo.begin();
// set Temboo account credentials
StatusesUpdateChoreo.setAccountName(TEMBOO_ACCOUNT);
StatusesUpdateChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
StatusesUpdateChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Twitter > Tweets > StatusesUpdate)
StatusesUpdateChoreo.setChoreo("/Library/Twitter/Tweets/StatusesUpdate");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Twitter/Tweets/StatusesUpdate/
// for complete details about the inputs for this Choreo
// add the Twitter account information
StatusesUpdateChoreo.addInput("AccessToken", TWITTER_ACCESS_TOKEN);
StatusesUpdateChoreo.addInput("AccessTokenSecret", TWITTER_ACCESS_TOKEN_SECRET);
StatusesUpdateChoreo.addInput("ConsumerKey", TWITTER_CONSUMER_KEY);
StatusesUpdateChoreo.addInput("ConsumerSecret", TWITTER_CONSUMER_SECRET);
// and the tweet we want to send
StatusesUpdateChoreo.addInput("StatusUpdate", tweetText);
// tell the Process to run and wait for the results. The
// return code (returnCode) will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = StatusesUpdateChoreo.run();
// a return code of zero (0) means everything worked
if (returnCode == 0) {
Serial.println("Success! Tweet sent!");
} else {
// a non-zero return code means there was an error
// read and print the error message
while (StatusesUpdateChoreo.available()) {
char c = StatusesUpdateChoreo.read();
Serial.print(c);
}
}
StatusesUpdateChoreo.close();
// do nothing for the next 90 seconds
Serial.println("Waiting...");
delay(90000);
}
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,4 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,166 @@
/*
SendAnEmail
Demonstrates sending an email via a Google Gmail account using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and tutorials at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
Instructions:
1. Create a Temboo account: http://www.temboo.com
2. Retrieve your Temboo application details: http://www.temboo.com/account/applications
3. Replace the values in the TembooAccount.h tab with your Temboo application details
4. You'll also need a Gmail account. Update the placeholder Gmail address in the code
below with your own details.
https://www.gmail.com
5. Once you have a Gmail account, turn on 2-step authentication, and create an application-specific
password to allow Temboo to access your Google account: https://www.google.com/landing/2step/.
6. After you've enabled 2-Step authentication, you'll need to create an App Password:
https://security.google.com/settings/security/apppasswords
7. In the "Select app" dropdown menu, choose "Other", and give your app a name (e.g., TembooApp).
8. Click "Generate". You'll be given a 16-digit passcode that can be used to access your Google Account from Temboo.
9. Copy and paste this password into the code below, updating the GMAIL_APP_PASSWORD variable
10. Upload the sketch to your Arduino Yún and open the serial monitor
NOTE: You can test this Choreo and find the latest instructions on our website:
https://temboo.com/library/Library/Google/Gmail/SendEmail
You can also find an in-depth version of this example here:
https://temboo.com/arduino/yun/send-an-email
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
// your Gmail username, formatted as a complete email address, eg "bob.smith@gmail.com"
const String GMAIL_USER_NAME = "xxxxxxxxxx";
// your application specific password (see instructions above)
const String GMAIL_APP_PASSWORD = "xxxxxxxxxx";
// the email address you want to send the email to, eg "jane.doe@temboo.com"
const String TO_EMAIL_ADDRESS = "xxxxxxxxxx";
// a flag to indicate whether we've tried to send the email yet or not
boolean attempted = false;
void setup() {
Serial.begin(9600);
// for debugging, wait until a serial console is connected
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// only try to send the email if we haven't already tried
if (!attempted) {
Serial.println("Running SendAnEmail...");
TembooChoreo SendEmailChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked, and repopulated with
// appropriate arguments, each time its run() method is called.
SendEmailChoreo.begin();
// set Temboo account credentials
SendEmailChoreo.setAccountName(TEMBOO_ACCOUNT);
SendEmailChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
SendEmailChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Google > Gmail > SendEmail)
SendEmailChoreo.setChoreo("/Library/Google/Gmail/SendEmail");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Google/Gmail/SendEmail/
// for complete details about the inputs for this Choreo
// the first input is your Gmail email address.
SendEmailChoreo.addInput("Username", GMAIL_USER_NAME);
// next is your application specific password
SendEmailChoreo.addInput("Password", GMAIL_APP_PASSWORD);
// who to send the email to
SendEmailChoreo.addInput("ToAddress", TO_EMAIL_ADDRESS);
// then a subject line
SendEmailChoreo.addInput("Subject", "ALERT: Greenhouse Temperature");
// next comes the message body, the main content of the email
SendEmailChoreo.addInput("MessageBody", "Hey! The greenhouse is too cold!");
// tell the Choreo to run and wait for the results. The
// return code (returnCode) will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = SendEmailChoreo.run();
// a return code of zero (0) means everything worked
if (returnCode == 0) {
Serial.println("Success! Email sent!");
} else {
// a non-zero return code means there was an error
// read and print the error message
while (SendEmailChoreo.available()) {
char c = SendEmailChoreo.read();
Serial.print(c);
}
}
SendEmailChoreo.close();
// set the flag showing we've tried
attempted = true;
}
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,4 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,154 @@
/*
SendAnSMS
Demonstrates sending an SMS via Twilio using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
Since this sketch uses Twilio to send the SMS, you'll also need a valid
Twilio account. You can create one for free at https://www.twilio.com.
The sketch needs your Twilio phone number, along with
the Account SID and Auth Token you get when you register with Twilio.
Make sure to use the Account SID and Auth Token from your Twilio Dashboard
(not your test credentials from the Dev Tools panel).
Also note that if you're using a free Twilio account, you'll need to verify
the phone number to which messages are being sent by going to twilio.com and following
the instructions under the "Numbers > Verified Caller IDs" tab (this restriction
doesn't apply if you have a paid Twilio account).
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
// the Account SID from your Twilio account
const String TWILIO_ACCOUNT_SID = "xxxxxxxxxx";
// the Auth Token from your Twilio account
const String TWILIO_AUTH_TOKEN = "xxxxxxxxxx";
// your Twilio phone number, e.g., "+1 555-222-1212"
const String TWILIO_NUMBER = "xxxxxxxxxx";
// the number to which the SMS should be sent, e.g., "+1 555-222-1212"
const String RECIPIENT_NUMBER = "xxxxxxxxxx";
// a flag to indicate whether we've attempted to send the SMS yet or not
boolean attempted = false;
void setup() {
Serial.begin(9600);
// for debugging, wait until a serial console is connected
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// only try to send the SMS if we haven't already sent it successfully
if (!attempted) {
Serial.println("Running SendAnSMS...");
// we need a Process object to send a Choreo request to Temboo
TembooChoreo SendSMSChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked and repopulated with
// appropriate arguments each time its run() method is called.
SendSMSChoreo.begin();
// set Temboo account credentials
SendSMSChoreo.setAccountName(TEMBOO_ACCOUNT);
SendSMSChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
SendSMSChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Twilio > SMSMessages > SendSMS)
SendSMSChoreo.setChoreo("/Library/Twilio/SMSMessages/SendSMS");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Twilio/SMSMessages/SendSMS/
// for complete details about the inputs for this Choreo
// the first input is a your AccountSID
SendSMSChoreo.addInput("AccountSID", TWILIO_ACCOUNT_SID);
// next is your Auth Token
SendSMSChoreo.addInput("AuthToken", TWILIO_AUTH_TOKEN);
// next is your Twilio phone number
SendSMSChoreo.addInput("From", TWILIO_NUMBER);
// next, what number to send the SMS to
SendSMSChoreo.addInput("To", RECIPIENT_NUMBER);
// finally, the text of the message to send
SendSMSChoreo.addInput("Body", "Hey, there! This is a message from your Arduino Yun!");
// tell the Process to run and wait for the results. The
// return code (returnCode) will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = SendSMSChoreo.run();
// a return code of zero (0) means everything worked
if (returnCode == 0) {
Serial.println("Success! SMS sent!");
} else {
// a non-zero return code means there was an error
// read and print the error message
while (SendSMSChoreo.available()) {
char c = SendSMSChoreo.read();
Serial.print(c);
}
}
SendSMSChoreo.close();
// set the flag indicatine we've tried once.
attempted=true;
}
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,5 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,200 @@
/*
SendDataToGoogleSpreadsheet
Demonstrates appending a row of data to a Google spreadsheet using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and tutorials at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
Instructions:
1. Create a Temboo account: http://www.temboo.com
2. Retrieve your Temboo application details: http://www.temboo.com/account/applications
3. Replace the values in the TembooAccount.h tab with your Temboo application details
4. You'll also need a Google Spreadsheet that includes a title in the first row
of each column that data will be written to. This example assumes there are two columns.
The first column is the time (in milliseconds) that the row was appended, and the second
column is a sensor value. In other words, your spreadsheet should look like:
Time | Sensor Value |
------+-----------------
| |
5. Google Spreadsheets requires you to authenticate via OAuth. Follow the steps
in the link below to find your ClientID, ClientSecret, and RefreshToken, and then
use those values to overwrite the placeholders in the code below.
https://temboo.com/library/Library/Google/OAuth/
For the scope field, you need to use: https://www.googleapis.com/auth/spreadsheets
Here's a video outlines how Temboo helps with the OAuth process:
https://www.temboo.com/videos#oauthchoreos
And here's a more in-depth version of this example on our website:
https://temboo.com/arduino/yun/update-google-spreadsheet
6. Next, upload the sketch to your Arduino Yún and open the serial monitor
Note: you can test this Choreo and find the latest instructions on our website:
https://temboo.com/library/Library/Google/Sheets/AppendValues/
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information,
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
// the clientID found in Google's Developer Console under API Manager > Credentials
const String CLIENT_ID = "your-client-id";
// the clientSecret found in Google's Developer Console under API Manager > Credentials
const String CLIENT_SECRET = "your-client-secret";
// returned after running FinalizeOAuth
const String REFRESH_TOKEN = "your-oauth-refresh-token";
// The ID of the spreadsheet you want to send data to
// which can be found in the URL when viewing your spreadsheet at Google. For example,
// the ID in the URL below is: "1tvFW2n-xFFJCE1q5j0HTetOsDhhgw7_998_K4sFtk"
// Sample URL: https://docs.google.com/spreadsheets/d/1tvFW2n-xFFJCE1q5j0HTetOsDhhgw7_998_K4sFtk/edit
const String SPREADSHEET_ID = "your-spreadsheet-id";
const unsigned long RUN_INTERVAL_MILLIS = 60000; // how often to run the Choreo (in milliseconds)
// the last time we ran the Choreo
// (initialized to 60 seconds ago so the
// Choreo is run immediately when we start up)
unsigned long lastRun = (unsigned long)-60000;
void setup() {
// for debugging, wait until a serial console is connected
Serial.begin(9600);
delay(4000);
while(!Serial);
Serial.print("Initializing the bridge...");
Bridge.begin();
Serial.println("Done");
}
void loop()
{
// get the number of milliseconds this sketch has been running
unsigned long now = millis();
// run again if it's been 60 seconds since we last ran
if (now - lastRun >= RUN_INTERVAL_MILLIS) {
// remember 'now' as the last time we ran the choreo
lastRun = now;
Serial.println("Getting sensor value...");
// get the value we want to append to our spreadsheet
unsigned long sensorValue = getSensorValue();
Serial.println("Appending value to spreadsheet...");
// we need a Process object to send a Choreo request to Temboo
TembooChoreo AppendValuesChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked and repopulated with
// appropriate arguments each time its run() method is called.
AppendValuesChoreo.begin();
// set Temboo account credentials
AppendValuesChoreo.setAccountName(TEMBOO_ACCOUNT);
AppendValuesChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
AppendValuesChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Google > Sheets > AppendValues)
AppendValuesChoreo.setChoreo("/Library/Google/Sheets/AppendValues");
// set the required Choreo inputs
// see https://www.temboo.com/library/Library/Google/Sheets/AppendValues/
// for complete details about the inputs for this Choreo
// your Google application client ID
AppendValuesChoreo.addInput("ClientID", CLIENT_ID);
// your Google application client secret
AppendValuesChoreo.addInput("ClientSecret", CLIENT_SECRET);
// your Google OAuth refresh token
AppendValuesChoreo.addInput("RefreshToken", REFRESH_TOKEN);
// the ID of the spreadsheet you want to append to
AppendValuesChoreo.addInput("SpreadsheetID", SPREADSHEET_ID);
// convert the time and sensor values to a comma separated string
String rowData = "[[\"" + String(now) + "\", \"" + String(sensorValue) + "\"]]";
// add the RowData input item
AppendValuesChoreo.addInput("Values", rowData);
// run the Choreo and wait for the results
// The return code (returnCode) will indicate success or failure
unsigned int returnCode = AppendValuesChoreo.run();
// return code of zero (0) means success
if (returnCode == 0) {
Serial.println("Success! Appended " + rowData);
Serial.println("");
} else {
// return code of anything other than zero means failure
// read and display any error messages
while (AppendValuesChoreo.available()) {
char c = AppendValuesChoreo.read();
Serial.print(c);
}
}
AppendValuesChoreo.close();
}
}
// this function simulates reading the value of a sensor
unsigned long getSensorValue() {
return analogRead(A0);
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,5 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,97 @@
// Copyright 2017, Temboo Inc.
#include <Process.h>
void setup() {
// initialize the Bridge
Bridge.begin();
Serial.begin(9600);
Process p;
//intro message
Serial.println("**** Temboo Cloud Controls ****\n");
// update the package list
Serial.print("Updating package listings...");
p.runShellCommand("opkg update");
int returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed. Make sure your device is connected to the internet properly.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// upgrade the Temboo package
Serial.print("Updating Temboo...");
p.runShellCommand("opkg install http://downloads.arduino.cc/openwrtyun/1/packages/temboo_1.4.0-1_ar71xx.ipk");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// install python openssl to allow for for ssl connections
Serial.print("Installing python-openssl...");
p.runShellCommand("opkg install python-openssl");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// Installing twisted web to work with CoAP gateway
Serial.print("Installing twisted-web...");
p.runShellCommand("opkg install twisted-web");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// Configuring zope
Serial.print("Configuring zope...");
p.runShellCommand("touch /usr/lib/python2.7/site-packages/zope/__init__.py");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
Serial.println("Update Complete - your Yun is ready for Cloud Controls!");
}
void loop() {
// do nothing
}

View File

@@ -0,0 +1,5 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,171 @@
/*
ToxicFacilitiesSearch
Demonstrates making a request to the Envirofacts API using Temboo from an Arduino Yún.
This example retrieves the names and addresses of EPA-regulated facilities in the
Toxins Release Inventory (TRI) database within a given zip code.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
// the zip code to search for toxin-emitting facilities
String US_ZIP_CODE = "11215";
int numRuns = 1; // execution count, so that this doesn't run forever
int maxRuns = 10; // max number of times the Envirofacts FacilitiesSearch Choreo should be run
void setup() {
Serial.begin(9600);
// for debugging, wait until a serial console is connected
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// while we haven't reached the max number of runs...
if (numRuns <= maxRuns) {
// print status
Serial.println("Running ToxicFacilitiesSearch - Run #" + String(numRuns++) + "...");
// we need a Process object to send a Choreo request to Temboo
TembooChoreo FacilitiesSearchByZipChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked and repopulated with
// appropriate arguments each time its run() method is called.
FacilitiesSearchByZipChoreo.begin();
// set Temboo account credentials
FacilitiesSearchByZipChoreo.setAccountName(TEMBOO_ACCOUNT);
FacilitiesSearchByZipChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
FacilitiesSearchByZipChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (EnviroFacts > Toxins > FacilitiesSearchByZip)
FacilitiesSearchByZipChoreo.setChoreo("/Library/EnviroFacts/Toxins/FacilitiesSearchByZip");
// set choreo inputs; in this case, the US zip code for which to retrieve toxin release data
// the Temboo client provides standardized calls to 100+ cloud APIs
FacilitiesSearchByZipChoreo.addInput("Zip", US_ZIP_CODE);
// specify two output filters, to help simplify the Envirofacts API results.
// see the tutorials on using Temboo SDK output filters at http://www.temboo.com/arduino
FacilitiesSearchByZipChoreo.addOutputFilter("fac", "FACILITY_NAME", "Response");
FacilitiesSearchByZipChoreo.addOutputFilter("addr", "STREET_ADDRESS", "Response");
// run the choreo
unsigned int returnCode = FacilitiesSearchByZipChoreo.run();
if (returnCode == 0) {
String facilities;
String addresses;
// when the choreo results are available, process them.
// the output filters we specified will return comma delimited
// lists containing the name and street address of the facilities
// located in the specified zip code.
while(FacilitiesSearchByZipChoreo.available()) {
String name = FacilitiesSearchByZipChoreo.readStringUntil('\x1F');
name.trim();
String data = FacilitiesSearchByZipChoreo.readStringUntil('\x1E');
data.trim();
if (name == "fac") {
facilities = data;
} else if (name == "addr") {
addresses = data;
}
}
FacilitiesSearchByZipChoreo.close();
// parse the comma delimited lists of facilities to join the
// name with the address and print it to the serial monitor
if (facilities.length() > 0) {
int i = -1;
int facilityStart = 0;
int addressStart = 0;
String facility;
String address;
do {
i = facilities.indexOf(',', facilityStart);
if (i >= 0) {
facility = facilities.substring(facilityStart, i);
facilityStart = i + 1;
}
i = addresses.indexOf(',', addressStart);
if (i >= 0) {
address = addresses.substring(addressStart, i);
addressStart = i + 1;
}
if (i >= 0) {
printResult(facility, address);
}
}while (i >= 0);
facility = facilities.substring(facilityStart);
address = addresses.substring(addressStart);
printResult(facility, address);
} else {
Serial.println("No facilities found in zip code " + US_ZIP_CODE);
}
} else {
while(FacilitiesSearchByZipChoreo.available()) {
char c = FacilitiesSearchByZipChoreo.read();
Serial.print(c);
}
}
}
Serial.println("Waiting...");
Serial.println("");
delay(30000); // wait 30 seconds between calls
}
// a simple utility function, to output the facility name and address in the serial monitor.
void printResult(String facility, String address) {
Serial.print(facility);
Serial.print(" - ");
Serial.println(address);
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,5 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,132 @@
/*
UpdateFacebookStatus
Demonstrates sending a Facebook status update using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
In order to run this sketch, you'll need to register an application using
the Facebook dev console at https://developers.facebook.com/apps -- after creating
the app, log in to Temboo and visit https://www.temboo.com/library/Library/Facebook/Publishing/SetStatus/
to use our OAuth Wizard (or OAuth Choreos) to obtain a Facebook access token.
Substitute your access token for the placeholder value of FACEBOOK_ACCESS_TOKEN below.
This example assumes basic familiarity with Arduino sketches, and that your Yún
is connected to the Internet.
Want to use another social API with your Arduino Yún? We've got Twitter, Google+,
Instagram, Tumblr and more in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information,
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use a #define statement to specify this value in a .h file.
// the Facebook Access Token, which can be obtained using the Temboo OAuth Wizard or Choreos
const String FACEBOOK_ACCESS_TOKEN = "xxxxxxxxxx";
int numRuns = 1; // execution count, so this sketch doesn't run forever
int maxRuns = 10; // the max number of times the Facebook SetStatus Choreo should run
void setup() {
Serial.begin(9600);
// For debugging, wait until a serial console is connected.
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop() {
// while we haven't reached the max number of runs...
if (numRuns <= maxRuns) {
// print status
Serial.println("Running UpdateFacebookStatus - Run #" + String(numRuns++) + "...");
// Define the status message we want to post on Facebook; since Facebook
// doesn't allow duplicate status messages, we'll include a changing value.
String statusMsg = "My Arduino Yun has been running for " + String(millis()) + " milliseconds!";
// define the Process that will be used to call the "temboo" client
TembooChoreo SetStatusChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked and repopulated with
// appropriate arguments each time its run() method is called.
SetStatusChoreo.begin();
// set Temboo account credentials
SetStatusChoreo.setAccountName(TEMBOO_ACCOUNT);
SetStatusChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
SetStatusChoreo.setAppKey(TEMBOO_APP_KEY);
// tell the Temboo client which Choreo to run (Facebook > Publishing > SetStatus)
SetStatusChoreo.setChoreo("/Library/Facebook/Publishing/SetStatus");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Facebook/Publishing/SetStatus/
// for complete details about the inputs for this Choreo
SetStatusChoreo.addInput("AccessToken", FACEBOOK_ACCESS_TOKEN);
SetStatusChoreo.addInput("Message", statusMsg);
// tell the Process to run and wait for the results. The
// return code (returnCode) will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = SetStatusChoreo.run();
// print the response code and API response.
Serial.println("Response code: " + String(returnCode));
// note that in this case, we're just printing the raw response from Facebook.
// see the examples on using Temboo SDK output filters at http://www.temboo.com/arduino
// for information on how to filter this data
while(SetStatusChoreo.available()) {
char c = SetStatusChoreo.read();
Serial.print(c);
}
SetStatusChoreo.close();
}
Serial.println("Waiting...");
Serial.println("");
delay(30000); // wait 30 seconds between SetStatus calls
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,5 @@
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key

View File

@@ -0,0 +1,208 @@
/*
UploadToDropbox
Demonstrates uploading a file to a Dropbox account using Temboo from an Arduino Yún.
Check out the latest Arduino & Temboo examples and support docs at http://www.temboo.com/arduino
A Temboo account and application key are necessary to run all Temboo examples.
If you don't already have one, you can register for a free Temboo account at
http://www.temboo.com
You'll also need a valid Dropbox app and accompanying OAuth credentials.
To create a Dropbox app, visit https://www.dropbox.com/developers/apps and
do the following:
1. Create a "Dropbox API app"
2. Select "Files and datastores"
3. Select "Yes - my app only needs access to the files it creates."
Once you've created your app, follow the instructions at
https://www.temboo.com/library/Library/Dropbox/OAuth/ to run the Initialize and Finalize
OAuth Choreos. These Choreos complete the OAuth handshake and retrieve your Dropbox OAuth access tokens.
This example assumes basic familiarity with Arduino sketches, and that your Yún is connected
to the Internet.
Looking for another API to use with your Arduino Yún? We've got over 100 in our Library!
This example code is in the public domain.
*/
#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information
// as described in the footer comment below
/*** SUBSTITUTE YOUR VALUES BELOW: ***/
// Note that for additional security and reusability, you could
// use #define statements to specify these values in a .h file.
// your Dropbox app key, available on the Dropbox developer console after registering an app
const String DROPBOX_APP_KEY = "xxxxxxxxxx";
// your Dropbox app secret, available on the Dropbox developer console after registering an app
const String DROPBOX_APP_SECRET = "xxxxxxxxxx";
// your Dropbox access token, which is returned by the FinalizeOAuth Choreo
const String DROPBOX_ACCESS_TOKEN = "xxxxxxxxxx";
// your Dropbox access token secret, which is returned by the FinalizeOAuth Choreo
const String DROPBOX_ACCESS_TOKEN_SECRET = "xxxxxxxxxx";
boolean success = false; // a flag to indicate whether we've uploaded the file yet
void setup() {
Serial.begin(9600);
// For debugging, wait until a serial console is connected.
delay(4000);
while(!Serial);
Bridge.begin();
}
void loop()
{
// only try to upload the file if we haven't already done so
if (!success) {
Serial.println("Base64 encoding data to upload...");
// base64 encode the data to upload
String base64EncodedData = base64Encode("Hello, Arduino!");
Serial.println("Uploading data to Dropbox...");
// we need a Process object to send a Choreo request to Temboo
TembooChoreo UploadFileChoreo;
// invoke the Temboo client
// NOTE that the client must be reinvoked and repopulated with
// appropriate arguments each time its run() method is called.
UploadFileChoreo.begin();
// set Temboo account credentials
UploadFileChoreo.setAccountName(TEMBOO_ACCOUNT);
UploadFileChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
UploadFileChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Dropbox > FilesAndMetadata > UploadFile)
UploadFileChoreo.setChoreo("/Library/Dropbox/FilesAndMetadata/UploadFile");
// set the required choreo inputs
// see https://www.temboo.com/library/Library/Dropbox/FilesAndMetadata/UploadFile/
// for complete details about the inputs for this Choreo
// first specify the name of the file to create/update on Dropbox
UploadFileChoreo.addInput("FileName", "ArduinoTest.txt");
// next, the root folder on Dropbox relative to which the file path is specified.
// to work with the Dropbox app you created earlier, this should be left as "sandbox"
// if your Dropbox app has full access to your files, specify "dropbox"
UploadFileChoreo.addInput("Root","sandbox");
// next, the Base64 encoded file data to upload
UploadFileChoreo.addInput("FileContents", base64EncodedData);
// finally, the Dropbox OAuth credentials defined above
UploadFileChoreo.addInput("AppSecret", DROPBOX_APP_SECRET);
UploadFileChoreo.addInput("AccessToken", DROPBOX_ACCESS_TOKEN);
UploadFileChoreo.addInput("AccessTokenSecret", DROPBOX_ACCESS_TOKEN_SECRET);
UploadFileChoreo.addInput("AppKey", DROPBOX_APP_KEY);
// tell the Process to run and wait for the results. The
// return code (returnCode) will tell us whether the Temboo client
// was able to send our request to the Temboo servers
unsigned int returnCode = UploadFileChoreo.run();
// a return code of zero (0) means everything worked
if (returnCode == 0) {
Serial.println("Success! File uploaded!");
success = true;
} else {
// a non-zero return code means there was an error
Serial.println("Uh-oh! Something went wrong!");
}
// print out the full response to the serial monitor in all
// cases, just for debugging
while (UploadFileChoreo.available()) {
char c = UploadFileChoreo.read();
Serial.print(c);
}
UploadFileChoreo.close();
Serial.println("Waiting...");
}
delay(30000); // wait 30 seconds between upload attempts
}
/*
A utility function to Base64 encode the specified string
by calling a Temboo Utilities Choreo.
*/
String base64Encode(String toEncode) {
// we need a Process object to send a Choreo request to Temboo
TembooChoreo Base64EncodeChoreo;
// invoke the Temboo client
Base64EncodeChoreo.begin();
// set Temboo account credentials
Base64EncodeChoreo.setAccountName(TEMBOO_ACCOUNT);
Base64EncodeChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
Base64EncodeChoreo.setAppKey(TEMBOO_APP_KEY);
// identify the Temboo Library choreo to run (Utilities > Encoding > Base64Encode)
Base64EncodeChoreo.setChoreo("/Library/Utilities/Encoding/Base64Encode");
// set choreo inputs
Base64EncodeChoreo.addInput("Text", toEncode);
// run the choreo
Base64EncodeChoreo.run();
// read in the choreo results, and return the "Base64EncodedText" output value.
// see http://www.temboo.com/arduino for more details on using choreo outputs.
while(Base64EncodeChoreo.available()) {
// read the name of the output item
String name = Base64EncodeChoreo.readStringUntil('\x1F');
name.trim();
// read the value of the output item
String data = Base64EncodeChoreo.readStringUntil('\x1E');
data.trim();
if(name == "Base64EncodedText") {
return data;
}
}
}
/*
IMPORTANT NOTE: TembooAccount.h:
TembooAccount.h is a file referenced by this sketch that contains your Temboo account information.
You'll need to edit the placeholder version of TembooAccount.h included with this example sketch,
by inserting your own Temboo account name and app key information. The contents of the file should
look like:
#define TEMBOO_ACCOUNT "myTembooAccountName" // your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // your Temboo app key name
#define TEMBOO_APP_KEY "xxx-xxx-xxx-xx-xxx" // your Temboo app key
You can find your Temboo App Key information on the Temboo website,
under My Account > Application Keys
The same TembooAccount.h file settings can be used for all Temboo SDK sketches.
Keeping your account information in a separate file means you can share the main .ino file without worrying
that you forgot to delete your credentials.
*/

View File

@@ -0,0 +1,99 @@
// Copyright 2017, Temboo Inc.
#include <Process.h>
void setup() {
// initialize the Bridge
Bridge.begin();
Serial.begin(9600);
Process p;
//intro message
Serial.println("**** Temboo Cloud Controls ****\n");
// update the package list
Serial.print("Updating package listings...");
p.runShellCommand("opkg update");
int returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed. Make sure your device is connected to the internet properly.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// upgrade the Temboo package
Serial.print("Updating Temboo...");
p.runShellCommand("opkg install http://downloads.arduino.cc/openwrtyun/1/packages/temboo_1.3.1-1_ar71xx.ipk --force-depends");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
Serial.println("Error number: " +String(returnCode));
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// install python openssl to allow for for ssl connections
Serial.print("Installing python-openssl...");
p.runShellCommand("opkg install python-openssl");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// Installing twisted web to work with CoAP gateway
Serial.print("Installing twisted-web...");
p.runShellCommand("opkg install http://downloads.arduino.cc/openwrtyun/1/packages/twisted-web_2.5.0-1_ar71xx.ipk --force-depends");
returnCode = p.exitValue();
if (returnCode == 0) {
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println();
// Configuring zope
Serial.print("Configuring zope...");
p.runShellCommand("opkg install http://downloads.arduino.cc/openwrtyun/1/packages/zope-interface_2.5.0-1_ar71xx.ipk --force-depends");
returnCode = p.exitValue();
if (returnCode == 0) {
p.runShellCommand("touch /usr/lib/python2.7/site-packages/zope/__init__.py");
Serial.println("Success!");
} else {
Serial.println("Failed.");
while(p.available()) {
char c = p.read();
Serial.print(c);
}
return;
}
Serial.println("Update Complete - your Yun is ready for Cloud Controls!");
}
void loop() {
// do nothing
}

View File

@@ -0,0 +1,48 @@
#######################################
# Syntax Coloring Map Temboo
#######################################
#######################################
# Class (KEYWORD1)
#######################################
Temboo KEYWORD1
TembooMQTTEdgeDevice KEYWORD1
TembooCoAPEdgeDevice KEYWORD1
TembooYunShield KEYWORD1
#######################################
# Datatypes (KEYWORD2)
#######################################
TembooChoreo KEYWORD2
TembooCoAPChoreo KEYWORD2
TembooMQTTChoreo KEYWORD2
TembooYunShieldChoreo KEYWORD2
TembooDS18B20Config KEYWORD2
TembooGPIOConfig KEYWORD2
TembooSensor KEYWORD2
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
setAccountName KEYWORD2
setAppKeyName KEYWORD2
setAppKey KEYWORD2
setChoreo KEYWORD2
setCredential KEYWORD2
setSavedInputs KEYWORD2
addInput KEYWORD2
addOutputFilter KEYWORD2
addSensorInput KEYWORD2
addInputExpression KEYWORD2
setDeviceName KEYWORD2
setDeviceType KEYWORD2
setSettingsFileToWrite KEYWORD2
setSettingsFileToRead KEYWORD2
setGatewayAddress KEYWORD2
tembooDS18B20Init KEYWORD2
tembooDigitalGPIOInit KEYWORD2
tembooAnalogGPIOInit KEYWORD2

View File

@@ -0,0 +1,10 @@
name=Temboo
author=Temboo
maintainer=Temboo <support@temboo.com>
sentence=This library enables calls to Temboo, a platform that connects Arduino/Genuino boards to 100+ APIs, databases, code utilities and more.
paragraph=Use this library to connect your Arduino or Genuino board to Temboo, making it simple to interact with a vast array of web-based resources and services.
category=Communication
url=http://www.temboo.com/arduino
architectures=*
version=1.2.1
core-dependencies=arduino (>=1.5.0)

View File

@@ -0,0 +1,433 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE)
///////////////////////////////////////////////////////
// ARDUINO YUN AND TRE SUPPORT IN HEADER FILE
///////////////////////////////////////////////////////
#else //ARDUINO_AVR_YUN
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT
///////////////////////////////////////////////////////
#include <string.h>
#include <Client.h>
#include <avr/pgmspace.h>
#include <Temboo.h>
#include "utility/TembooGlobal.h"
#include "utility/TembooSession.h"
static const char HTTP_CODE[] PROGMEM = "HTTP_CODE\x0A\x1F";
static char HTTP_EOL[] = "\r\n";
static char HTTP_EOH[] = "\r\n\r\n";
TembooChoreo::TembooChoreo(Client& client) : m_client(client) {
m_accountName = NULL;
m_appKeyName = NULL;
m_appKeyValue = NULL;
m_path = NULL;
m_nextChar = NULL;
m_nextState = END;
}
void TembooChoreo::setAccountName(const String& accountName) {
m_accountName = accountName.c_str();
}
void TembooChoreo::setAccountName(const char* accountName) {
m_accountName = accountName;
}
void TembooChoreo::setAppKeyName(const String& appKeyName) {
m_appKeyName = appKeyName.c_str();
}
void TembooChoreo::setAppKeyName(const char* appKeyName) {
m_appKeyName = appKeyName;
}
void TembooChoreo::setAppKey(const String& appKeyValue) {
m_appKeyValue = appKeyValue.c_str();
}
void TembooChoreo::setAppKey(const char* appKeyValue) {
m_appKeyValue = appKeyValue;
}
void TembooChoreo::setChoreo(const String& path) {
m_path = path.c_str();
}
void TembooChoreo::setChoreo(const char* path) {
m_path = path;
}
void TembooChoreo::setSavedInputs(const String& savedInputsName) {
m_preset.put(savedInputsName.c_str());
}
void TembooChoreo::setSavedInputs(const char* savedInputsName) {
m_preset.put(savedInputsName);
}
void TembooChoreo::setCredential(const String& credentialName) {
m_preset.put(credentialName.c_str());
}
void TembooChoreo::setCredential(const char* credentialName) {
m_preset.put(credentialName);
}
void TembooChoreo::setProfile(const String& profileName) {
m_preset.put(profileName.c_str());
}
void TembooChoreo::setProfile(const char* profileName) {
m_preset.put(profileName);
}
void TembooChoreo::setDeviceType(const String& deviceType) {
m_deviceType.put(deviceType.c_str());
}
void TembooChoreo::setDeviceType(const char* deviceType) {
m_deviceType.put(deviceType);
}
void TembooChoreo::setDeviceName(const String& deviceName) {
m_deviceName.put(deviceName.c_str());
}
void TembooChoreo::setDeviceName(const char* deviceName) {
m_deviceName.put(deviceName);
}
void TembooChoreo::addInput(const String& inputName, const String& inputValue) {
m_inputs.put(inputName.c_str(), inputValue.c_str());
}
void TembooChoreo::addInput(const char* inputName, const char* inputValue) {
m_inputs.put(inputName, inputValue);
}
void TembooChoreo::addInput(const char* inputName, const String& inputValue) {
m_inputs.put(inputName, inputValue.c_str());
}
void TembooChoreo::addInput(const String& inputName, const char* inputValue) {
m_inputs.put(inputName.c_str(), inputValue);
}
void TembooChoreo::addInputWithSensor(const String& inputName, const String& inputValue) {
m_expressions.put(inputName.c_str(), inputValue.c_str());
}
void TembooChoreo::addInputWithSensor(const char* inputName, const String& inputValue) {
m_expressions.put(inputName, inputValue.c_str());
}
void TembooChoreo::addInputWithSensor(const char* inputName, const char* inputValue) {
m_expressions.put(inputName, inputValue);
}
void TembooChoreo::addInputExpression(const String& inputName, const String& inputValue) {
m_expressions.put(inputName.c_str(), inputValue.c_str());
}
void TembooChoreo::addInputExpression(const char* inputName, const String& inputValue) {
m_expressions.put(inputName, inputValue.c_str());
}
void TembooChoreo::addInputExpression(const char* inputName, const char* inputValue) {
m_expressions.put(inputName, inputValue);
}
void TembooChoreo::addSensorValue(const char* sensorName, int sensorValue, const char* conversion) {
m_sensors.put(sensorName, sensorValue, conversion, NULL, NULL, NULL, NULL, NULL);
}
void TembooChoreo::addSensorValue(const char* sensorName, int sensorValue) {
m_sensors.put(sensorName, sensorValue, NULL, NULL, NULL, NULL, NULL, NULL);
}
void TembooChoreo::addSensorValue(const char* sensorName, int sensorValue, const char* conversion, const char* calibrationValue) {
m_sensors.put(sensorName, sensorValue, conversion, NULL, NULL, NULL, NULL, calibrationValue);
}
void TembooChoreo::addSensorValue(const char* sensorName, int sensorValue, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh) {
m_sensors.put(sensorName, sensorValue, NULL, rawLow, rawHigh, scaleLow, scaleHigh, NULL);
}
void TembooChoreo::addSensorInput(const char* sensorName, int sensorValue, const char* conversion) {
m_sensors.put(sensorName, sensorValue, conversion, NULL, NULL, NULL, NULL, NULL);
}
void TembooChoreo::addSensorInput(const char* sensorName, int sensorValue) {
m_sensors.put(sensorName, sensorValue, NULL, NULL, NULL, NULL, NULL, NULL);
}
void TembooChoreo::addSensorInput(const char* sensorName, int sensorValue, const char* conversion, const char* calibrationValue) {
m_sensors.put(sensorName, sensorValue, conversion, NULL, NULL, NULL, NULL, calibrationValue);
}
void TembooChoreo::addSensorInput(const char* sensorName, int sensorValue, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh) {
m_sensors.put(sensorName, sensorValue, NULL, rawLow, rawHigh, scaleLow, scaleHigh, NULL);
}
void TembooChoreo::addOutputFilter(const char* outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath, variableName);
}
void TembooChoreo::addOutputFilter(const String& outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName);
}
void TembooChoreo::addOutputFilter(const char* outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName);
}
void TembooChoreo::addOutputFilter(const String& outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName);
}
void TembooChoreo::addOutputFilter(const char* outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath, variableName.c_str());
}
void TembooChoreo::addOutputFilter(const String& outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName.c_str());
}
void TembooChoreo::addOutputFilter(const char* outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName.c_str());
}
void TembooChoreo::addOutputFilter(const String& outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName.c_str());
}
int TembooChoreo::run() {
return run(INADDR_NONE, 80, TEMBOO_CHOREO_DEFAULT_TIMEOUT_SECS);
}
int TembooChoreo::run(uint16_t timeoutSecs) {
return run(INADDR_NONE, 80, timeoutSecs);
}
int TembooChoreo::run(IPAddress addr, uint16_t port) {
return run(addr, port, TEMBOO_CHOREO_DEFAULT_TIMEOUT_SECS);
}
int TembooChoreo::run(IPAddress addr, uint16_t port, uint16_t timeoutSecs) {
m_nextChar = NULL;
if (m_accountName == NULL || *m_accountName == '\0') {
return TEMBOO_ERROR_ACCOUNT_MISSING;
}
if (m_path == NULL || *m_path == '\0') {
return TEMBOO_ERROR_CHOREO_MISSING;
}
if (m_appKeyName == NULL || *m_appKeyName == '\0') {
return TEMBOO_ERROR_APPKEY_NAME_MISSING;
}
if (m_appKeyValue == NULL || *m_appKeyValue == '\0') {
return TEMBOO_ERROR_APPKEY_MISSING;
}
TembooSession session(m_client, addr, port);
uint16_t httpCode = 0;
for (int i = 0; i < 2; i++) {
unsigned long timeoutBeginSecs = session.getTime();
if (0 != session.executeChoreo(m_accountName, m_appKeyName, m_appKeyValue, m_path, m_inputs, m_expressions, m_sensors, m_outputs, m_preset, m_deviceType, m_deviceName)) {
httpCode = 0;
break;
}
while(!m_client.available()) {
if((session.getTime() - timeoutBeginSecs) >= timeoutSecs) {
TEMBOO_TRACELN("Receive time out");
m_client.stop();
return TEMBOO_ERROR_STREAM_TIMEOUT;
}
if (!m_client.connected()) {
TEMBOO_TRACELN("Disconnected");
return TEMBOO_ERROR_HTTP_ERROR;
}
delay(10);
}
if (!m_client.findUntil("HTTP/1.", HTTP_EOL)) {
TEMBOO_TRACELN("No HTTP");
return TEMBOO_ERROR_HTTP_ERROR;
}
//Don't care if the next byte is a '1' or a '0'
m_client.read();
//Read the HTTP status code
httpCode = (uint16_t)m_client.parseInt();
// We expect HTTP response codes to be <= 599, but
// we need to be prepared for anything.
if (httpCode >= 600) {
TEMBOO_TRACELN("Invalid HTTP");
httpCode = 0;
}
// if we get an auth error AND there was an x-temboo-time header,
// update the session timeOffset
if ((httpCode == 401) && (i == 0)) {
if (m_client.findUntil("x-temboo-time:", HTTP_EOH)) {
TembooSession::setTime((unsigned long)m_client.parseInt());
while(m_client.available()) {
m_client.read();
}
m_client.stop();
}
} else {
break;
}
}
uint16toa(httpCode, m_httpCodeStr);
strcat_P(m_httpCodeStr, PSTR("\x0A\x1E"));
m_nextState = START;
m_nextChar = HTTP_CODE;
if (httpCode < 200 || httpCode >= 300) {
return TEMBOO_ERROR_HTTP_ERROR;
}
if (!m_client.find(HTTP_EOH)) {
return TEMBOO_ERROR_HTTP_ERROR;
}
return TEMBOO_ERROR_OK;
}
void TembooChoreo::close() {
m_client.stop();
}
int TembooChoreo::available() {
// If we're still sending the HTTP response code,
// report at least one character available.
if (m_nextChar != NULL) {
return m_client.available() + 1;
}
// Otherwise, return however many characters the client has.
return m_client.available();
}
int TembooChoreo::peek() {
// If we're still sending the HTTP response code,
// return the next character in that sequence.
if (m_nextChar != NULL) {
if(m_nextState != HTTP_CODE_VALUE) {
return (int)pgm_read_byte(m_nextChar);
} else {
return (int)*m_nextChar;
}
}
// Otherwise, return whatever is in the client buffer.
return m_client.peek();
}
int TembooChoreo::read() {
int c = 0;
switch(m_nextState) {
case START:
m_nextChar = HTTP_CODE;
c = (int)pgm_read_byte(m_nextChar++);
m_nextState = HTTP_CODE_TAG;
break;
case HTTP_CODE_TAG:
c = (int)pgm_read_byte(m_nextChar++);
if (pgm_read_byte(m_nextChar) == '\0') {
m_nextState = HTTP_CODE_VALUE;
m_nextChar = m_httpCodeStr;
}
break;
case HTTP_CODE_VALUE:
c = (int)(*m_nextChar++);
if (*m_nextChar == '\0') {
m_nextState = END;
m_nextChar = NULL;
}
break;
default:
c = m_client.read();
}
return c;
}
size_t TembooChoreo::write(uint8_t data) {
return m_client.write(data);
}
void TembooChoreo::flush() {
m_nextChar = NULL;
m_nextState = END;
m_client.flush();
}
#endif //ARDUINO_AVR_YUN

View File

@@ -0,0 +1,225 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOO_H_
#define TEMBOO_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
#include <Arduino.h>
#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE)
///////////////////////////////////////////////////////
// BEGIN ARDUINO YUN AND TRE SUPPORT
///////////////////////////////////////////////////////
#include <Process.h>
class TembooChoreo : public Process {
public:
void begin() {Process::begin("temboo");}
void setAccountName(const String& accountName) { addParameter("-a" + accountName);}
void setAppKeyName(const String& appKeyName) { addParameter("-u" + appKeyName);}
void setAppKey(const String& appKey) { addParameter("-p" + appKey);}
void setChoreo(const String& choreo) { addParameter("-c" + choreo);}
void setCredential(const String& credentialName) { addParameter("-e" + credentialName);}
void setSavedInputs(const String& savedInputsName) { addParameter("-e" + savedInputsName);}
void setProfile(const String& profileName) { addParameter("-e" + profileName);}
void addInput(const String& inputName, const String& inputValue) { addParameter("-i" + inputName + ":" + inputValue);}
void addOutputFilter(const String& filterName, const String& filterPath, const String& variableName) { addParameter("-o" + filterName + ":" + filterPath + ":" + variableName);}
void setSettingsFileToWrite(const String& filePath) { addParameter("-w" + filePath);}
void setSettingsFileToRead(const String& filePath) { addParameter("-r" + filePath);}
void setGatewayAddress(const String& addr) { addParameter("-s" + addr);}
void addInputExpression(const String& inputName, const String& inputValue) { addParameter("-f" + inputName + ":" + inputValue);}
void addInputWithSensor(const String& inputName, const String& inputValue) { addParameter("-f" + inputName + ":" + inputValue);}
void addSensorInput(const String& sensorName, long sensorValue, const String& conversion) {addParameter("-n" + sensorName + ":" + String(sensorValue) + ":" + conversion);}
void addSensorInput(const String& sensorName, long sensorValue) {addParameter("-v" + sensorName + ":" + String(sensorValue));}
void addSensorInput(const String& sensorName, long sensorValue, const String& conversion, const String& calibrationValue) {addParameter("-b" + sensorName + ":" + String(sensorValue) + ":" + conversion + ":" + calibrationValue);}
void addSensorInput(const String& sensorName, long sensorValue, const String& rawLow, const String& rawHigh, const String& scaleLow, const String& scaleHigh) {addParameter("-m" + sensorName + ":" + String(sensorValue) + ":" + rawLow+ ":" + rawHigh+ ":" + scaleLow+ ":" + scaleHigh);}
void addSensorValue(const String& sensorName, long sensorValue, const String& conversion) {addParameter("-n" + sensorName + ":" + String(sensorValue) + ":" + conversion);}
void addSensorValue(const String& sensorName, long sensorValue) {addParameter("-v" + sensorName + ":" + String(sensorValue));}
void addSensorValue(const String& sensorName, long sensorValue, const String& conversion, const String& calibrationValue) {addParameter("-b" + sensorName + ":" + String(sensorValue) + ":" + conversion + ":" + calibrationValue);}
void addSensorValue(const String& sensorName, long sensorValue, const String& rawLow, const String& rawHigh, const String& scaleLow, const String& scaleHigh) {addParameter("-m" + sensorName + ":" + String(sensorValue) + ":" + rawLow+ ":" + rawHigh+ ":" + scaleLow+ ":" + scaleHigh);}
void setDeviceName(const String& deviceName) {addParameter("-d" + deviceName);}
void setDeviceType(const String& deviceType) {addParameter("-t" + deviceType);}
};
#else //ARDUINO_AVR_YUN
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT
///////////////////////////////////////////////////////
#include <Stream.h>
#include <Client.h>
#include <IPAddress.h>
#include "utility/ChoreoInputSet.h"
#include "utility/ChoreoInputExpressionSet.h"
#include "utility/ChoreoSensorInputSet.h"
#include "utility/ChoreoOutputSet.h"
#include "utility/ChoreoPreset.h"
#include "utility/ChoreoDevice.h"
#define TEMBOO_ERROR_OK (0)
#define TEMBOO_ERROR_ACCOUNT_MISSING (201)
#define TEMBOO_ERROR_CHOREO_MISSING (203)
#define TEMBOO_ERROR_APPKEY_NAME_MISSING (205)
#define TEMBOO_ERROR_APPKEY_MISSING (207)
#define TEMBOO_ERROR_HTTP_ERROR (223)
#define TEMBOO_ERROR_STREAM_TIMEOUT (225)
#define TEMBOO_CHOREO_DEFAULT_TIMEOUT_SECS (901) //15 minutes and 1 second
class TembooChoreo : public Stream {
public:
// Constructor.
// client - an instance of an Arduino Client, usually an EthernetClient
// or a WiFiClient. Used to communicate with Temboo.
TembooChoreo(Client& client);
// Does nothing. Just for source compatibility with Yun code.
void begin() {};
// Sets the account name to use when communicating with Temboo.
// (required)
void setAccountName(const String& accountName);
void setAccountName(const char* accountName);
// Sets the application key name to use with choreo execution requests.
// (required)
void setAppKeyName(const String& appKeyName);
void setAppKeyName(const char* appKeyName);
// Sets the application key value to use with choreo execution requests
// (required)
void setAppKey(const String& appKey);
void setAppKey(const char* appKey);
// Sets the name of the choreo to be executed.
// (required)
void setChoreo(const String& choreoPath);
void setChoreo(const char* choreoPath);
// Sets the name of the saved inputs to use when executing the choreo
// (optional)
void setSavedInputs(const String& savedInputsName);
void setSavedInputs(const char* savedInputsName);
void setCredential(const String& credentialName);
void setCredential(const char* credentialName);
void setProfile(const String& profileName);
void setProfile(const char* profileName);
void setDeviceType(const String& deviceType);
void setDeviceType(const char* deviceType);
void setDeviceName(const String& deviceName);
void setDeviceName(const char* deviceName);
// Sets an input to be used when executing a choreo.
// (optional or required, depending on the choreo being executed.)
void addInput(const String& inputName, const String& inputValue);
void addInput(const char* inputName, const char* inputValue);
void addInput(const char* inputName, const String& inputValue);
void addInput(const String& inputName, const char* inputValue);
// Sets a Choreo input that contains a sensor value to be converted by Temboo
void addInputWithSensor(const String& inputName, const String& inputValue);
void addInputWithSensor(const char* inputName, const String& inputValue);
void addInputWithSensor(const char* inputName, const char* inputValue);
// Keeping legacy methods
void addInputExpression(const String& inputName, const String& inputValue);
void addInputExpression(const char* inputName, const String& inputValue);
void addInputExpression(const char* inputName, const char* inputValue);
// Sets in input that is using a sensor value. Different parameters are needed depending
// on the type of sensor being used.
void addSensorValue(const char* sensorName, int sensorValue, const char* conversion);
void addSensorValue(const char* sensorName, int sensorValue);
void addSensorValue(const char* sensorName, int sensorValue, const char* conversion, const char* calibrationValue);
void addSensorValue(const char* sensorName, int sensorValue, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh);
// Keeping legacy methods
void addSensorInput(const char* sensorName, int sensorValue, const char* conversion);
void addSensorInput(const char* sensorName, int sensorValue);
void addSensorInput(const char* sensorName, int sensorValue, const char* conversion, const char* calibrationValue);
void addSensorInput(const char* sensorName, int sensorValue, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh);
// Sets an output filter to be used to process the choreo output
// (optional)
void addOutputFilter(const char* filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const String& variableName);
// Run the choreo using the current input info
int run();
// Run the choreo with a user specified timeout
int run(uint16_t timeoutSecs);
// Run the choreo on the Temboo server at the given IP address and port
int run(IPAddress addr, uint16_t port);
int run(IPAddress addr, uint16_t port, uint16_t timeoutSecs);
void close();
// Stream interface - see the Arduino library documentation.
int available();
int read();
int peek();
void flush();
//Print interface - see the Arduino library documentation
size_t write(uint8_t data);
protected:
ChoreoInputSet m_inputs;
ChoreoInputExpressionSet m_expressions;
ChoreoSensorInputSet m_sensors;
ChoreoOutputSet m_outputs;
ChoreoPreset m_preset;
ChoreoDevice m_deviceType;
ChoreoDevice m_deviceName;
const char* m_accountName;
const char* m_appKeyValue;
const char* m_appKeyName;
const char* m_path;
Client& m_client;
char m_httpCodeStr[6];
const char* m_nextChar;
enum State {START, HTTP_CODE_TAG, HTTP_CODE_VALUE, END};
State m_nextState;
};
#endif //ARDUINO_AVR_YUN
#endif //TEMBOO_H_

View File

@@ -0,0 +1,935 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "utility/TembooGlobal.h"
#include "utility/TembooCoAPSession.h"
#include "utility/TembooTags.h"
#include "TembooCoAPEdgeDevice.h"
#ifndef UINT16_MAX
#define UINT16_MAX (0xFFFF)
#endif
#ifndef UINT32_MAX
#define UINT32_MAX (0xFFFFFFFF)
#endif
//TODO: Maybe. Put these in PROGMEM and
// modify any code that uses them.
const char HTTP_CODE_PREFIX[] = "HTTP_CODE\x0A\x1F";
const char HTTP_CODE_SUFFIX[] = "\x0A\x1E";
const char TembooCoAPClient::URI_PATH[] = "exec";
const char TIME_URI_PATH[] = "time";
static char HEADER_TIME[] = "x-temboo-time:";
uint16_t TembooCoAPChoreo::s_nextRequestId = 0;
TembooCoAPClient::TembooCoAPClient(TembooCoAPIPStack& ipStack, IPAddress gatewayAddress, uint16_t gatewayPort) :
m_messageLayer(m_rxBuffer, sizeof(m_rxBuffer), ipStack),
m_rrLayer(m_messageLayer, m_rxBuffer, sizeof(m_rxBuffer)),
m_gatewayAddress(gatewayAddress),
m_gatewayPort(gatewayPort),
m_messageID(0),
m_state(STATE_IDLE),
m_blockSize(MAX_BLOCK_SIZE),
m_lastError(NO_ERROR),
m_dataLen(0),
m_txIndex(0),
m_respLen(0),
m_txByteCount(0),
m_respHttpCode(0) {
memset(m_token, 0, sizeof(m_token));
memset(m_dataBuffer, 0, sizeof(m_dataBuffer));
memset(m_respBuffer, 0, sizeof(m_respBuffer));
}
void TembooCoAPClient::resetChoreo() {
memset(m_token, 0, sizeof(m_token));
memset(m_dataBuffer, 0, sizeof(m_dataBuffer));
memset(m_respBuffer, 0, sizeof(m_respBuffer));
m_dataLen = 0;
m_respLen = 0;
m_txIndex = 0;
m_txByteCount = 0;
m_rxBlockNum = 0;
m_rrLayer.setState(CoapRRLayer::STATE_IDLE);
m_messageLayer.setState(CoapMessageLayer::STATE_CLOSED);
}
void TembooCoAPClient::begin(long seed){
//RFC7252 "strongly recommends" that the initial
//value of messageID be randomized. There's no good way
//to do that reliably on many MCU boards. We will
//use random(), and can instruct the user to call randomSeed
//with input from an unused analog input if it's important to them.
randomSeed(seed);
m_messageID = random(0, UINT16_MAX);
}
TembooCoAPClient::~TembooCoAPClient() {
}
TembooCoAPClient::Result TembooCoAPClient::write(uint8_t value) {
if (m_dataLen < sizeof(m_dataBuffer)) {
m_dataBuffer[m_dataLen] = value;
m_dataLen++;
m_txByteCount = 0;
m_txIndex = 0;
return NO_ERROR;
}
return ERROR_BUFFER_FULL;
}
TembooCoAPClient::Result TembooCoAPClient::saveResponse(uint8_t* values, uint16_t len) {
len = len < (sizeof(m_respBuffer) - m_respLen - 1) ? len : (sizeof(m_respBuffer) - m_respLen -1);
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Saving payload to the buffer");
if ( len > 0) {
memcpy(&m_respBuffer[m_respLen], values, len);
m_respLen += len;
return NO_ERROR;
}
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Buffer full, payload not saved");
return ERROR_BUFFER_FULL;
}
TembooCoAPClient::Result TembooCoAPClient::write(uint8_t* values, uint16_t len) {
Result rc = NO_ERROR;
while(NO_ERROR == rc && len > 0) {
rc = write(*values++);
len--;
}
return rc;
}
uint16_t TembooCoAPClient::getNextMessageID() {
m_messageID++;
if (m_messageID == 0) {
m_messageID++;
}
return m_messageID;
}
TembooCoAPClient::Result TembooCoAPClient::generateToken() {
// 5.3.1. Token suggests the tokenID should be a random value
for (int i = 0; i < 8; i++) {
m_token[i] = (rand() % 93) + 33;
}
m_token[8] = '\0';
return NO_ERROR;
}
TembooCoAPClient::Result TembooCoAPClient::sendBlockRequest(uint16_t msgID, uint32_t blockNum) {
CoapMsg msg(m_txBuffer, sizeof (m_txBuffer));
msg.setCode(CoapMsg::COAP_POST);
if (msg.setToken((uint8_t*)m_token, strlen(m_token))) {
TEMBOO_TRACELN("err: setToken");
return ERROR_MSG_TOKEN;
}
msg.setId(msgID);
if (msg.addOption(CoapMsg::COAP_OPTION_URI_PATH, (const uint8_t*)URI_PATH, strlen(URI_PATH))) {
TEMBOO_TRACELN("err: setURI");
return ERROR_MSG_OPTION;
}
uint8_t optionValue[3];
uint16_t optionLen = 0;
// If this is the last block in a series of blocks (or an only block)
// include a block2 option to let the server know what our
// desired block size is for the response.
optionValue[0] = (blockNum & 0xF000) >> 12;
optionValue[1] = (blockNum & 0x0FF0) >> 4;
optionValue[2] = (blockNum & 0x000F) << 4;
optionValue[2] |= (0 ? 0x08 : 0);
optionValue[2] |= (m_blockSize >> 5) & 0x07;
optionLen = 1;
if (optionValue[0] > 0) {
optionLen = 3;
} else if (optionValue[1] > 0) {
optionLen = 2;
}
if (msg.addOption(CoapMsg::COAP_OPTION_BLOCK2, (const uint8_t*)&optionValue[3 - optionLen], optionLen)) {
TEMBOO_TRACELN("err: block2");
return ERROR_MSG_OPTION;
}
if (m_rrLayer.reliableSend(msg, m_token, m_gatewayAddress, m_gatewayPort) != CoapRRLayer::NO_ERROR) {
TEMBOO_TRACELN("err: send");
return ERROR_SENDING_MSG;
}
return NO_ERROR;
}
TembooCoAPClient::Result TembooCoAPClient::sendBlock(uint16_t msgID, uint8_t* payload, size_t len, uint32_t blockNum, bool moreBlocks) {
CoapMsg msg(m_txBuffer, sizeof (m_txBuffer));
msg.setCode(CoapMsg::COAP_POST);
if (msg.setToken((uint8_t*)m_token, strlen(m_token))) {
TEMBOO_TRACELN("err: setToken");
return ERROR_MSG_TOKEN;
}
msg.setId(msgID);
if (msg.addOption(CoapMsg::COAP_OPTION_URI_PATH, (const uint8_t*)URI_PATH, strlen(URI_PATH))) {
TEMBOO_TRACELN("err: setURI");
return ERROR_MSG_OPTION;
}
uint8_t optionValue[3];
uint16_t optionLen = 0;
// If this is the last block in a series of blocks (or an only block)
// include a block2 option to let the server know what our
// desired block size is for the response.
if (!moreBlocksToSend()) {
optionValue[0] = (m_blockSize >> 5) & 0x07;
optionLen = (optionValue[0] > 0) ? 1 : 0;
if (msg.addOption(CoapMsg::COAP_OPTION_BLOCK2, (const uint8_t*)optionValue, optionLen)) {
TEMBOO_TRACELN("err: block2");
return ERROR_MSG_OPTION;
}
}
// If this is not the only block in the request,
// include the block1 option.
if (blockNum > 0 || moreBlocks) {
optionValue[0] = (blockNum & 0xF000) >> 12;
optionValue[1] = (blockNum & 0x0FF0) >> 4;
optionValue[2] = (blockNum & 0x000F) << 4;
optionValue[2] |= (moreBlocks ? 0x08 : 0);
optionValue[2] |= (m_blockSize >> 5) & 0x07;
optionLen = 1;
if (optionValue[0] > 0) {
optionLen = 3;
} else if (optionValue[1] > 0) {
optionLen = 2;
}
if (msg.addOption(CoapMsg::COAP_OPTION_BLOCK1, (const uint8_t*)&optionValue[3 - optionLen], optionLen)) {
TEMBOO_TRACELN("err: block1");
return ERROR_MSG_OPTION;
}
}
if (msg.setPayload((uint8_t*)payload, len)) {
TEMBOO_TRACELN("err: setPayload");
return ERROR_MSG_PAYLOAD;
}
if (m_rrLayer.reliableSend(msg, m_token, m_gatewayAddress, m_gatewayPort) != CoapRRLayer::NO_ERROR) {
TEMBOO_TRACELN("err: send");
return ERROR_SENDING_MSG;
}
return NO_ERROR;
}
void TembooCoAPClient::adjustRequestBlockSize(CoapMsg& msg) {
// A block1 option in a response means the server is
// requesting that we use a smaller block size.
uint16_t newBlockSize = msg.getBlock1Size();
if (newBlockSize > 0 && newBlockSize < m_blockSize) {
m_blockSize = newBlockSize;
}
}
TembooCoAPClient::Result TembooCoAPClient::loop() {
m_lastResult = NO_ERROR;
switch (m_state) {
case STATE_IDLE:
case STATE_RESPONSE_READY:
// Pump the receiver.
// We're not serving anything, so unless there's an outstanding
// request (which would mean we would be in STATE_WAITING, not STATE_IDLE),
// the R/R layer will reject or ignore any incoming traffic.
m_rrLayer.loop();
break;
case STATE_SEND_REQUEST:
case STATE_RESPONSE_STARTED:
case STATE_WAITING_FOR_RESPONSE:
// We're waiting for a response to an earlier request.
switch(m_rrLayer.loop()) {
case CoapRRLayer::NO_ERROR:
// Nothing happened. Nothing to do.
break;
case CoapRRLayer::RESPONSE_RECEIVED: {
// A response to our request was received.
// It may have been a piggybacked ACK or a separate response
CoapMsg msg(m_rxBuffer, sizeof(m_rxBuffer), m_messageLayer.getRXByteCount());
// See if it has a BLOCK1 option. If so, make sure the
// block number matches the one we just sent. If the block
// numbers don't match, we're FUBAR, so abort the request.
// If they do match, adjust our request block size if the
// server requested a different (smaller) size.
if (msg.getOptionCount(CoapMsg::COAP_OPTION_BLOCK1)) {
uint32_t ackBlockNum = msg.getBlock1Num();
if (ackBlockNum != m_txBlockNum) {
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Block1 message number does not match");
m_lastResult = ERROR_RECEIVING_RESPONSE;
if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
m_messageLayer.rejectMsg(msg);
}
resetChoreo();
break;
}
adjustRequestBlockSize(msg);
}
// Now deal with the response itself.
switch(msg.getCode()) {
case CoapMsg::COAP_CONTINUE: //2.31
// 2.31 means the server is requesting the next block of the request.
// If there are no more blocks to send, we're FUBAR, so abort the
// request. Otherwise, send the next block.
if (m_txIndex >= m_dataLen) {
// no more data to send, bad news
resetChoreo();
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Gateway requested too many blocks");
break;
}
if (sendChoreoRequest() != NO_ERROR) {
resetChoreo();
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Send Choreo request failed");
}
break;
case CoapMsg::COAP_REQUEST_ENTITY_INCOMPLETE: //4.08
// 4.08 means the server is missing one or more blocks, so can't
// service the request.
// We're FUBAR, so abort the request.
resetChoreo();
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Gateway returned 4.08");
break;
case CoapMsg::COAP_REQUEST_ENTITY_TOO_LARGE: //4.13
// 4.13 means the server ran out of memory when receiving the
// request.
// We're FUBAR, so abort the request.
resetChoreo();
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Gateway returned 4.13");
break;
default:
// Any response code other than the special ones above means the
// server has processed the request and is returning the final result,
// which may be in one or more blocks. If we haven't finished sending
// the request, we're FUBAR, so abort the request. Otherwise, process
// the response.
m_dataLen = 0;
if (moreBlocksToSend()) {
m_lastResult = ERROR_RECEIVING_RESPONSE;
if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
m_messageLayer.rejectMsg(msg);
}
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Response received before request finished");
resetChoreo();
m_state = STATE_IDLE;
break;
} else {
if (msg.getOptionCount(CoapMsg::COAP_OPTION_BLOCK2)) {
// The server is sending a multi-block response, make sure
// it's sending the response block we're expecting.
uint32_t respBlockNum = msg.getBlock2Num();
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Block2 opt recv");
if (respBlockNum > m_rxBlockNum) {
// It sent a newer block than the one we're expecting,
// (i.e. we've somehow missed a block)
// that's an error.
m_lastResult = ERROR_RECEIVING_RESPONSE;
if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
m_messageLayer.rejectMsg(msg);
}
resetChoreo();
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Received block out of order");
break;
} else if (respBlockNum < m_rxBlockNum) {
// It resent a block we've already received,
// (i.e. it didn't see our ACK),
// just accept (ACK) it again.
if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
m_messageLayer.acceptMsg(msg);
}
m_lastResult = NO_ERROR;
sendBlockRequest(m_messageID, m_rxBlockNum);
m_state = STATE_RESPONSE_STARTED;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Received previous block");
} else {
// Server sent the next block we are expecting.
// Accept it and add the payload to our buffer.
bool block2More = msg.getBlock2More();
m_respHttpCode = msg.getHTTPStatus();
m_lastResult = saveResponse(msg.getPayload(), msg.getPayloadLen());
if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
m_rxBlockNum = respBlockNum;
m_messageLayer.acceptMsg(msg);
}
if (block2More) {
m_rxBlockNum++;
m_messageID++;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Request next block2 msg");
sendBlockRequest(m_messageID, m_rxBlockNum);
m_state = STATE_RESPONSE_STARTED;
} else {
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Final block2 msg recv");
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Response complete");
m_state = STATE_RESPONSE_READY;
}
}
} else {
// There's no Block2 option, so is either
// the one and only block in the response
// or an empty ack.
// check if empty to handle final ack. If empty
// wait for CON with matching token and then
// request other blocks of response
if (msg.getCode() == CoapMsg::COAP_EMPTY) {
m_rrLayer.setState(CoapRRLayer::STATE_WAITING);
m_messageLayer.setState(CoapMessageLayer::STATE_WAITING_FOR_CON);
m_state = STATE_WAITING_FOR_RESPONSE;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Empty ACK received, waiting for response");
} else {
m_respHttpCode = msg.getHTTPStatus();
m_lastResult = saveResponse(msg.getPayload(), msg.getPayloadLen());
m_messageLayer.acceptMsg(msg);
m_state = STATE_RESPONSE_READY;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Response complete");
}
}
}
}
// Does it have a Block2 option?
uint32_t responseBlockNum = msg.getBlock2Num();
if (responseBlockNum == m_rxBlockNum && msg.getOptionCount(CoapMsg::COAP_OPTION_BLOCK2)) {
adjustRequestBlockSize(msg);
m_rxBlockNum++;
if (0 == m_rxBlockNum) {
clearData();
}
}
break;
}
case CoapRRLayer::ERROR_RECEIVING_RESPONSE:
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Error receiving response");
break;
case CoapRRLayer::RST_RECEIVED:
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("RST received");
break;
default:
// Anything else indicates a failure of some sort. Check
// the messageLayer lastResult for specifics.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Request failed");
m_lastResult = ERROR_REQUEST_FAILED;
m_state = STATE_IDLE;
}
break;
default:
break;
}
return m_lastResult;
}
void TembooCoAPClient::cancelWait() {
if (STATE_WAITING_FOR_RESPONSE == m_state) {
m_messageLayer.cancelReliableSend();
m_dataLen = 0;
m_state = STATE_IDLE;
}
}
bool TembooCoAPClient::moreBlocksToSend() {
uint16_t payloadLength = (m_dataLen - m_txByteCount) < m_blockSize ? (m_dataLen - m_txByteCount) : m_blockSize;
return ((m_txByteCount + payloadLength) < m_dataLen);
}
TembooCoAPClient::Result TembooCoAPClient::sendChoreoRequest() {
uint16_t payloadLength = 0;
generateToken();
payloadLength = (m_dataLen - m_txByteCount) < m_blockSize ? (m_dataLen - m_txByteCount) : m_blockSize;
m_txBlockNum = m_txByteCount/m_blockSize;
bool moreBlocks = (m_txByteCount + payloadLength) < m_dataLen;
m_lastError = sendBlock(m_messageID, &m_dataBuffer[m_txIndex], payloadLength, m_txBlockNum, moreBlocks);
m_messageID++;
if (TembooCoAPClient::NO_ERROR == m_lastError) {
m_state = STATE_SEND_REQUEST;
m_txIndex += payloadLength;
m_txByteCount += payloadLength;
} else {
m_lastError = ERROR_SENDING_MSG;
m_state = STATE_ERROR;
}
return m_lastError;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
TembooCoAPChoreo::TembooCoAPChoreo(TembooCoAPClient& client) :
m_client(client),
m_accountName(NULL),
m_appKeyName(NULL),
m_appKeyValue(NULL),
m_path(NULL),
m_requestId(0),
m_availableChars(0),
m_nextChar(NULL),
m_nextState(END)
{
}
TembooCoAPChoreo::~TembooCoAPChoreo() {
}
void TembooCoAPChoreo::setAccountName(const String& accountName) {
m_accountName = accountName.c_str();
}
void TembooCoAPChoreo::setAccountName(const char* accountName) {
m_accountName = accountName;
}
void TembooCoAPChoreo::setAppKeyName(const String& appKeyName) {
m_appKeyName = appKeyName.c_str();
}
void TembooCoAPChoreo::setAppKeyName(const char* appKeyName) {
m_appKeyName = appKeyName;
}
void TembooCoAPChoreo::setAppKey(const String& appKeyValue) {
m_appKeyValue = appKeyValue.c_str();
}
void TembooCoAPChoreo::setAppKey(const char* appKeyValue) {
m_appKeyValue = appKeyValue;
}
void TembooCoAPChoreo::setChoreo(const String& path) {
m_path = path.c_str();
}
void TembooCoAPChoreo::setChoreo(const char* path) {
m_path = path;
}
void TembooCoAPChoreo::setSavedInputs(const String& savedInputsName) {
m_preset.put(savedInputsName.c_str());
}
void TembooCoAPChoreo::setSavedInputs(const char* savedInputsName) {
m_preset.put(savedInputsName);
}
void TembooCoAPChoreo::setCredential(const String& credentialName) {
m_preset.put(credentialName.c_str());
}
void TembooCoAPChoreo::setCredential(const char* credentialName) {
m_preset.put(credentialName);
}
void TembooCoAPChoreo::setProfile(const String& profileName) {
m_preset.put(profileName.c_str());
}
void TembooCoAPChoreo::setProfile(const char* profileName) {
m_preset.put(profileName);
}
void TembooCoAPChoreo::addInput(const String& inputName, const String& inputValue) {
m_inputs.put(inputName.c_str(), inputValue.c_str());
}
void TembooCoAPChoreo::addInput(const char* inputName, const char* inputValue) {
m_inputs.put(inputName, inputValue);
}
void TembooCoAPChoreo::addInput(const char* inputName, const String& inputValue) {
m_inputs.put(inputName, inputValue.c_str());
}
void TembooCoAPChoreo::addInput(const String& inputName, const char* inputValue) {
m_inputs.put(inputName.c_str(), inputValue);
}
void TembooCoAPChoreo::addOutputFilter(const char* outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath, variableName);
}
void TembooCoAPChoreo::addOutputFilter(const String& outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName);
}
void TembooCoAPChoreo::addOutputFilter(const char* outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName);
}
void TembooCoAPChoreo::addOutputFilter(const String& outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName);
}
void TembooCoAPChoreo::addOutputFilter(const char* outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath, variableName.c_str());
}
void TembooCoAPChoreo::addOutputFilter(const String& outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName.c_str());
}
void TembooCoAPChoreo::addOutputFilter(const char* outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName.c_str());
}
void TembooCoAPChoreo::addOutputFilter(const String& outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName.c_str());
}
int TembooCoAPChoreo::waitForResponse(TembooTimer& timer) {
int rc = SUCCESS;
while (m_client.getState() == TembooCoAPClient::STATE_RESPONSE_STARTED || m_client.getState() == TembooCoAPClient::STATE_WAITING_FOR_RESPONSE) {
if (timer.expired()) {
TEMBOO_TRACELN("ERROR: Choreo timeout");
rc = TEMBOO_ERROR_TIMEOUT;
break;
}
m_client.loop();
// While the buffer may be full, we need to receive all of the data
// from the gateway even though we discard it. We still return
// the buffer error code, but the user is still able to see what
// data was able to fit in the current buffer
if (m_client.getMessageState() != TembooCoAPClient::NO_ERROR && m_client.getMessageState() != TembooCoAPClient::ERROR_BUFFER_FULL) {
rc = FAILURE;
break;
}
}
return rc;
}
int TembooCoAPChoreo::run(uint16_t timeoutSecs) {
m_nextChar = NULL;
if (IS_EMPTY(m_accountName)) {
return TEMBOO_ERROR_ACCOUNT_MISSING;
}
if (IS_EMPTY(m_path)) {
return TEMBOO_ERROR_CHOREO_MISSING;
}
if (IS_EMPTY(m_appKeyName)) {
return TEMBOO_ERROR_APPKEY_NAME_MISSING;
}
if (IS_EMPTY(m_appKeyValue)) {
return TEMBOO_ERROR_APPKEY_MISSING;
}
int rc = 0;
TembooTimer timer(timeoutSecs * 1000L);
for (int i = 0; i < 2; i++) {
m_client.resetChoreo();
TembooCoAPSession session(m_client);
m_requestId = s_nextRequestId++;
m_respData = NULL;
m_availableChars = 0;
m_nextState = START;
uint16toa(0 , m_httpCodeStr);
m_client.getNextMessageID();
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Sending request");
rc = session.executeChoreo(m_requestId, m_accountName, m_appKeyName, m_appKeyValue, m_path, m_inputs, m_expressions, m_sensors, m_outputs, m_preset, m_deviceType, m_deviceName);
if (SUCCESS != rc) {
goto ErrorExit;
}
// finish sending Choreo request
while (m_client.getState() == TembooCoAPClient::STATE_SEND_REQUEST) {
if(m_client.loop() == TembooCoAPClient::ERROR_REQUEST_FAILED) {
rc = m_client.getMessageState();
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Choreo request failed");
goto ErrorExit;
}
}
// choreo request complete, wait for CON from gateway
// and then request the rest of the response
rc = waitForResponse(timer);
if (SUCCESS != rc){
rc = m_client.getMessageState();
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Waiting for response failed");
goto ErrorExit;
} else {
m_respData = (char*)m_client.getPacketBuffer();
uint16_t httpCode = m_client.getRespHttpCode();
if (httpCode >= 700) {
httpCode = 0;
}
uint16toa(httpCode, m_httpCodeStr);
m_availableChars = strlen(m_respData) + strlen(m_httpCodeStr) + strlen(HTTP_CODE_PREFIX) + strlen(HTTP_CODE_SUFFIX);
m_nextChar = HTTP_CODE_PREFIX;
//Unauthroized, need to update the time
if (httpCode == 401 && i == 0) {
find(HEADER_TIME);
TembooCoAPSession::setTime((unsigned long)this->parseInt());
} else {
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACE(m_availableChars);
TEMBOO_TRACELN(" CHARS");
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Response buffer data:");
TEMBOO_TRACELN(m_respData);
rc = m_client.getMessageState();
break;
}
}
}
ErrorExit:
if (SUCCESS != rc) {
TEMBOO_TRACE(" ERROR:");
TEMBOO_TRACELN(rc);
}
return rc;
}
int TembooCoAPChoreo::available() {
return m_availableChars;
}
int TembooCoAPChoreo::peek() {
if (m_availableChars > 0) {
return (int)*m_nextChar;
}
return -1;
}
int TembooCoAPChoreo::read() {
if (m_availableChars > 0) {
int c = 0;
switch(m_nextState) {
case START:
m_nextChar = HTTP_CODE_PREFIX;
c = (int)(*m_nextChar++);
m_nextState = HTTP_CODE_PRE;
break;
case HTTP_CODE_PRE:
c = (int)(*m_nextChar++);
if ('\0' == *m_nextChar) {
m_nextState = HTTP_CODE_VALUE;
m_nextChar = m_httpCodeStr;
}
break;
case HTTP_CODE_VALUE:
c = (int)(*m_nextChar++);
if (*m_nextChar == '\0') {
m_nextState = HTTP_CODE_SUF;
m_nextChar = HTTP_CODE_SUFFIX;
}
break;
case HTTP_CODE_SUF:
c = (int)(*m_nextChar++);
if ('\0' == *m_nextChar) {
m_nextState = RESP_DATA;
m_nextChar = m_respData;
}
break;
case RESP_DATA:
c = (int)(*m_nextChar++);
if ('\0' == *m_nextChar || m_availableChars <= 0) {
m_nextState = END;
}
break;
case END:
default:
c = -1;
}
if (m_availableChars > 0) {
m_availableChars--;
}
return c;
} else {
return -1;
}
}
size_t TembooCoAPChoreo::write(uint8_t __attribute__ ((unused)) data) {
return 0;
}
void TembooCoAPChoreo::flush() {
m_nextChar = NULL;
m_nextState = END;
m_availableChars = 0;
}

View File

@@ -0,0 +1,317 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOCOAP_H_
#define TEMBOOCOAP_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT
///////////////////////////////////////////////////////
#include "Arduino.h"
#include "utility/TembooTimer.h"
#include "utility/TembooTags.h"
#include "utility/TembooCoAPIPStack.h"
#include "utility/ChoreoInputSet.h"
#include "utility/ChoreoInputExpressionSet.h"
#include "utility/ChoreoSensorInputSet.h"
#include "utility/ChoreoOutputSet.h"
#include "utility/ChoreoPreset.h"
#include "utility/ChoreoDevice.h"
#include "utility/CoapMsg.h"
#include "utility/CoapMessageLayer.h"
#include "utility/CoapRRLayer.h"
#define IS_EMPTY(s) (NULL == s || '\0' == *s)
#define DEFAULT_CHOREO_TIMEOUT 900
class TembooCoAPChoreo;
class TembooCoAPClient {
public:
TembooCoAPClient(TembooCoAPIPStack& ipStack, IPAddress gatewayAddress, uint16_t gatewayPort = DEFAULT_COAP_PORT);
virtual ~TembooCoAPClient();
void begin(long seed);
enum Result {
NO_ERROR = 0,
ERROR_REQUEST_FAILED,
ERROR_BAD_RESPONSE,
ERROR_MSG_TOKEN,
ERROR_MSG_OPTION,
ERROR_MSG_PAYLOAD,
ERROR_SENDING_MSG,
ERROR_RECEIVING_MSG,
ERROR_BUFFER_FULL,
ERROR_INVALID_MSG,
ERROR_RECEIVING_RESPONSE
};
enum State {
STATE_IDLE,
STATE_TRANSMIT,
STATE_WAITING_FOR_RESPONSE,
STATE_RESPONSE_STARTED,
STATE_RESPONSE_READY,
STATE_NO_RESPONSE,
STATE_SEND_REQUEST,
STATE_ERROR
};
Result write(uint8_t value);
Result write(uint8_t* value, uint16_t len);
void clearData() {m_dataLen = 0;}
Result loop();
Result sendChoreoRequest();
uint8_t* getPacketBuffer() {return m_respBuffer;}
int32_t getPacketBufferSize() {return m_respLen;}
int32_t getPacketLength() {return 1000;}
int16_t getRespHttpCode() {return m_respHttpCode;}
State getState() {return m_state;}
Result sendBlockRequest(uint16_t msgID, uint32_t blockNum);
void resetChoreo();
int getMessageState() {return m_lastResult;}
Result requestTime(uint16_t msgID);
protected:
static const char URI_PATH[];
Result m_lastResult;
bool moreBlocksToSend();
// MAX_BLOCK_SIZE *MUST* be one of the standard CoAP block sizes
// This size should be set with consideration to any network or
// hardware limitations on UDP packet size.
// (16, 32, 64, 128, 256, 512, or 1024).
static const int MAX_BLOCK_SIZE = 64;
// MAX_PACKET_SIZE should be at least big enough to hold:
// for outgoing requests:
// 4 header bytes
// 6 token bytes (in our case, Spec says tokens can be up to 8 bytes).
// 1 byte for the URI PATH option (I *think*)
// 4 strlen(URI_PATH) bytes for the URI PATH option value.
// 3 bytes for a block1 option
// 3 bytes for a block1 option value
// 1 bytes for a block2 option (early response size negotiation)
// 1 bytes for a block2 option value
// ? 5 bytes for a size1 or size2 option
// ? 4 bytes for a sizeX option value
// 1 byte for the FF payload marker
// MAX_BLOCK_SIZE for the payload
//
// or 24 + MAX_BLOCK_SIZE
//
// HOWEVER... we need to consider the possibility that the server may
// use more options and thus send more bytes. So we should add as much
// extra space as we can reasonably afford so as to avoid buffer overflows.
static const size_t MAX_PACKET_SIZE = 90;
static const size_t MAX_DATA_SIZE = 1000;
static const uint16_t DEFAULT_COAP_PORT = 5683;
CoapMessageLayer m_messageLayer;
CoapRRLayer m_rrLayer;
IPAddress m_gatewayAddress;
uint16_t m_gatewayPort;
uint16_t m_messageID;
State m_state;
uint16_t m_blockSize;
Result m_lastError;
uint16_t m_dataLen;
uint8_t m_dataBuffer[MAX_DATA_SIZE];
uint8_t m_respBuffer[MAX_DATA_SIZE];
char m_token[9];
uint8_t m_txBuffer[MAX_PACKET_SIZE];
uint8_t m_rxBuffer[MAX_PACKET_SIZE];
uint32_t m_rxBlockNum;
int32_t m_txIndex;
int32_t m_txByteCount;
int32_t m_respLen;
uint32_t m_txBlockNum;
int16_t m_respHttpCode;
Result generateToken();
uint16_t getNextMessageID();
Result sendBlock(uint16_t msgID, uint8_t* payload, size_t len, uint32_t blockNum, bool moreBlocks);
void adjustRequestBlockSize(CoapMsg& msg);
void cancelWait();
int send(CoapMsg* msg);
int sendResetMsg(CoapMsg& msg);
int sendEmptyAckMsg(CoapMsg& msg);
int sendBlock2AckMsg(CoapMsg& msg);
bool isMsgResponse(CoapMsg& msg);
void handleBlockAck(CoapMsg& msg);
void handleBlock1InResponse(CoapMsg& msg);
Result saveResponse(uint8_t* values, uint16_t len);
friend class TembooCoAPChoreo;
};
class TembooCoAPChoreo : public Stream {
public:
// Constructor.
// client - an instance of a TembooCoAPClient.
// Used to communicate with a Temboo CoAP Gateway.
TembooCoAPChoreo(TembooCoAPClient& client);
~TembooCoAPChoreo();
// Does nothing. Just for source compatibility with Yun code.
void begin() {;};
// Sets the account name to use when communicating with Temboo.
// (required)
void setAccountName(const String& accountName);
void setAccountName(const char* accountName);
// Sets the application key name to use with choreo execution requests.
// (required)
void setAppKeyName(const String& appKeyName);
void setAppKeyName(const char* appKeyName);
// Sets the application key value to use with choreo execution requests
// (required)
void setAppKey(const String& appKey);
void setAppKey(const char* appKey);
// sets the name of the choreo to be executed.
// (required)
void setChoreo(const String& choreoPath);
void setChoreo(const char* choreoPath);
// sets the name of the saved inputs to use when executing the choreo
// (optional)
void setSavedInputs(const String& savedInputsName);
void setSavedInputs(const char* savedInputsName);
void setCredential(const String& credentialName);
void setCredential(const char* credentialName);
void setProfile(const String& profileName);
void setProfile(const char* profileName);
// sets an input to be used when executing a choreo.
// (optional or required, depending on the choreo being executed.)
void addInput(const String& inputName, const String& inputValue);
void addInput(const char* inputName, const char* inputValue);
void addInput(const char* inputName, const String& inputValue);
void addInput(const String& inputName, const char* inputValue);
// sets an output filter to be used to process the choreo output
// (optional)
void addOutputFilter(const char* filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const String& variableName);
// run the choreo using the current input info
int run(uint16_t timeoutSecs = DEFAULT_CHOREO_TIMEOUT);
char* getResponseData() {return m_respData;}
char* getHTTPResponseCode() {return m_httpCodeStr;}
// Stream interface
void close() {};
int available();
int read();
int peek();
void flush();
//Print interface
size_t write(uint8_t data);
enum Error {
SUCCESS = 0,
FAILURE,
TEMBOO_ERROR_ACCOUNT_MISSING = 201,
TEMBOO_ERROR_CHOREO_MISSING = 203,
TEMBOO_ERROR_APPKEY_NAME_MISSING = 205,
TEMBOO_ERROR_APPKEY_MISSING = 207,
TEMBOO_ERROR_HTTP_ERROR = 223,
TEMBOO_ERROR_TIMEOUT = 225,
TEMBOO_ERROR_MEMORY = 900,
TEMBOO_ERROR_TCPIP_CONNECT_FAIL = 901,
TEMBOO_ERROR_NO_RESPONSE = 0xFFFF
};
protected:
static const size_t MAX_RESPONSE_SIZE = 900;
TembooCoAPClient& m_client;
const char* m_accountName;
const char* m_appKeyName;
const char* m_appKeyValue;
const char* m_path;
ChoreoInputSet m_inputs;
ChoreoInputExpressionSet m_expressions;
ChoreoSensorInputSet m_sensors;
ChoreoOutputSet m_outputs;
ChoreoPreset m_preset;
ChoreoDevice m_deviceType;
ChoreoDevice m_deviceName;
char m_httpCodeStr[4];
char* m_respData;
uint16_t m_requestId;
static uint16_t s_nextRequestId;
// variables for the stream interface
size_t m_availableChars;
const char* m_nextChar;
enum State {START,
HTTP_CODE_PRE,
HTTP_CODE_VALUE,
HTTP_CODE_SUF,
RESP_DATA,
END};
State m_nextState;
protected:
int waitForResponse(TembooTimer& timer);
uint16_t getRequestId() {return m_requestId;}
};
#endif //TEMBOO_H_

View File

@@ -0,0 +1,861 @@
/*
###############################################################################
#
# Temboo MQTT Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT
///////////////////////////////////////////////////////
#include <avr/pgmspace.h>
#include "utility/TembooGlobal.h"
#include "utility/TembooMQTTSession.h"
#include "utility/MQTTPacket.h"
#include "TembooMQTTEdgeDevice.h"
#ifndef UINT16_MAX
#define UINT16_MAX (0xFFFF)
#endif
static const char HTTP_CODE_PREFIX[] PROGMEM = "HTTP_CODE\x0A\x1F";
static const char HTTP_CODE_SUFFIX[] PROGMEM = "\x0A\x1E";
static const char REQUEST_TOPIC_PREFIX[] PROGMEM = "/temboo/req/";
static const char REQUEST_ACK_TOPIC_PREFIX[] PROGMEM = "/temboo/ack/";
static const char RESPONSE_TOPIC_PREFIX[] PROGMEM = "/temboo/resp/";
static const char RESPONSE_DATA_TOPIC_PREFIX[] PROGMEM = "/temboo/resp-data/";
// TIME_TOPIC must NOT be PROGMEM
static const char TIME_TOPIC[] = "/temboo/time";
static TembooMQTTChoreo* volatile g_currentChoreo = NULL;
uint16_t TembooMQTTChoreo::s_nextRequestId = 0;
void handleDataMessage(MQTT::MessageData& md) {
TEMBOO_TRACE("data: ");
MQTT::Message& msg = md.message;
char* c;
unsigned int count;
for (c = (char*)msg.payload, count = 0; *c != '\0' && isdigit(*c) && count < msg.payloadlen; c++) {
count++;
if (count > 5) {
TEMBOO_TRACELN(" long");
return;
}
}
if (count == 0) {
TEMBOO_TRACELN(" short");
return;
}
if (TAG_VALUE_SEPARATOR != *c) {
TEMBOO_TRACELN(" bad msg");
return;
}
// Replace the : with \0 so we can use strtoul.
*c = '\0';
unsigned long requestId = strtoul((char*)msg.payload, NULL, 10);
if (UINT16_MAX < requestId) {
TEMBOO_TRACELN(" bad id");
return;
}
if ((NULL == g_currentChoreo) || (g_currentChoreo->getRequestId() != (uint16_t)requestId)) {
TEMBOO_TRACE(" stale id: ");
TEMBOO_TRACELN(requestId);
return;
}
g_currentChoreo->setResponseData(c + 1, msg.payloadlen - strlen((char*)msg.payload) - 1);
TEMBOO_TRACELN(" ok");
}
bool validateUint16PairMessage(const char* msg) {
const char* c;
int count = 0;
for (c = msg; *c != '\0' && isdigit(*c); c++) {
count++;
if (count > 5)
return false;
}
if (count == 0) {
return false;
}
if (TAG_VALUE_SEPARATOR != *c) {
return false;
}
c++;
for (count = 0; *c != '\0' && isdigit(*c); c++) {
count++;
if (count > 5) {
return false;
}
}
if (count == 0) {
return false;
}
return true;
}
void handleResponseMessage(MQTT::MessageData& md) {
TEMBOO_TRACE("resp: ");
MQTT::Message& msg = md.message;
// Expected max length is 9 (for 65535:599)
if (msg.payloadlen > 9) {
TEMBOO_TRACELN("long");
return;
}
if (msg.payloadlen < 3) {
TEMBOO_TRACELN("short");
return;
}
// Copy the payload and nul terminate it;
char respStr[10];
memcpy(respStr, msg.payload, msg.payloadlen);
respStr[msg.payloadlen] = '\0';
TEMBOO_TRACE(respStr);
if (!validateUint16PairMessage(respStr)) {
TEMBOO_TRACELN(" bad msg");
return;
}
char* next;
unsigned long respCode = 0;
unsigned long requestId = strtoul(respStr, &next, 10);
// validate only checks that the request ID
// has at least 1 but no more than 6 digits.
// so we have to check the actual value here.
if (UINT16_MAX < requestId) {
TEMBOO_TRACELN(" bad id");
return;
}
// If the request ID in the message doesn't match the
// current request ID, then it's a stale ack.
if ((NULL == g_currentChoreo) || (g_currentChoreo->getRequestId() != (uint16_t)requestId)) {
TEMBOO_TRACE(" stale id: ");
TEMBOO_TRACELN(respStr);
return;
}
next++;
respCode = strtoul(next, NULL, 10);
// Validate only checks that the ack code
// has at least 1 but no more than 6 digits,
// so we have to check the actual value here.
if (1000 <= respCode) {
TEMBOO_TRACELN(" bad code");
return;
}
// FINALLY, everything's OK.
// pass the value to the waiting choreo.
g_currentChoreo->setHTTPResponseCode(next);
TEMBOO_TRACELN(" ok");
}
void handleTimeMessage(MQTT::MessageData& md) {
TEMBOO_TRACE("time: ");
MQTT::Message& msg = md.message;
// Time messages should be <= 10 characters long.
if (msg.payloadlen > 10) {
TEMBOO_TRACELN("long");
return;
}
if (msg.payloadlen == 0) {
TEMBOO_TRACELN("short");
return;
}
// Payload should consist only of digits (0 - 9)
for (unsigned int i = 0; i < msg.payloadlen; i++) {
if (! isdigit(((char*)msg.payload)[i])) {
TEMBOO_TRACELN("!digit");
return;
}
}
char timeStr[11];
memcpy(timeStr, msg.payload, msg.payloadlen);
timeStr[msg.payloadlen] = '\0';
TEMBOO_TRACE(timeStr);
uint32_t t = strtoul(timeStr, NULL, 10);
TembooMQTTSession::setTime(t);
TEMBOO_TRACELN(" ok");
}
void handleAckMessage(MQTT::MessageData& md) {
TEMBOO_TRACE("ack: ");
MQTT::Message& msg = md.message;
// Expected max length is 11 (for 65535:65535)
if (msg.payloadlen > 11) {
TEMBOO_TRACELN("long");
return;
}
if (msg.payloadlen == 0) {
TEMBOO_TRACELN("short");
return;
}
// Copy the payload and nul terminate it;
char ackStr[12];
memcpy(ackStr, msg.payload, msg.payloadlen);
ackStr[msg.payloadlen] = '\0';
TEMBOO_TRACE(ackStr);
if (!validateUint16PairMessage(ackStr)) {
TEMBOO_TRACELN(" bad msg");
return;
}
char* next;
unsigned long ackCode = TEMBOO_ERROR_FAILURE;
unsigned long requestId = strtoul(ackStr, &next, 10);
// validate only checks that the request ID
// has at least 1 but no more than 6 digits.
// so we have to check the actual value here.
if (UINT16_MAX < requestId) {
TEMBOO_TRACELN(" bad id");
return;
}
// If the request ID in the message doesn't match the
// current request ID, then it's a stale ack.
if ((NULL == g_currentChoreo) || (g_currentChoreo->getRequestId() != (uint16_t)requestId)) {
TEMBOO_TRACE(" stale id: ");
TEMBOO_TRACELN(ackStr);
return;
}
next++;
ackCode = strtoul(next, NULL, 10);
// Validate only checks that the ack code
// has at least 1 but no more than 6 digits,
// so we have to check the actual value here.
if (UINT16_MAX < ackCode) {
TEMBOO_TRACELN(" bad code");
return;
}
// FINALLY, everything's OK.
// pass the value to the waiting choreo.
g_currentChoreo->setAckCode(ackCode);
TEMBOO_TRACELN(" ok");
}
TembooMQTTClient::TembooMQTTClient(TembooMQTTIPStack& ipStack, unsigned int commandTimeoutMs )
: BaseClient (ipStack, commandTimeoutMs),
m_ipStack(ipStack) {
m_deviceId = NULL;
m_requestTopic = NULL;
m_ackTopic = NULL;
m_responseTopic = NULL;
m_dataTopic = NULL;
}
void TembooMQTTClient::makeTopics() {
m_requestTopic = strCatNew_P(REQUEST_TOPIC_PREFIX, m_deviceId);
m_ackTopic = strCatNew_P(REQUEST_ACK_TOPIC_PREFIX, m_deviceId);
m_responseTopic = strCatNew_P(RESPONSE_TOPIC_PREFIX, m_deviceId);
m_dataTopic = strCatNew_P(RESPONSE_DATA_TOPIC_PREFIX, m_deviceId);
}
int TembooMQTTClient::setDeviceId(char* id) {
int rc = TEMBOO_ERROR_FAILURE;
if (NULL == m_deviceId) {
m_deviceId = new char[strlen(id) + 1];
if (NULL != m_deviceId) {
strcpy(m_deviceId, id);
makeTopics();
rc = TEMBOO_ERROR_OK;
} else {
rc = TEMBOO_ERROR_MEMORY;
}
}
return rc;
}
int TembooMQTTClient::setDeviceIdFromMac(byte (&mac)[6]) {
int rc = TEMBOO_ERROR_FAILURE;
// Only do something if we don't already have a deviceId
if (NULL == m_deviceId) {
// generate the deviceId from the MAC address.
char macStr[13];
for (unsigned int i = 0; i < sizeof(mac)/sizeof(*mac); i++) {
byte m = mac[i] >> 4;
macStr[2 * i] = m < 10 ? ('0' + m):('A' + m - 10);
m = mac[i] & 0x0F;
macStr[2 * i + 1] = m < 10 ? ('0' + m):('A' + m - 10);
}
macStr[12] = '\0';
rc = setDeviceId(macStr);
}
return rc;
}
char* TembooMQTTClient::strCatNew_P(const char* s1, const char* s2) {
size_t len = strlen_P(s1) + strlen(s2) + 1;
char* result = new char[len];
if (NULL != result) {
strcpy_P(result, s1);
strcat(result, s2);
}
return result;
}
TembooMQTTClient::~TembooMQTTClient() {
if (NULL != m_requestTopic) delete[] m_requestTopic;
if (NULL != m_ackTopic) delete[] m_ackTopic;
if (NULL != m_responseTopic) delete[] m_responseTopic;
if (NULL != m_dataTopic) delete[] m_dataTopic;
if (NULL != m_deviceId) delete[] m_deviceId;
}
int TembooMQTTClient::connect(const char* hostname, int port) {
int rc = TEMBOO_ERROR_FAILURE;
if (NULL == m_deviceId) {
return TEMBOO_ERROR_DEVICE_ID_MISSING;
}
TEMBOO_TRACE("DEVICE ID: ");
TEMBOO_TRACELN(m_deviceId);
// Open a socket to the broker.
TEMBOO_TRACE("IP: ");
rc = m_ipStack.connect(hostname, port);
if (1 != rc) {
TEMBOO_TRACELN(rc);
rc = TEMBOO_ERROR_TCPIP_CONNECT_FAIL;
goto ErrorExit;
} else {
TEMBOO_TRACELN("OK");
// Establish an MQTT connection with the broker.
TEMBOO_TRACE("MQ ");
TEMBOO_TRACE(m_deviceId);
TEMBOO_TRACE(": ");
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.MQTTVersion = 3;
data.clientID.cstring = (char*)m_deviceId;
rc = connect(data);
if (MQTT::SUCCESS != rc) {
TEMBOO_TRACELN(rc);
rc = TEMBOO_ERROR_MQTT_CONNECT_FAIL;
goto ErrorExit;
}
TEMBOO_TRACELN("OK");
}
// Subscribe to the various topics we need to monitor.
//NOTE: 'subscribe()' does NOT copy the topic strings,
//so those strings must stay valid for the life of the connection.
TEMBOO_TRACE("SUB:");
//Yes, it's slightly risky counting on MQTT::SUCCESS always being 0.
rc = this->subscribe(m_ackTopic, MQTT::QOS1, handleAckMessage);
rc |= this->subscribe(m_responseTopic, MQTT::QOS1, handleResponseMessage);
rc |= this->subscribe(m_dataTopic, MQTT::QOS1, handleDataMessage);
rc |= this->subscribe(TIME_TOPIC, MQTT::QOS1, handleTimeMessage);
if (MQTT::SUCCESS != rc) {
TEMBOO_TRACELN("NO");
rc = TEMBOO_ERROR_MQTT_SUBSCRIBE_FAIL;
goto ErrorExit;
}
TEMBOO_TRACELN("OK");
return TEMBOO_ERROR_OK;
ErrorExit:
if (isConnected()) {
disconnect();
}
m_ipStack.disconnect();
return rc;
}
bool TembooMQTTClient::isConnected() {
if (m_ipStack.isConnected()) {
return BaseClient::isConnected();
}
return false;
}
int TembooMQTTClient::sendChoreoRequest(const char* request, size_t len) {
MQTT::Message message;
message.qos = MQTT::QOS0;
message.retained = false;
message.dup = false;
message.payload = (void*)request;
message.payloadlen = len;
int rc = this->publish(this->m_requestTopic, message);
return rc == MQTT::SUCCESS ? TEMBOO_ERROR_OK : rc;
}
TembooMQTTChoreo::TembooMQTTChoreo(TembooMQTTClient& client) :
m_client(client),
m_accountName(NULL),
m_appKeyName(NULL),
m_appKeyValue(NULL),
m_path(NULL),
m_haveHttpCode(false),
m_ackCode(0),
m_haveAckCode(false),
m_haveData(false),
m_requestId(0),
m_availableChars(0),
m_nextChar(NULL),
m_nextState(END),
m_packetStatus(TEMBOO_ERROR_OK)
{
}
TembooMQTTChoreo::~TembooMQTTChoreo() {
memset(m_respData, '\0', sizeof(m_respData)/sizeof(m_respData[0]));
}
void TembooMQTTChoreo::setAccountName(const String& accountName) {
m_accountName = accountName.c_str();
}
void TembooMQTTChoreo::setAccountName(const char* accountName) {
m_accountName = accountName;
}
void TembooMQTTChoreo::setAppKeyName(const String& appKeyName) {
m_appKeyName = appKeyName.c_str();
}
void TembooMQTTChoreo::setAppKeyName(const char* appKeyName) {
m_appKeyName = appKeyName;
}
void TembooMQTTChoreo::setAppKey(const String& appKeyValue) {
m_appKeyValue = appKeyValue.c_str();
}
void TembooMQTTChoreo::setAppKey(const char* appKeyValue) {
m_appKeyValue = appKeyValue;
}
void TembooMQTTChoreo::setChoreo(const String& path) {
m_path = path.c_str();
}
void TembooMQTTChoreo::setChoreo(const char* path) {
m_path = path;
}
void TembooMQTTChoreo::setSavedInputs(const String& savedInputsName) {
m_preset.put(savedInputsName.c_str());
}
void TembooMQTTChoreo::setSavedInputs(const char* savedInputsName) {
m_preset.put(savedInputsName);
}
void TembooMQTTChoreo::setCredential(const String& credentialName) {
m_preset.put(credentialName.c_str());
}
void TembooMQTTChoreo::setCredential(const char* credentialName) {
m_preset.put(credentialName);
}
void TembooMQTTChoreo::setProfile(const String& profileName) {
m_preset.put(profileName.c_str());
}
void TembooMQTTChoreo::setProfile(const char* profileName) {
m_preset.put(profileName);
}
void TembooMQTTChoreo::addInput(const String& inputName, const String& inputValue) {
m_inputs.put(inputName.c_str(), inputValue.c_str());
}
void TembooMQTTChoreo::addInput(const char* inputName, const char* inputValue) {
m_inputs.put(inputName, inputValue);
}
void TembooMQTTChoreo::addInput(const char* inputName, const String& inputValue) {
m_inputs.put(inputName, inputValue.c_str());
}
void TembooMQTTChoreo::addInput(const String& inputName, const char* inputValue) {
m_inputs.put(inputName.c_str(), inputValue);
}
void TembooMQTTChoreo::addOutputFilter(const char* outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath, variableName);
}
void TembooMQTTChoreo::addOutputFilter(const String& outputName, const char* filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName);
}
void TembooMQTTChoreo::addOutputFilter(const char* outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName);
}
void TembooMQTTChoreo::addOutputFilter(const String& outputName, const String& filterPath, const char* variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName);
}
void TembooMQTTChoreo::addOutputFilter(const char* outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath, variableName.c_str());
}
void TembooMQTTChoreo::addOutputFilter(const String& outputName, const char* filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath, variableName.c_str());
}
void TembooMQTTChoreo::addOutputFilter(const char* outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName, filterPath.c_str(), variableName.c_str());
}
void TembooMQTTChoreo::addOutputFilter(const String& outputName, const String& filterPath, const String& variableName) {
m_outputs.put(outputName.c_str(), filterPath.c_str(), variableName.c_str());
}
int TembooMQTTChoreo::waitForResponse(volatile bool& var, TembooMQTTSession& session, ArduinoTimer& timer) {
int rc = TEMBOO_ERROR_OK;
while (!var) {
if (timer.expired()) {
rc = TEMBOO_ERROR_TIMEOUT;
break;
}
if (!m_client.isConnected()) {
TEMBOO_TRACELN("DISCO");
rc = TEMBOO_ERROR_MQTT_DISCONNECT;
break;
}
m_client.yield(YIELD_TIME_MILLIS);
}
return rc;
}
void TembooMQTTChoreo::setHTTPResponseCode(char* respCodeStr) {
//NOTE: if we run in a true multithreaded environment, this code
//will have to be synchronized somehow. Checking the m_haveHttpCode
//flag is insufficient to prevent race conditions.
if (!m_haveHttpCode) {
strncpy(m_httpCodeStr, respCodeStr, sizeof(m_httpCodeStr)-1);
m_httpCodeStr[sizeof(m_httpCodeStr)-1] = '\0';
m_haveHttpCode = true;
}
}
void TembooMQTTChoreo::setResponseData(char* payload, size_t payloadLen) {
//NOTE: if we run in a true multithreaded environment, this code
//will have to be synchronized somehow. Checking the m_haveData
//flag is insufficient to prevent race conditions.
if (!m_haveData) {
size_t len = 0;
if(payloadLen > MAX_MESSAGE_SIZE) {
m_packetStatus = TEMBOO_ERROR_MQTT_BUFFER_OVERFLOW;
len = sizeof(m_respData)/sizeof(m_respData[0])-1;
} else if (payloadLen > MAX_RESPONSE_SIZE) {
m_packetStatus = TEMBOO_ERROR_MQTT_DATA_TRUNCATED;
len = sizeof(m_respData)/sizeof(m_respData[0])-1;
} else {
len = payloadLen;
}
memcpy(m_respData, payload, len);
m_respData[len] = '\0';
m_haveData = true;
}
}
void TembooMQTTChoreo::setAckCode(uint16_t ackCode) {
//NOTE: if we run in a true multithreaded environment, this code
//will have to be synchronized somehow. Checking the m_haveAckCode
//flag is insufficient to prevent race conditions.
if (!m_haveAckCode) {
m_ackCode = ackCode;
m_haveAckCode = true;
}
}
int TembooMQTTChoreo::run(uint16_t timeoutSecs) {
m_nextChar = NULL;
if (!m_client.isConnected()) {
return TEMBOO_ERROR_MQTT_DISCONNECT;
}
if (IS_EMPTY(m_accountName)) {
return TEMBOO_ERROR_ACCOUNT_MISSING;
}
if (IS_EMPTY(m_path)) {
return TEMBOO_ERROR_CHOREO_MISSING;
}
if (IS_EMPTY(m_appKeyName)) {
return TEMBOO_ERROR_APPKEY_NAME_MISSING;
}
if (IS_EMPTY(m_appKeyValue)) {
return TEMBOO_ERROR_APPKEY_MISSING;
}
m_packetStatus = TEMBOO_ERROR_OK;
TembooMQTTSession session(m_client);
m_requestId = s_nextRequestId++;
m_haveAckCode = false;
m_ackCode = TEMBOO_ERROR_NO_RESPONSE;
m_haveHttpCode = false;
memset(m_httpCodeStr, '\0', sizeof(m_httpCodeStr)/sizeof(m_httpCodeStr[0]));
m_haveData = false;
memset(m_respData, '\0', sizeof(m_respData)/sizeof(m_respData[0]));
m_availableChars = 0;
m_nextState = START;
g_currentChoreo = this;
TEMBOO_TRACE("RUN ");
TEMBOO_TRACE(m_requestId);
TEMBOO_TRACELN(" START");
ArduinoTimer timer(timeoutSecs * 1000L);
int rc = session.executeChoreo( m_requestId, m_accountName, m_appKeyName, m_appKeyValue, m_path, m_inputs, m_expressions, m_sensors, m_outputs, m_preset, m_deviceType, m_deviceName);
if (TEMBOO_ERROR_OK != rc) {
goto ErrorExit;
}
rc = waitForResponse(m_haveAckCode, session, timer);
if (TEMBOO_ERROR_OK != rc){
goto ErrorExit;
}
TEMBOO_TRACE("RUN ");
TEMBOO_TRACE(m_requestId);
TEMBOO_TRACE(" ACK: ");
TEMBOO_TRACELN(m_ackCode);
if (TEMBOO_ERROR_OK != m_ackCode) {
rc = m_ackCode;
goto ErrorExit;
}
rc = waitForResponse(m_haveHttpCode, session, timer);
if (TEMBOO_ERROR_OK != rc) {
goto ErrorExit;
}
TEMBOO_TRACE("RUN ");
TEMBOO_TRACE(m_requestId);
TEMBOO_TRACE(" RESP: ");
TEMBOO_TRACELN(m_httpCodeStr);
rc = waitForResponse(m_haveData, session, timer);
if (TEMBOO_ERROR_OK != rc) {
goto ErrorExit;
}
m_availableChars = strlen(m_respData) + strlen(m_httpCodeStr) + strlen_P(HTTP_CODE_PREFIX) + strlen_P(HTTP_CODE_SUFFIX);
m_nextChar = HTTP_CODE_PREFIX;
TEMBOO_TRACE("RUN ");
TEMBOO_TRACE(m_requestId);
TEMBOO_TRACE(" ");
TEMBOO_TRACE(m_availableChars);
TEMBOO_TRACELN(" CHARS");
TEMBOO_TRACELN(m_respData);
rc = m_packetStatus;
ErrorExit:
if (TEMBOO_ERROR_OK != rc) {
TEMBOO_TRACE("RUN ");
TEMBOO_TRACE(m_requestId);
TEMBOO_TRACE(" ERROR:");
TEMBOO_TRACELN(rc);
}
g_currentChoreo = NULL;
m_client.yield(YIELD_TIME_MILLIS);
return rc;
}
int TembooMQTTChoreo::available() {
return m_availableChars;
}
int TembooMQTTChoreo::peek() {
if (m_availableChars) {
if (m_nextState == HTTP_CODE_VALUE || m_nextState == RESP_DATA || m_nextState == END) {
return (int)*m_nextChar;
} else {
return (int)pgm_read_byte(m_nextChar);
}
} else {
return -1;
}
}
int TembooMQTTChoreo::read() {
if (m_haveData) {
int c = 0;
switch(m_nextState) {
case START:
m_nextChar = HTTP_CODE_PREFIX;
c = (int)pgm_read_byte(m_nextChar++);
m_nextState = HTTP_CODE_PRE;
break;
case HTTP_CODE_PRE:
c = (int)pgm_read_byte(m_nextChar++);
if (pgm_read_byte(m_nextChar) == '\0') {
m_nextState = HTTP_CODE_VALUE;
m_nextChar = m_httpCodeStr;
}
break;
case HTTP_CODE_VALUE:
c = (int)(*m_nextChar++);
if (*m_nextChar == '\0') {
m_nextState = HTTP_CODE_SUF;
m_nextChar = HTTP_CODE_SUFFIX;
}
break;
case HTTP_CODE_SUF:
c = (int)pgm_read_byte(m_nextChar++);
if (pgm_read_byte(m_nextChar) == '\0') {
m_nextState = RESP_DATA;
m_nextChar = m_respData;
}
break;
case RESP_DATA:
c = (int)(*m_nextChar++);
if ((m_availableChars - 1) <= 0) {
m_nextState = END;
}
break;
case END:
default:
c = -1;
}
if (m_availableChars > 0) {
m_availableChars--;
}
return c;
} else {
return -1;
}
}
size_t TembooMQTTChoreo::write(uint8_t data) {
return 0;
}
void TembooMQTTChoreo::flush() {
m_nextChar = NULL;
m_nextState = END;
m_availableChars = 0;
}

View File

@@ -0,0 +1,249 @@
/*
###############################################################################
#
# Temboo MQTT Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOMQTT_H_
#define TEMBOOMQTT_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
#include <Arduino.h>
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT
///////////////////////////////////////////////////////
#include "Arduino.h"
#include "utility/MQTTClient.h"
#include "utility/ArduinoTimer.h"
#include "utility/TembooTags.h"
#include "utility/TembooMQTTIPStack.h"
#include "utility/ChoreoInputSet.h"
#include "utility/ChoreoInputExpressionSet.h"
#include "utility/ChoreoSensorInputSet.h"
#include "utility/ChoreoOutputSet.h"
#include "utility/ChoreoPreset.h"
#include "utility/ChoreoDevice.h"
#define IS_EMPTY(s) (NULL == s || '\0' == *s)
#define TEMBOO_ERROR_OK (0)
#define TEMBOO_ERROR_FAILURE (1)
#define TEMBOO_ERROR_ACCOUNT_MISSING (201)
#define TEMBOO_ERROR_CHOREO_MISSING (203)
#define TEMBOO_ERROR_APPKEY_NAME_MISSING (205)
#define TEMBOO_ERROR_APPKEY_MISSING (207)
#define TEMBOO_ERROR_HTTP_ERROR (223)
#define TEMBOO_ERROR_TIMEOUT (225)
#define TEMBOO_ERROR_MEMORY (900)
#define TEMBOO_ERROR_TCPIP_CONNECT_FAIL (901)
#define TEMBOO_ERROR_MQTT_CONNECT_FAIL (902)
#define TEMBOO_ERROR_MQTT_SUBSCRIBE_FAIL (903)
#define TEMBOO_ERROR_MQTT_DISCONNECT (904)
#define TEMBOO_ERROR_DEVICE_ID_MISSING (905)
/*
* The data from the MQTT is too large to fit in the buffer.
* The MQTT read buffer read as much as it could and discarded
* the rest of the packet data. Increase MAX_MESSAGE_SIZE
* to read more data in MQTT's readbuf
*/
#define TEMBOO_ERROR_MQTT_BUFFER_OVERFLOW (906)
/*
* There was more data to be returned in the packet data than could
* fit in the return buffer. Incease MAX_RESPONSE_SIZE to read more
* of the packet response
*/
#define TEMBOO_ERROR_MQTT_DATA_TRUNCATED (907)
#define TEMBOO_ERROR_NO_RESPONSE (0xFFFF)
static const int MAX_MESSAGE_SIZE = 512;
static const int MAX_RESPONSE_SIZE = 100;
static const int MAX_HANDLERS = 4;
static const int YIELD_TIME_MILLIS = 200;
typedef MQTT::Client<TembooMQTTIPStack, ArduinoTimer, MAX_MESSAGE_SIZE, MAX_HANDLERS> BaseClient;
class TembooMQTTClient : public BaseClient {
public:
TembooMQTTClient(TembooMQTTIPStack& ipStack, unsigned int commandTimeoutMs = 30000 );
virtual ~TembooMQTTClient();
using BaseClient::connect;
int connect(const char* hostname, int port=1883);
using BaseClient::isConnected;
bool isConnected();
int sendChoreoRequest(const char* request, size_t len);
int setDeviceId(char* id);
int setDeviceIdFromMac(byte (&mac)[6]);
protected:
TembooMQTTIPStack& m_ipStack;
char* m_deviceId;
char* m_requestTopic;
char* m_ackTopic;
char* m_responseTopic;
char* m_dataTopic;
char* strCatNew_P(const char*, const char*);
void makeTopics();
};
class TembooMQTTSession;
class TembooMQTTChoreo : public Stream {
public:
// Constructor.
// client - an instance of a TembooMQTTClient.
// Used to communicate with a Temboo Gateway.
TembooMQTTChoreo(TembooMQTTClient& client);
~TembooMQTTChoreo();
// Does nothing. Just for source compatibility with Yun code.
void begin() {};
// Sets the account name to use when communicating with Temboo.
// (required)
void setAccountName(const String& accountName);
void setAccountName(const char* accountName);
// Sets the application key name to use with choreo execution requests.
// (required)
void setAppKeyName(const String& appKeyName);
void setAppKeyName(const char* appKeyName);
// Sets the application key value to use with choreo execution requests
// (required)
void setAppKey(const String& appKey);
void setAppKey(const char* appKey);
// sets the name of the choreo to be executed.
// (required)
void setChoreo(const String& choreoPath);
void setChoreo(const char* choreoPath);
// sets the name of the saved inputs to use when executing the choreo
// (optional)
void setSavedInputs(const String& savedInputsName);
void setSavedInputs(const char* savedInputsName);
void setCredential(const String& credentialName);
void setCredential(const char* credentialName);
void setProfile(const String& profileName);
void setProfile(const char* profileName);
// sets an input to be used when executing a choreo.
// (optional or required, depending on the choreo being executed.)
void addInput(const String& inputName, const String& inputValue);
void addInput(const char* inputName, const char* inputValue);
void addInput(const char* inputName, const String& inputValue);
void addInput(const String& inputName, const char* inputValue);
// sets an output filter to be used to process the choreo output
// (optional)
void addOutputFilter(const char* filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const char* variableName);
void addOutputFilter(const char* filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const char* filterPath, const String& variableName);
void addOutputFilter(const char* filterName, const String& filterPath, const String& variableName);
void addOutputFilter(const String& filterName, const String& filterPath, const String& variableName);
// run the choreo using the current input info
int run(uint16_t timeoutSecs);
char* getResponseData() {return m_respData;}
char* getHTTPResponseCode() {return m_httpCodeStr;}
// Stream interface - see the Arduino library documentation.
void close() {};
int available();
int read();
int peek();
void flush();
//Print interface - see the Arduino library documentation
size_t write(uint8_t data);
protected:
TembooMQTTClient& m_client;
const char* m_accountName;
const char* m_appKeyName;
const char* m_appKeyValue;
const char* m_path;
ChoreoInputSet m_inputs;
ChoreoInputExpressionSet m_expressions;
ChoreoSensorInputSet m_sensors;
ChoreoOutputSet m_outputs;
ChoreoPreset m_preset;
ChoreoDevice m_deviceType;
ChoreoDevice m_deviceName;
char m_httpCodeStr[4];
volatile bool m_haveHttpCode;
uint16_t m_ackCode;
volatile bool m_haveAckCode;
char m_respData[MAX_RESPONSE_SIZE+1];
volatile bool m_haveData;
uint16_t m_requestId;
static uint16_t s_nextRequestId;
volatile uint16_t m_packetStatus;
// variables for the stream interface
size_t m_availableChars;
const char* m_nextChar;
enum State {START, HTTP_CODE_PRE, HTTP_CODE_VALUE, HTTP_CODE_SUF, RESP_DATA, END};
State m_nextState;
protected:
uint16_t getRequestId() {return m_requestId;}
void setAckCode(uint16_t ackCode);
void setHTTPResponseCode(char* respCode);
void setResponseData(char* data, size_t len);
int waitForResponse(volatile bool& var, TembooMQTTSession& session, ArduinoTimer& timer);
friend void handleDataMessage(MQTT::MessageData& md);
friend void handleResponseMessage(MQTT::MessageData& md);
friend void handleAckMessage(MQTT::MessageData& md);
};
#endif //TEMBOO_H_

View File

@@ -0,0 +1,267 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <Arduino.h>
#include <Mailbox.h>
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)
#include "avr/dtostrf.h"
#endif
#include "utility/TembooGlobal.h"
#include "TembooMonitoring.h"
const int MAX_MAILBOX_MESSAGE_SIZE = 128;
const unsigned long POLL_TIMEOUT = 50;
const unsigned long INITIATE_TIMEOUT_MS = 5000;
const unsigned long MCU_PING_PERIOD_MS = 5000;
void logTembooDebug(const char *c) {
Console.print("Debug: ");
Console.println(c);
}
void addWebSocketPinData(int pin, int pinVal, bool requestResponse) {
TembooMessaging::sendData(pin, pinVal, requestResponse);
}
void updateIntervalTime(int intervalTime) {
uint8_t msg[MAX_MAILBOX_MESSAGE_SIZE] = {0};
int messageSize = snprintf((char*)msg, MAX_MAILBOX_MESSAGE_SIZE, "Mi|V%i", intervalTime);
Mailbox.writeMessage(msg, messageSize);
}
TembooMessaging::TembooMessaging(TembooSensor** sensorTable, int sensorTableSize) {
m_accountName = NULL;
m_appKey = NULL;
m_appKeyName = NULL;
m_deviceID = NULL;
m_connectionStatus = false;
m_sensorTable = sensorTable;
m_sensorTableSize = sensorTableSize;
m_sensorTableDepth = 0;
m_connectionAttemptTime = millis();
m_lastPingTime = millis();
// init sensor array
int i = 0;
for (i = 0; i < m_sensorTableSize; i++) {
m_sensorTable[i] = NULL;
}
}
int TembooMessaging::addTembooSensor(TembooSensor* sensor) {
int i = 0;
for (; i < m_sensorTableSize; i++) {
if (m_sensorTable[i] == sensor) {
logTembooDebug("Sensor already added");
return -1;
}
if (m_sensorTable[i] == NULL) {
m_sensorTable[i] = sensor;
m_sensorTableDepth++;
return 0;
}
}
logTembooDebug("Sensor table full, sensor not added");
return -1;
}
void TembooMessaging::startMessaging() {
if (!running()) {
TEMBOO_TRACELN("starting messanger");
m_connectionStatus = false;
Process::begin("tembooMessaging");
runAsynchronously();
}
}
void TembooMessaging::setSensorsToDefaultState() {
int i = 0;
for (; i < m_sensorTableDepth; i++) {
if (m_sensorTable[i]->write != NULL) {
m_sensorTable[i]->write(m_sensorTable[i]->sensorConfig, m_sensorTable[i]->defaultValue);
}
}
}
void TembooMessaging::begin() {
Mailbox.begin();
startMessaging();
}
void TembooMessaging::setAccountName(const String& accountName) {
m_accountName = accountName.c_str();
}
void TembooMessaging::setAccountName(const char* accountName) {
m_accountName = accountName;
}
void TembooMessaging::setAppKeyName(const String& appKeyName) {
m_appKeyName = appKeyName.c_str();
}
void TembooMessaging::setAppKeyName(const char* appKeyName) {
m_appKeyName = appKeyName;
}
void TembooMessaging::setAppKey(const String& appKey) {
m_appKey = appKey.c_str();
}
void TembooMessaging::setAppKey(const char* appKey) {
m_appKey = appKey;
}
void TembooMessaging::setDeviceID(const String& deviceID) {
m_deviceID = deviceID.c_str();
}
void TembooMessaging::setDeviceID(const char* deviceID) {
m_deviceID = deviceID;
}
int TembooMessaging::initiateConnection() {
unsigned long now = millis();
if (now - m_connectionAttemptTime < INITIATE_TIMEOUT_MS) {
poll();
return TEMBOO_MONITORING_ERROR_NOT_CONNECTION_TIME;
}
if (m_accountName == NULL || *m_accountName == '\0') {
return TEMBOO_MONITORING_ERROR_ACCOUNT_MISSING;
}
if (m_appKeyName == NULL || *m_appKeyName == '\0') {
return TEMBOO_MONITORING_ERROR_APPKEY_NAME_MISSING;
}
if (m_deviceID == NULL || *m_deviceID == '\0') {
return TEMBOO_MONITORING_ERROR_DEVICEID_MISSING;
}
if (m_appKey == NULL || *m_appKey == '\0') {
return TEMBOO_MONITORING_ERROR_APPKEY_MISSING;
}
startMessaging();
int messageSize = strlen(m_accountName) + strlen(m_appKey) + strlen(m_appKeyName) + strlen(m_deviceID) + 11;
uint8_t msg[messageSize];
if (messageSize < MAX_MAILBOX_MESSAGE_SIZE) {
messageSize = snprintf((char*)msg, messageSize, "MI|N%s|K%s|B%s|A%s", m_accountName, m_appKeyName, m_deviceID, m_appKey);
Mailbox.writeMessage(msg, messageSize);
m_connectionAttemptTime = now;
} else {
return TEMBOO_MONITORING_ERROR_REQUEST_TOO_LARGE;
}
return TEMBOO_MONITORING_ERROR_OK;
}
WSMessageRequest TembooMessaging::poll() {
startMessaging();
long int now = millis();
WSMessageRequest rc = WS_NO_MESSAGE;
while (millis() - now < POLL_TIMEOUT) {
if (millis() - m_lastPingTime >= MCU_PING_PERIOD_MS) {
m_lastPingTime = millis();
sendPing();
}
if (Mailbox.messageAvailable()) {
uint8_t msg[MAX_MAILBOX_MESSAGE_SIZE] = {0};
int recvLen = Mailbox.readMessage(msg, MAX_MAILBOX_MESSAGE_SIZE);
if (recvLen > 0) {
rc = handleResponse(msg, m_sensorTable, m_sensorTableDepth, m_connectionStatus);
if (rc == WS_UPDATE_CONNECTED) {
//logTembooDebug("Connected to Temboo");
m_connectionStatus = true;
} else if (rc == WS_UPDATE_DISCONNECTED) {
//logTembooDebug("Disconnected from Temboo");
m_connectionStatus = false;
} else if (rc == WS_REQUEST_ERROR) {
// disconnect
sendError("Message request error");
}
}
}
}
return rc;
}
void TembooMessaging::updatePinValue(int pinNum, int pinVal) {
// save the data to the strcuture and then send to Temboo
int i = 0;
for (; i < m_sensorTableDepth; i++) {
if (m_sensorTable[i]->getSensorPin(m_sensorTable[i]->sensorConfig) == pinNum) {
// if pin has pinWrite as NULL, it is an input
// pin and needs to be stored. If not NULL,
// pin is an actuator and should not be stored
if(m_sensorTable[i]->write == NULL){
sendData(pinNum, pinVal, false);
} else {
sendData(pinNum, pinVal, true);
}
return;
}
}
logTembooDebug("Unable to update pin");
}
int TembooMessaging::retrievePinValue(int pinNum) {
// search through pin structure and return the pin value
int i = 0;
for (; i < m_sensorTableDepth; i++) {
if (m_sensorTable[i]->getSensorPin(m_sensorTable[i]->sensorConfig) == pinNum) {
return m_sensorTable[i]->read(m_sensorTable[i]->sensorConfig);
}
}
logTembooDebug("Unable to obtain pin value");
return 0;
}
void TembooMessaging::sendError(const char* errorText) {
uint8_t msg[MAX_MAILBOX_MESSAGE_SIZE] = {0};
int messageSize = snprintf((char*)msg, MAX_MAILBOX_MESSAGE_SIZE, "ME|T%s", errorText);
Mailbox.writeMessage(msg, messageSize);
}
void TembooMessaging::disconnectWSConnection(int closeCode, const char* closeReason) {
int messageSize = strlen(closeReason) + 11;
uint8_t msg[messageSize];
messageSize = snprintf((char*)msg, messageSize, "MF|O%i|r%s", closeCode, closeReason);
Mailbox.writeMessage(msg, messageSize);
}
void TembooMessaging::sendData(int pin, int pinVal, bool requestResponse) {
uint8_t msg[MAX_MAILBOX_MESSAGE_SIZE] = {0};
int messageSize = snprintf((char*)msg, MAX_MAILBOX_MESSAGE_SIZE, "MD|P%i|V%i%s", pin, pinVal, requestResponse ? "|Q" : "");
Mailbox.writeMessage(msg, messageSize);
}
void TembooMessaging::sendData(int pin, float pinVal) {
uint8_t msg[MAX_MAILBOX_MESSAGE_SIZE] = {0};
char floatStr[12] = {0};
dtostrf(pinVal, 4, 2, floatStr);
int messageSize = snprintf((char*)msg, MAX_MAILBOX_MESSAGE_SIZE, "MD|P%i|V%s", pin, floatStr);
Mailbox.writeMessage(msg, messageSize);
}
bool TembooMessaging::isConnected() {
if (running()) {
return m_connectionStatus;
}
return false;
}
void TembooMessaging::sendPing() {
Mailbox.writeMessage((uint8_t*)"P",1);
}

View File

@@ -0,0 +1,95 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOMESSAGING_H_
#define TEMBOOMESSAGING_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
#include <Arduino.h>
#include <Process.h>
#include "utility/TembooWebSocketRequestHandles.h"
#define TEMBOO_MONITORING_ERROR_OK (0)
#define TEMBOO_MONITORING_ERROR_ACCOUNT_MISSING (201)
#define TEMBOO_MONITORING_ERROR_CHOREO_MISSING (203)
#define TEMBOO_MONITORING_ERROR_APPKEY_NAME_MISSING (205)
#define TEMBOO_MONITORING_ERROR_APPKEY_MISSING (207)
#define TEMBOO_MONITORING_ERROR_DEVICEID_MISSING (209)
#define TEMBOO_MONITORING_ERROR_REQUEST_TOO_LARGE (251)
#define TEMBOO_MONITORING_ERROR_NOT_CONNECTION_TIME (253)
class TembooMessaging : public Process {
public:
TembooMessaging(TembooSensor** sensors, int pinTableSize);
void begin();
void setAccountName(const String& accountName);
void setAccountName(const char* accountName);
void setAppKeyName(const String& appKeyName);
void setAppKeyName(const char* appKeyName);
void setAppKey(const String& appKey);
void setAppKey(const char* appKey);
void setDeviceID(const String& appKey);
void setDeviceID(const char* appKey);
int initiateConnection();
WSMessageRequest poll();
static void sendData(int pin, int pinVal, bool requestResponse=false);
void sendData(int pin, float pinVal);
void updatePinValue(int pinNum, int pinVal);
int retrievePinValue(int pinNum);
int addTembooSensor(TembooSensor* sensor);
void setSensorsToDefaultState();
bool isConnected();
private:
const char* m_accountName;
const char* m_appKey;
const char* m_appKeyName;
const char* m_deviceID;
bool m_connectionStatus;
int m_sensorTableSize;
int m_sensorTableDepth;
long int m_connectionAttemptTime;
TembooSensor** m_sensorTable;
unsigned long m_lastPingTime;
void startMessaging();
void disconnectWSConnection(int closeCode, const char* closeReason);
void sendError(const char* errorText);
void sendPing();
};
#endif

View File

@@ -0,0 +1,51 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE)
#else
#include <string.h>
#include <Client.h>
#include <avr/pgmspace.h>
#include <TembooSSL.h>
#include "utility/TembooGlobal.h"
#include "utility/TembooSession.h"
TembooChoreoSSL::TembooChoreoSSL(Client& client) : TembooChoreo(client) {
m_accountName = NULL;
m_appKeyName = NULL;
m_appKeyValue = NULL;
m_path = NULL;
m_nextChar = NULL;
m_nextState = END;
}
int TembooChoreoSSL::run() {
return TembooChoreo::run(INADDR_NONE, 443, TEMBOO_CHOREO_DEFAULT_TIMEOUT_SECS);
}
int TembooChoreoSSL::run(uint16_t timeoutSecs) {
return TembooChoreo::run(INADDR_NONE, 443, timeoutSecs);
}
#endif

View File

@@ -0,0 +1,70 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOSSL_H_
#define TEMBOOSSL_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
#include <Arduino.h>
#if defined (ARDUINO_AVR_YUN) || defined (ARDUINO_AVR_TRE)
#include <Temboo.h>
class TembooChoreoSSL : public TembooChoreo {};
#else
///////////////////////////////////////////////////////
// BEGIN ARDUINO NON-YUN SUPPORT FOR SSL
///////////////////////////////////////////////////////
#include <Stream.h>
#include <Client.h>
#include <IPAddress.h>
#include <Temboo.h>
#include "utility/ChoreoInputSet.h"
#include "utility/ChoreoOutputSet.h"
#include "utility/ChoreoPreset.h"
class TembooChoreoSSL : public TembooChoreo {
public:
// Constructor.
// client - an instance of an Arduino Client that
// allows HTTPS conntections, usually WiFiSSL
TembooChoreoSSL(Client& client);
// run the choreo using the current input info
// uses port 443
int run();
// run the choreo with a user specified timeout
// uses port 443
int run(uint16_t timeoutSecs);
};
#endif //ARDUINO_AVR_YUN
#endif //TEMBOO_H_

View File

@@ -0,0 +1,70 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOYUNSHIELD_H_
#define TEMBOOYUNSHIELD_H_
#ifndef TEMBOO_LIBRARY_VERSION
#define TEMBOO_LIBRARY_VERSION 2
#endif
#include <Arduino.h>
///////////////////////////////////////////////////////
// BEGIN ARDUINO YUN SHIELD SUPPORT
///////////////////////////////////////////////////////
#include <Process.h>
class TembooYunShieldChoreo : public Process {
public:
void begin() {Process::begin("temboo");}
void setAccountName(const String& accountName) { addParameter("-a" + accountName);}
void setAppKeyName(const String& appKeyName) { addParameter("-u" + appKeyName);}
void setAppKey(const String& appKey) { addParameter("-p" + appKey);}
void setChoreo(const String& choreo) { addParameter("-c" + choreo);}
void setCredential(const String& credentialName) { addParameter("-e" + credentialName);}
void setSavedInputs(const String& savedInputsName) { addParameter("-e" + savedInputsName);}
void setProfile(const String& profileName) { addParameter("-e" + profileName);}
void addInput(const String& inputName, const String& inputValue) { addParameter("-i" + inputName + ":" + inputValue);}
void addOutputFilter(const String& filterName, const String& filterPath, const String& variableName) { addParameter("-o" + filterName + ":" + filterPath + ":" + variableName);}
void setSettingsFileToWrite(const String& filePath) { addParameter("-w" + filePath);}
void setSettingsFileToRead(const String& filePath) { addParameter("-r" + filePath);}
void setGatewayAddress(const String& addr) { addParameter("-s" + addr);}
void addInputExpression(const String& inputName, const String& inputValue) { addParameter("-f" + inputName + ":" + inputValue);}
void addInputWithSensor(const String& inputName, const String& inputValue) { addParameter("-f" + inputName + ":" + inputValue);}
void addSensorInput(const String& sensorName, long sensorValue, const String& conversion) {addParameter("-n" + sensorName + ":" + String(sensorValue) + ":" + conversion);}
void addSensorInput(const String& sensorName, long sensorValue) {addParameter("-v" + sensorName + ":" + String(sensorValue));}
void addSensorInput(const String& sensorName, long sensorValue, const String& conversion, const String& calibrationValue) {addParameter("-b" + sensorName + ":" + String(sensorValue) + ":" + conversion + ":" + calibrationValue);}
void addSensorInput(const String& sensorName, long sensorValue, const String& rawLow, const String& rawHigh, const String& scaleLow, const String& scaleHigh) {addParameter("-m" + sensorName + ":" + String(sensorValue) + ":" + rawLow+ ":" + rawHigh+ ":" + scaleLow+ ":" + scaleHigh);}
void addSensorValue(const String& sensorName, long sensorValue, const String& conversion) {addParameter("-n" + sensorName + ":" + String(sensorValue) + ":" + conversion);}
void addSensorValue(const String& sensorName, long sensorValue) {addParameter("-v" + sensorName + ":" + String(sensorValue));}
void addSensorValue(const String& sensorName, long sensorValue, const String& conversion, const String& calibrationValue) {addParameter("-b" + sensorName + ":" + String(sensorValue) + ":" + conversion + ":" + calibrationValue);}
void addSensorValue(const String& sensorName, long sensorValue, const String& rawLow, const String& rawHigh, const String& scaleLow, const String& scaleHigh) {addParameter("-m" + sensorName + ":" + String(sensorValue) + ":" + rawLow+ ":" + rawHigh+ ":" + scaleLow+ ":" + scaleHigh);}
void setDeviceName(const String& deviceName) {addParameter("-d" + deviceName);}
void setDeviceType(const String& deviceType) {addParameter("-t" + deviceType);}
};
#endif //TEMBOOYUNSHIELD_H_

View File

@@ -0,0 +1,62 @@
/*
###############################################################################
#
# Temboo MQTT edge device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef ARDUINOTIMER_H_
#define ARDUINOTIMER_H_
class ArduinoTimer {
public:
ArduinoTimer() {
this->startTimeMillis = 0;
this->durationMillis = 0;
}
ArduinoTimer(unsigned long durationMillis) {
this->countdown_ms(durationMillis);
}
bool expired() {
return left_ms() == 0;
}
void countdown_ms(unsigned long durationMillis) {
this->startTimeMillis = millis();
this->durationMillis = durationMillis;
}
void countdown(int durationSeconds) {
countdown_ms((unsigned long)durationSeconds * 1000L);
}
unsigned long left_ms() {
unsigned long elapsedMillis = millis() - this->startTimeMillis;
return elapsedMillis < this->durationMillis ? (this->durationMillis - elapsedMillis) : 0;
}
private:
unsigned long startTimeMillis;
unsigned long durationMillis;
};
#endif /* ARDUINOIMER_H_ */

View File

@@ -0,0 +1,96 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "BaseFormatter.h"
char BaseFormatter::escape(char c) {
char outChar = c;
switch(c) {
case '\\':
case '"':
outChar = '\\';
m_escapedChar = c;
break;
case '\b':
outChar = '\\';
m_escapedChar = 'b';
break;
case '\f':
outChar = '\\';
m_escapedChar = 'f';
break;
case '\n':
outChar = '\\';
m_escapedChar = 'n';
break;
case '\r':
outChar = '\\';
m_escapedChar = 'r';
break;
case '\t':
outChar = '\\';
m_escapedChar = 't';
break;
default:
m_escapedChar = '\0';
}
return outChar;
}
char BaseFormatter::finishEscape() {
char c = m_escapedChar;
m_escapedChar = '\0';
return c;
}
char BaseFormatter::readTagChar(int nextState) {
char c = pgm_read_byte(m_nextChar++);
if (pgm_read_byte(m_nextChar) == '\0') {
m_nextState = nextState;
}
return c;
}
char BaseFormatter::readValueChar(int nextState) {
char c;
if (isEscaping()) {
c = finishEscape();
if (*m_nextChar == '\0') {
m_nextState = nextState;
}
} else {
c = escape(*m_nextChar++);
if (!isEscaping()) {
if(*m_nextChar == '\0') {
m_nextState = nextState;
}
}
}
return c;
}
char BaseFormatter::readStartTagChar(const char* tag, int nextState) {
m_nextChar = tag;
char c = pgm_read_byte(m_nextChar++);
m_nextState = nextState;
return c;
}

View File

@@ -0,0 +1,46 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef BASEFORMATTER_H_
#define BASEFORMATTER_H_
#include "TembooGlobal.h"
class BaseFormatter {
public:
BaseFormatter() {m_escapedChar = '\0';}
protected:
const char* m_nextChar;
int m_nextState;
char m_escapedChar;
char escape(char c);
bool isEscaping() {return m_escapedChar != '\0';}
char finishEscape();
char readTagChar(int nextState);
char readValueChar(int nextState);
char readStartTagChar(const char* tag, int nextState);
};
#endif

View File

@@ -0,0 +1,23 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "ChoreoDevice.h"

View File

@@ -0,0 +1,42 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREODEVICE_H_
#define CHOREODEVICE_H_
#include <stddef.h>
#include "TembooGlobal.h"
// Can be used for device type or device name
class ChoreoDevice {
public:
ChoreoDevice() {m_name = NULL;}
ChoreoDevice(const char* name) {put(name);}
const char* getName() const {return m_name;}
void put(const char* name) {m_name = name;}
bool isEmpty() const {return m_name == NULL || *m_name == '\0';}
private:
const char* m_name;
};
#endif

View File

@@ -0,0 +1,90 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoDeviceFormatter.h"
#include "ChoreoDevice.h"
static const char TAG_DEVICE[] PROGMEM = "\"deviceType\":";
static const char TAG_DEVICE_NAME[] PROGMEM = "\"device\":";
ChoreoDeviceFormatter::ChoreoDeviceFormatter(const ChoreoDevice* device, Type type) {
m_device = device;
m_type = type;
reset();
}
void ChoreoDeviceFormatter::reset() {
m_nextChar = NULL;
if (m_device == NULL || m_device->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoDeviceFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoDeviceFormatter::next() {
char c = '\0';
switch(m_nextState) {
case START:
if (m_type == DEVICE_TYPE) {
c = readStartTagChar(TAG_DEVICE, DEVICE_TAG);
} else {
c = readStartTagChar(TAG_DEVICE_NAME, DEVICE_TAG);
}
break;
case DEVICE_TAG:
c = readTagChar(NAME_START);
break;
case NAME_START:
c = '"';
m_nextChar = m_device->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = NAME_END;
} else {
m_nextState = NAME;
}
break;
case NAME:
c = readValueChar(NAME_END);
break;
case NAME_END:
c = '"';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,55 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREODEVICEFORMATTER_H_
#define CHOREODEVICEFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoDevice.h"
class ChoreoDeviceFormatter : public BaseFormatter {
public:
enum Type {
DEVICE_TYPE,
DEVICE_NAME
};
ChoreoDeviceFormatter(const ChoreoDevice* device, Type type);
bool hasNext();
char next();
void reset();
protected:
enum State {
START,
DEVICE_TAG,
NAME_START,
NAME,
NAME_END,
END
};
const ChoreoDevice* m_device;
Type m_type;
};
#endif

View File

@@ -0,0 +1,34 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include "ChoreoInput.h"
ChoreoInput::ChoreoInput(ChoreoInput* prev, const char* name, const char* value) {
if (prev != NULL) {
prev->m_next = this;
}
m_next = NULL;
m_name = name;
m_value = value;
}

View File

@@ -0,0 +1,41 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUT_H_
#define CHOREOINPUT_H_
#include "TembooGlobal.h"
class ChoreoInput {
public:
ChoreoInput(ChoreoInput* prev, const char* name, const char* value);
const char* getName() const {return m_name;}
const char* getValue() const {return m_value;}
void setValue(const char* value) {m_value = value;}
ChoreoInput* getNext() const {return m_next;}
private:
ChoreoInput* m_next;
const char* m_name;
const char* m_value;
};
#endif

View File

@@ -0,0 +1,34 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include "ChoreoInputExpression.h"
ChoreoInputExpression::ChoreoInputExpression(ChoreoInputExpression* prev, const char* name, const char* value) {
if (prev != NULL) {
prev->m_next = this;
}
m_next = NULL;
m_name = name;
m_value = value;
}

View File

@@ -0,0 +1,41 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUTEXPRESSION_H_
#define CHOREOINPUTEXPRESSION_H_
#include "TembooGlobal.h"
class ChoreoInputExpression {
public:
ChoreoInputExpression(ChoreoInputExpression* prev, const char* name, const char* value);
const char* getName() const {return m_name;}
const char* getValue() const {return m_value;}
void setValue(const char* value) {m_value = value;}
ChoreoInputExpression* getNext() const {return m_next;}
private:
ChoreoInputExpression* m_next;
const char* m_name;
const char* m_value;
};
#endif //CHOREOINPUTEXPRESSION_H_

View File

@@ -0,0 +1,125 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoInputExpressionFormatter.h"
#include "ChoreoInputExpressionSet.h"
static const char TAG_INPUTS_START[] PROGMEM = "\"inputExpressions\":{";
ChoreoInputExpressionFormatter::ChoreoInputExpressionFormatter(const ChoreoInputExpressionSet* inputSet) {
m_inputSet = inputSet;
reset();
}
void ChoreoInputExpressionFormatter::reset() {
m_currentInput = NULL;
m_nextChar = NULL;
if (m_inputSet == NULL || m_inputSet->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoInputExpressionFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoInputExpressionFormatter::next() {
char c;
switch(m_nextState) {
case START:
c = readStartTagChar(TAG_INPUTS_START, INPUTS_TAG);
break;
case INPUTS_TAG:
c = readTagChar(NAME_START);
if (m_nextState == NAME_START) {
m_currentInput= m_inputSet->getFirstInput();
}
break;
case NAME_START:
c = '"';
m_nextChar = m_currentInput->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = NAME_END;
} else {
m_nextState = NAME;
}
break;
case NAME:
c = readValueChar(NAME_END);
break;
case NAME_END:
c = '"';
m_nextState = NAME_VALUE_SEPARATOR;
break;
case NAME_VALUE_SEPARATOR:
c = ':';
m_nextState = VALUE_START;
break;
case VALUE_START:
c = '"';
m_nextChar = m_currentInput->getValue();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = VALUE_END;
} else {
m_nextState = VALUE;
}
break;
case VALUE:
c = readValueChar(VALUE_END);
break;
case VALUE_END:
c = '"';
m_currentInput = m_currentInput->getNext();
if (m_currentInput != NULL) {
m_nextState = NEXT_INPUT;
} else {
m_nextState = INPUTS_END;
}
break;
case NEXT_INPUT:
c = ',';
m_nextChar = m_currentInput->getName();
m_nextState = NAME_START;
break;
case INPUTS_END:
c = '}';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,59 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUTEXPRESSIONFORMATTER_H_
#define CHOREOINPUTEXPRESSIONFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoInputExpressionSet.h"
class ChoreoInputExpressionFormatter : public BaseFormatter {
public:
ChoreoInputExpressionFormatter(const ChoreoInputExpressionSet* inputSet);
bool hasNext();
char next();
void reset();
protected:
const ChoreoInputExpressionSet* m_inputSet;
const ChoreoInputExpression* m_currentInput;
enum State {
START,
INPUTS_TAG,
NAME_START,
NAME,
NAME_END,
NAME_VALUE_SEPARATOR,
VALUE_START,
VALUE,
VALUE_END,
NEXT_INPUT,
INPUTS_END,
END
};
};
#endif //CHOREOINPUTEXPRESSIONFORMATTER_H_

View File

@@ -0,0 +1,80 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <string.h>
#include "ChoreoInputExpressionSet.h"
ChoreoInputExpressionSet::ChoreoInputExpressionSet() {
m_first = NULL;
}
ChoreoInputExpressionSet::~ChoreoInputExpressionSet() {
ChoreoInputExpression* i = m_first;
ChoreoInputExpression* next = NULL;
while (i != NULL) {
next = i->getNext();
delete i;
i = next;
}
}
void ChoreoInputExpressionSet::put(const char* name, const char* value) {
// Haven't set ANY inputs yet?
// Just create a new one.
if (m_first == NULL) {
m_first = new ChoreoInputExpression(NULL, name, value);
} else {
// Some inputs already set.
// See if we already have this input.
ChoreoInputExpression* last = NULL;
ChoreoInputExpression* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
// We already have an input with this name.
// Just update the value.
i->setValue(value);
break;
}
last = i;
i = i->getNext();
}
// We don't have an input with this name
// So we need to create a new one.
if (i == NULL) {
new ChoreoInputExpression(last, name, value);
}
}
}
const char* ChoreoInputExpressionSet::get(const char* name) const {
ChoreoInputExpression* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
return i->getValue();
}
i = i->getNext();
}
return NULL;
}

View File

@@ -0,0 +1,44 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUTEXPRESSIONSET_H_
#define CHOREOINPUTEXPRESSIONSET_H_
#include <stddef.h>
#include "TembooGlobal.h"
#include "ChoreoInputExpression.h"
class ChoreoInputExpressionSet {
public:
ChoreoInputExpressionSet();
~ChoreoInputExpressionSet();
void put(const char* name, const char* value);
const char* get(const char* name) const;
bool isEmpty() const {return m_first == NULL;}
const ChoreoInputExpression* getFirstInput() const {return m_first;}
protected:
ChoreoInputExpression* m_first;
};
#endif //CHOREOINPUTEXPRESSIONSET_H_

View File

@@ -0,0 +1,125 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoInputFormatter.h"
#include "ChoreoInputSet.h"
static const char TAG_INPUTS_START[] PROGMEM = "\"inputs\":{";
ChoreoInputFormatter::ChoreoInputFormatter(const ChoreoInputSet* inputSet) {
m_inputSet = inputSet;
reset();
}
void ChoreoInputFormatter::reset() {
m_currentInput = NULL;
m_nextChar = NULL;
if (m_inputSet == NULL || m_inputSet->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoInputFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoInputFormatter::next() {
char c;
switch(m_nextState) {
case START:
c = readStartTagChar(TAG_INPUTS_START, INPUTS_TAG);
break;
case INPUTS_TAG:
c = readTagChar(NAME_START);
if (m_nextState == NAME_START) {
m_currentInput= m_inputSet->getFirstInput();
}
break;
case NAME_START:
c = '"';
m_nextChar = m_currentInput->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = NAME_END;
} else {
m_nextState = NAME;
}
break;
case NAME:
c = readValueChar(NAME_END);
break;
case NAME_END:
c = '"';
m_nextState = NAME_VALUE_SEPARATOR;
break;
case NAME_VALUE_SEPARATOR:
c = ':';
m_nextState = VALUE_START;
break;
case VALUE_START:
c = '"';
m_nextChar = m_currentInput->getValue();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = VALUE_END;
} else {
m_nextState = VALUE;
}
break;
case VALUE:
c = readValueChar(VALUE_END);
break;
case VALUE_END:
c = '"';
m_currentInput = m_currentInput->getNext();
if (m_currentInput != NULL) {
m_nextState = NEXT_INPUT;
} else {
m_nextState = INPUTS_END;
}
break;
case NEXT_INPUT:
c = ',';
m_nextChar = m_currentInput->getName();
m_nextState = NAME_START;
break;
case INPUTS_END:
c = '}';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,58 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUTFORMATTER_H_
#define CHOREOINPUTFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoInputSet.h"
class ChoreoInputFormatter : public BaseFormatter {
public:
ChoreoInputFormatter(const ChoreoInputSet* inputSet);
bool hasNext();
char next();
void reset();
protected:
const ChoreoInputSet* m_inputSet;
const ChoreoInput* m_currentInput;
enum State {
START,
INPUTS_TAG,
NAME_START,
NAME,
NAME_END,
NAME_VALUE_SEPARATOR,
VALUE_START,
VALUE,
VALUE_END,
NEXT_INPUT,
INPUTS_END,
END
};
};
#endif

View File

@@ -0,0 +1,80 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <string.h>
#include "ChoreoInputSet.h"
ChoreoInputSet::ChoreoInputSet() {
m_first = NULL;
}
ChoreoInputSet::~ChoreoInputSet() {
ChoreoInput* i = m_first;
ChoreoInput* next = NULL;
while (i != NULL) {
next = i->getNext();
delete i;
i = next;
}
}
void ChoreoInputSet::put(const char* name, const char* value) {
// Haven't set ANY inputs yet?
// Just create a new one.
if (m_first == NULL) {
m_first = new ChoreoInput(NULL, name, value);
} else {
// Some inputs already set.
// See if we already have this input.
ChoreoInput* last = NULL;
ChoreoInput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
// We already have an input with this name.
// Just update the value.
i->setValue(value);
break;
}
last = i;
i = i->getNext();
}
// We don't have an input with this name
// So we need to create a new one.
if (i == NULL) {
new ChoreoInput(last, name, value);
}
}
}
const char* ChoreoInputSet::get(const char* name) const {
ChoreoInput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
return i->getValue();
}
i = i->getNext();
}
return NULL;
}

View File

@@ -0,0 +1,43 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOINPUTSET_H_
#define CHOREOINPUTSET_H_
#include <stddef.h>
#include "TembooGlobal.h"
#include "ChoreoInput.h"
class ChoreoInputSet {
public:
ChoreoInputSet();
~ChoreoInputSet();
void put(const char* name, const char* value);
const char* get(const char* name) const;
bool isEmpty() const {return m_first == NULL;}
const ChoreoInput* getFirstInput() const {return m_first;}
protected:
ChoreoInput* m_first;
};
#endif

View File

@@ -0,0 +1,37 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include "ChoreoOutput.h"
ChoreoOutput::ChoreoOutput(ChoreoOutput* prev, const char* name, const char* path, const char* var) {
if (prev != NULL) {
prev->m_next = this;
}
m_next = NULL;
m_name = name;
m_path = path;
m_var = var;
}

View File

@@ -0,0 +1,44 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOOUTPUT_H_
#define CHOREOOUTPUT_H_
#include "TembooGlobal.h"
class ChoreoOutput {
public:
ChoreoOutput(ChoreoOutput* prev, const char* name, const char* path, const char* var);
const char* getName() const {return m_name;}
const char* getPath() const {return m_path;}
const char* getVariable() const {return m_var;}
void setPath(const char* path) {m_path = path;}
void setVariable(const char* variable) {m_var = variable;}
ChoreoOutput* getNext() const {return m_next;}
private:
ChoreoOutput* m_next;
const char* m_name;
const char* m_path;
const char* m_var;
};
#endif

View File

@@ -0,0 +1,181 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoOutputFormatter.h"
#include "ChoreoOutputSet.h"
static const char TAG_OUTPUTS_START[] PROGMEM = "\"outputs\":[";
static const char TAG_NAME[] PROGMEM = "\"name\":";
static const char TAG_PATH[] PROGMEM = "\"path\":";
static const char TAG_VAR[] PROGMEM = "\"variable\":";
ChoreoOutputFormatter::ChoreoOutputFormatter(const ChoreoOutputSet* outputSet) {
m_outputSet = outputSet;
reset();
}
void ChoreoOutputFormatter::reset() {
m_currentOutput = NULL;
m_nextChar = NULL;
if (m_outputSet == NULL || m_outputSet->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoOutputFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoOutputFormatter::next() {
char c = '\0';
switch(m_nextState) {
case START:
c = readStartTagChar(TAG_OUTPUTS_START, OUTPUTS_TAG);
break;
case OUTPUTS_TAG:
c = readTagChar(OUTPUT_START);
if (m_nextState == OUTPUT_START) {
m_currentOutput = m_outputSet->getFirstOutput();
}
break;
case OUTPUT_START:
c = '{';
m_nextChar = TAG_NAME;
m_nextState = NAME_TAG;
break;
case NAME_TAG:
c = readTagChar(NAME_START);
break;
case NAME_START:
c = '"';
m_nextChar = m_currentOutput->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = NAME_END;
} else {
m_nextState = NAME;
}
break;
case NAME:
c = readValueChar(NAME_END);
break;
case NAME_END:
c = '"';
m_nextState = NAME_PATH_SEPARATOR;
break;
case NAME_PATH_SEPARATOR:
c = ',';
m_nextState = PATH_TAG;
m_nextChar = TAG_PATH;
break;
case PATH_TAG:
c = readTagChar(PATH_START);
break;
case PATH_START:
c = '"';
m_nextChar = m_currentOutput->getPath();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = PATH_END;
} else {
m_nextState = PATH;
}
break;
case PATH:
c = readValueChar(PATH_END);
break;
case PATH_END:
c = '"';
m_nextState = PATH_VAR_SEPARATOR;
break;
case PATH_VAR_SEPARATOR:
c = ',';
m_nextState = VAR_TAG;
m_nextChar = TAG_VAR;
break;
case VAR_TAG:
c = readTagChar(VAR_START);
break;
case VAR_START:
c = '"';
m_nextChar = m_currentOutput->getVariable();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = VAR_END;
} else {
m_nextState = VAR;
}
break;
case VAR:
c = readValueChar(VAR_END);
break;
case VAR_END:
c = '"';
m_nextState = OUTPUT_END;
break;
case OUTPUT_END:
c = '}';
m_currentOutput = m_currentOutput->getNext();
if (m_currentOutput != NULL) {
m_nextState = NEXT_OUTPUT;
} else {
m_nextState = OUTPUTS_END;
}
break;
case NEXT_OUTPUT:
c = ',';
m_nextChar = m_currentOutput->getName();
m_nextState = OUTPUT_START;
break;
case OUTPUTS_END:
c = ']';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,66 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOOUTPUTFORMATTER_H_
#define CHOREOOUTPUTFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoOutputSet.h"
class ChoreoOutputFormatter : public BaseFormatter {
public:
ChoreoOutputFormatter(const ChoreoOutputSet* outputSet);
bool hasNext();
char next();
void reset();
protected:
const ChoreoOutputSet* m_outputSet;
const ChoreoOutput* m_currentOutput;
enum State {
START,
OUTPUTS_TAG,
OUTPUT_START,
NAME_TAG,
NAME_START,
NAME,
NAME_END,
NAME_PATH_SEPARATOR,
PATH_TAG,
PATH_START,
PATH,
PATH_END,
PATH_VAR_SEPARATOR,
VAR_TAG,
VAR_START,
VAR,
VAR_END,
OUTPUT_END,
NEXT_OUTPUT,
OUTPUTS_END,
END
};
};
#endif

View File

@@ -0,0 +1,73 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <string.h>
#include "ChoreoOutputSet.h"
ChoreoOutputSet::ChoreoOutputSet() {
m_first = NULL;
}
ChoreoOutputSet::~ChoreoOutputSet() {
ChoreoOutput* i = m_first;
ChoreoOutput* next = NULL;
while(i != NULL) {
next = i->getNext();
delete i;
i = next;
}
}
void ChoreoOutputSet::put(const char* name, const char* path, const char* variable) {
if (m_first == NULL) {
m_first = new ChoreoOutput(NULL, name, path, variable);
} else {
ChoreoOutput* last = NULL;
ChoreoOutput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
i->setPath(path);
i->setVariable(variable);
break;
}
last = i;
i = i->getNext();
}
if (i == NULL) {
new ChoreoOutput(last, name, path, variable);
}
}
}
const ChoreoOutput* ChoreoOutputSet::get(const char* name) const {
ChoreoOutput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
return i;
}
i = i->getNext();
}
return NULL;
}

View File

@@ -0,0 +1,43 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOOUTPUTSET_H_
#define CHOREOOUTPUTSET_H_
#include <stddef.h>
#include "TembooGlobal.h"
#include "ChoreoOutput.h"
class ChoreoOutputSet {
public:
ChoreoOutputSet();
~ChoreoOutputSet();
void put(const char* name, const char* path, const char* variable);
const ChoreoOutput* get(const char* name) const;
bool isEmpty() const {return m_first == NULL;}
const ChoreoOutput* getFirstOutput() const {return m_first;}
protected:
ChoreoOutput* m_first;
};
#endif

View File

@@ -0,0 +1,23 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "ChoreoPreset.h"

View File

@@ -0,0 +1,40 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOPRESET_H_
#define CHOREOPRESET_H_
#include <stddef.h>
#include "TembooGlobal.h"
class ChoreoPreset {
public:
ChoreoPreset() {m_name = NULL;}
ChoreoPreset(const char* name) {put(name);}
const char* getName() const {return m_name;}
void put(const char* name) {m_name = name;}
bool isEmpty() const {return m_name == NULL || *m_name == '\0';}
private:
const char* m_name;
};
#endif

View File

@@ -0,0 +1,84 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoPresetFormatter.h"
#include "ChoreoPreset.h"
static const char TAG_PRESET[] PROGMEM = "\"preset\":";
ChoreoPresetFormatter::ChoreoPresetFormatter(const ChoreoPreset* preset) {
m_preset = preset;
reset();
}
void ChoreoPresetFormatter::reset() {
m_nextChar = NULL;
if (m_preset == NULL || m_preset->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoPresetFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoPresetFormatter::next() {
char c = '\0';
switch(m_nextState) {
case START:
c = readStartTagChar(TAG_PRESET, PRESET_TAG);
break;
case PRESET_TAG:
c = readTagChar(NAME_START);
break;
case NAME_START:
c = '"';
m_nextChar = m_preset->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = NAME_END;
} else {
m_nextState = NAME;
}
break;
case NAME:
c = readValueChar(NAME_END);
break;
case NAME_END:
c = '"';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,50 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOPROFILEFORMATTER_H_
#define CHOREOPROFILEFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoPreset.h"
class ChoreoPresetFormatter : public BaseFormatter {
public:
ChoreoPresetFormatter(const ChoreoPreset* preset);
bool hasNext();
char next();
void reset();
protected:
const ChoreoPreset* m_preset;
enum State {
START,
PRESET_TAG,
NAME_START,
NAME,
NAME_END,
END
};
};
#endif

View File

@@ -0,0 +1,41 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include "ChoreoSensorInput.h"
ChoreoSensorInput::ChoreoSensorInput(ChoreoSensorInput* prev, const char* name, int value, const char* sensorConversion, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh, const char* calibration) {
if (prev != NULL) {
prev->m_next = this;
}
m_next = NULL;
m_name = name;
snprintf(m_value, TEMBOO_SENSOR_INPUT_ARRAY_SIZE, "%i",value);
m_sensorConversion = sensorConversion;
m_rawLow = rawLow;
m_rawHigh = rawHigh;
m_scaleLow = scaleLow;
m_scaleHigh = scaleHigh;
m_calibration = calibration;
}

View File

@@ -0,0 +1,63 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOSENSORINPUT_H_
#define CHOREOSENSORINPUT_H_
#include "TembooGlobal.h"
#define TEMBOO_SENSOR_INPUT_ARRAY_SIZE 8
class ChoreoSensorInput {
public:
ChoreoSensorInput(ChoreoSensorInput* prev, const char* name, int value, const char* sensorConversion, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh, const char* calibration);
const char* getName() const {return m_name;}
const char* getValue() const {return m_value;}
const char* getConversion() const {return m_sensorConversion;}
const char* getRawLow() const {return m_rawLow;}
const char* getRawHigh() const {return m_rawHigh;}
const char* getScaleLow() const {return m_scaleLow;}
const char* getScaleHigh() const {return m_scaleHigh;}
const char* getCalibration() const {return m_calibration;}
void setValue(int value) {snprintf(m_value, TEMBOO_SENSOR_INPUT_ARRAY_SIZE, "%i",value);}
void setConversion(const char* conversion) {m_sensorConversion = conversion;}
void setRawLow(const char* value) {m_rawLow = value;}
void setRawHigh(const char* value) {m_rawHigh = value;}
void setScaleLow(const char* value) {m_scaleLow = value;}
void setScaleHigh(const char* value) {m_scaleHigh = value;}
void setCalibration(const char* value) {m_calibration = value;}
ChoreoSensorInput* getNext() const {return m_next;}
private:
ChoreoSensorInput* m_next;
const char* m_name;
char m_value[TEMBOO_SENSOR_INPUT_ARRAY_SIZE];
const char* m_sensorConversion;
const char* m_rawLow;
const char* m_rawHigh;
const char* m_scaleLow;
const char* m_scaleHigh;
const char* m_calibration;
};
#endif //CHOREOSENSORINPUT_H_

View File

@@ -0,0 +1,316 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stddef.h>
#include <avr/pgmspace.h>
#include "ChoreoSensorInputFormatter.h"
#include "ChoreoSensorInputSet.h"
static const char TAG_INPUTS_START[] PROGMEM = "\"sensorInputs\":{";
static const char TAG_SENSOR_NAME_SEPERATOR[] PROGMEM = "\":{";
static const char TAG_VALUE_START[] PROGMEM = "\"value\":";
static const char TAG_CALIBRATION_START[] PROGMEM = ",\"calibration\":";
static const char TAG_RAW_LOW_START[] PROGMEM = ",\"rawLow\":";
static const char TAG_RAW_HIGH_START[] PROGMEM = ",\"rawHigh\":";
static const char TAG_SCALE_LOW_START[] PROGMEM = ",\"scaleLow\":";
static const char TAG_SCALE_HIGH_START[] PROGMEM = ",\"scaleHigh\":";
static const char TAG_CONVERSION_START[] PROGMEM = ",\"conversion\":";
ChoreoSensorInputFormatter::ChoreoSensorInputFormatter(const ChoreoSensorInputSet* inputSet) {
m_inputSet = inputSet;
reset();
}
void ChoreoSensorInputFormatter::reset() {
m_currentInput = NULL;
m_nextChar = NULL;
if (m_inputSet == NULL || m_inputSet->isEmpty()) {
m_nextState = END;
} else {
m_nextState = START;
}
}
bool ChoreoSensorInputFormatter::hasNext() {
return m_nextState != END;
}
char ChoreoSensorInputFormatter::next() {
char c;
switch(m_nextState) {
case START:
c = readStartTagChar(TAG_INPUTS_START, SENSOR_INPUT_TAG);
break;
case SENSOR_INPUT_TAG:
c = readTagChar(SENSOR_NAME_START);
if (m_nextState == SENSOR_NAME_START) {
m_currentInput= m_inputSet->getFirstInput();
}
break;
case SENSOR_NAME_START:
c = '"';
m_nextChar = m_currentInput->getName();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = SENSOR_NAME_END;
} else {
m_nextState = SENSOR_NAME;
}
break;
case SENSOR_NAME:
c = readValueChar(SENSOR_NAME_END);
break;
case SENSOR_NAME_END:
c = readStartTagChar(TAG_SENSOR_NAME_SEPERATOR, SENSOR_NAME_SEPERATOR);
break;
case SENSOR_NAME_SEPERATOR:
c = readTagChar(VALUE_TAG);
if (m_nextState == VALUE_TAG) {
m_nextChar= TAG_VALUE_START;
}
break;
case VALUE_TAG:
c = readTagChar(VALUE_START);
break;
case VALUE_START:
c = '"';
m_nextChar = m_currentInput->getValue();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = VALUE_END;
} else {
m_nextState = VALUE;
}
break;
case VALUE:
c = readValueChar(VALUE_END);
break;
case VALUE_END:
c = '"';
// decide how the data set should be set up, with just the value, with
// a conversion, or with the user defined scale
if (m_currentInput->getCalibration() != NULL) {
m_nextChar = TAG_CALIBRATION_START;
m_nextState = CALIBRATION_TAG;
} else {
if (m_currentInput->getConversion() != NULL) {
// use conversion
m_nextState = CONVERSION_TAG;
m_nextChar = TAG_CONVERSION_START;
} else if (m_currentInput->getScaleHigh() != m_currentInput->getScaleLow()) {
// use user defined scale
m_nextState = RAW_LOW_TAG;
m_nextChar = TAG_RAW_LOW_START;
} else {
// just the value is needed
m_nextState = DATA_SET_END;
}
}
break;
case CALIBRATION_TAG:
c = readTagChar(CALIBRATION_START);
break;
case CALIBRATION_START:
c = '"';
m_nextChar = m_currentInput->getCalibration();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = CALIBRATION_END;
} else {
m_nextState = CALIBRATION;
}
break;
case CALIBRATION:
c = readValueChar(CALIBRATION_END);
break;
case CALIBRATION_END:
c = '"';
if (m_currentInput->getConversion() != NULL) {
// use conversion
m_nextState = CONVERSION_TAG;
m_nextChar = TAG_CONVERSION_START;
} else if (m_currentInput->getScaleHigh() != m_currentInput->getScaleLow()) {
// use user defined scale
m_nextState = RAW_LOW_TAG;
m_nextChar = TAG_RAW_LOW_START;
} else {
// just the value is needed
m_nextState = DATA_SET_END;
}
break;
case CONVERSION_TAG:
c = readTagChar(CONVERSION_START);
break;
case CONVERSION_START:
c = '"';
m_nextChar = m_currentInput->getConversion();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = CONVERSION_END;
} else {
m_nextState = CONVERSION;
}
break;
case CONVERSION:
c = readValueChar(CONVERSION_END);
break;
case CONVERSION_END:
c = '"';
m_nextState = DATA_SET_END;
break;
case RAW_LOW_TAG:
c = readTagChar(RAW_LOW_START);
break;
case RAW_LOW_START:
c = '"';
m_nextChar = m_currentInput->getRawLow();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = RAW_LOW_END;
} else {
m_nextState = RAW_LOW;
}
break;
case RAW_LOW:
c = readValueChar(RAW_LOW_END);
break;
case RAW_LOW_END:
c = '"';
m_nextState = RAW_HIGH_TAG;
m_nextChar = TAG_RAW_HIGH_START;
break;
case RAW_HIGH_TAG:
c = readTagChar(RAW_HIGH_START);
break;
case RAW_HIGH_START:
c = '"';
m_nextChar = m_currentInput->getRawHigh();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = RAW_HIGH_END;
} else {
m_nextState = RAW_HIGH;
}
break;
case RAW_HIGH:
c = readValueChar(RAW_HIGH_END);
break;
case RAW_HIGH_END:
c = '"';
m_nextState = SCALE_LOW_TAG;
m_nextChar = TAG_SCALE_LOW_START;
break;
case SCALE_LOW_TAG:
c = readTagChar(SCALE_LOW_START);
break;
case SCALE_LOW_START:
c = '"';
m_nextChar = m_currentInput->getScaleLow();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = SCALE_LOW_END;
} else {
m_nextState = SCALE_LOW;
}
break;
case SCALE_LOW:
c = readValueChar(SCALE_LOW_END);
break;
case SCALE_LOW_END:
c = '"';
m_nextState = SCALE_HIGH_TAG;
m_nextChar = TAG_SCALE_HIGH_START;
break;
case SCALE_HIGH_TAG:
c = readTagChar(SCALE_HIGH_START);
break;
case SCALE_HIGH_START:
c = '"';
m_nextChar = m_currentInput->getScaleHigh();
if ((NULL == m_nextChar) || ('\0' == *m_nextChar)) {
m_nextState = SCALE_HIGH_END;
} else {
m_nextState = SCALE_HIGH;
}
break;
case SCALE_HIGH:
c = readValueChar(SCALE_HIGH_END);
break;
case SCALE_HIGH_END:
c = '"';
m_nextState = DATA_SET_END;
break;
case DATA_SET_END:
c = '}';
m_currentInput = m_currentInput->getNext();
if (NULL != m_currentInput) {
m_nextState = NEXT_INPUT;
} else {
m_nextState = INPUTS_END;
}
break;
case NEXT_INPUT:
c = ',';
m_nextChar = m_currentInput->getName();
m_nextState = SENSOR_NAME_START;
break;
case INPUTS_END:
c = '}';
m_nextState = END;
break;
case END:
default:
c = '\0';
}
return c;
}

View File

@@ -0,0 +1,86 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOSENSORINPUTFORMATTER_H_
#define CHOREOSENSORINPUTFORMATTER_H_
#include "TembooGlobal.h"
#include "BaseFormatter.h"
#include "ChoreoSensorInputSet.h"
class ChoreoSensorInputFormatter : public BaseFormatter {
public:
ChoreoSensorInputFormatter(const ChoreoSensorInputSet* inputSet);
bool hasNext();
char next();
void reset();
protected:
const ChoreoSensorInputSet* m_inputSet;
const ChoreoSensorInput* m_currentInput;
enum State {
// This is added to fix a compilation bug for the mkr1000
START = 256,
SENSOR_INPUT_TAG,
SENSOR_NAME_START,
SENSOR_NAME,
SENSOR_NAME_END,
SENSOR_NAME_SEPERATOR,
VALUE_TAG,
VALUE_START,
VALUE,
VALUE_END,
CALIBRATION_TAG,
CALIBRATION_START,
CALIBRATION,
CALIBRATION_END,
CONVERSION_TAG,
CONVERSION_START,
CONVERSION,
CONVERSION_END,
RAW_LOW_TAG,
RAW_LOW_START,
RAW_LOW,
RAW_LOW_END,
RAW_HIGH_TAG,
RAW_HIGH_START,
RAW_HIGH,
RAW_HIGH_END,
SCALE_LOW_TAG,
SCALE_LOW_START,
SCALE_LOW,
SCALE_LOW_END,
SCALE_HIGH_TAG,
SCALE_HIGH_START,
SCALE_HIGH,
SCALE_HIGH_END,
DATA_SET_END,
NEXT_INPUT,
INPUTS_END,
END
};
};
#endif //CHOREOSENSORINPUTFORMATTER_H_

View File

@@ -0,0 +1,86 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <string.h>
#include "ChoreoSensorInputSet.h"
ChoreoSensorInputSet::ChoreoSensorInputSet() {
m_first = NULL;
}
ChoreoSensorInputSet::~ChoreoSensorInputSet() {
ChoreoSensorInput* i = m_first;
ChoreoSensorInput* next = NULL;
while (i != NULL) {
next = i->getNext();
delete i;
i = next;
}
}
void ChoreoSensorInputSet::put(const char* name, int value, const char* sensorConversion, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh, const char* calibration) {
// Haven't set ANY inputs yet?
// Just create a new one.
if (m_first == NULL) {
m_first = new ChoreoSensorInput(NULL, name, value, sensorConversion, rawLow, rawHigh, scaleLow, scaleHigh, calibration);
} else {
// Some inputs already set.
// See if we already have this input.
ChoreoSensorInput* last = NULL;
ChoreoSensorInput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
// We already have an input with this name.
// Just update the value.
i->setValue(value);
i->setConversion(sensorConversion);
i->setRawLow(rawLow);
i->setRawHigh(rawHigh);
i->setScaleLow(scaleLow);
i->setScaleHigh(scaleHigh);
i->setCalibration(calibration);
break;
}
last = i;
i = i->getNext();
}
// We don't have an input with this name
// So we need to create a new one.
if (i == NULL) {
new ChoreoSensorInput(last, name, value, sensorConversion, rawLow, rawHigh, scaleLow, scaleHigh, calibration);
}
}
}
const char* ChoreoSensorInputSet::get(const char* name) const {
ChoreoSensorInput* i = m_first;
while(i != NULL) {
if (strcmp(i->getName(), name) == 0) {
return i->getValue();
}
i = i->getNext();
}
return NULL;
}

View File

@@ -0,0 +1,44 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef CHOREOSENSORINPUTSET_H_
#define CHOREOSENSORINPUTSET_H_
#include <stddef.h>
#include "TembooGlobal.h"
#include "ChoreoSensorInput.h"
class ChoreoSensorInputSet {
public:
ChoreoSensorInputSet();
~ChoreoSensorInputSet();
void put(const char* name, int value, const char* sensorConversion, const char* rawLow, const char* rawHigh, const char* scaleLow, const char* scaleHigh, const char* calibration);
const char* get(const char* name) const;
bool isEmpty() const {return m_first == NULL;}
const ChoreoSensorInput* getFirstInput() const {return m_first;}
protected:
ChoreoSensorInput* m_first;
};
#endif //CHOREOSENSORINPUTSET_H_

View File

@@ -0,0 +1,400 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "utility/CoapMessageLayer.h"
CoapMessageLayer::CoapMessageLayer(uint8_t* rxBuffer, uint16_t rxLen, TembooCoAPIPStack& ipStack) :
m_rxBuffer(rxBuffer),
m_rxLen(rxLen),
m_ipStack(ipStack),
m_state(STATE_CLOSED),
m_prevState(STATE_CLOSED),
m_retransmitCount(0),
m_lastResult(NO_ERROR),
m_msgID(0) {
}
CoapMessageLayer::Result CoapMessageLayer::rejectMsg(CoapMsg& msg) {
return rejectMsg(msg, m_ipStack.getRemoteAddress(), m_ipStack.getRemotePort());
}
CoapMessageLayer::Result CoapMessageLayer::rejectMsg(CoapMsg& msg, IPAddress addr, uint16_t port) {
msg.convertToReset();
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Sending RST");
if (m_ipStack.sendDatagram(addr, port, msg.getMsgBytes(), msg.getMsgLen())) {
m_lastResult = ERROR_SENDING_PACKET;
} else {
m_lastResult = NO_ERROR;
if (STATE_ACK_PENDING == m_state) {
// go back to previous state since the
// message recv'd wasn't what we're expecting
m_state = m_prevState;
}
}
return m_lastResult;
}
CoapMessageLayer::Result CoapMessageLayer::acceptMsg(CoapMsg& msg) {
return acceptMsg(msg, m_ipStack.getRemoteAddress(), m_ipStack.getRemotePort());
}
CoapMessageLayer::Result CoapMessageLayer::acceptMsg(CoapMsg& msg, IPAddress addr, uint16_t port) {
if (m_state != STATE_ACK_PENDING) {
m_lastResult = ERROR_IMPROPER_STATE;
} else {
msg.convertToEmptyAck();
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Sending ACK");
if (m_ipStack.sendDatagram(addr, port, msg.getMsgBytes(), msg.getMsgLen())) {
m_lastResult = ERROR_SENDING_PACKET;
} else {
m_state = STATE_CLOSED;
m_lastResult = NO_ERROR;
}
}
return m_lastResult;
}
CoapMessageLayer::Result CoapMessageLayer::reliableSend(CoapMsg& msg, IPAddress destAddr, uint16_t destPort) {
if (m_state != STATE_CLOSED) {
m_lastResult = ERROR_IMPROPER_STATE;
return m_lastResult;
}
m_rxByteCount = 0;
m_msgID = msg.getId();
m_msgBytes = msg.getMsgBytes();
m_msgLen = msg.getMsgLen();
m_destAddr = destAddr;
m_destPort = destPort;
msg.setType(CoapMsg::COAP_CONFIRMABLE);
m_retransmitCount = 0;
m_retransmitTimeoutMillis = random(ACK_TIMEOUT, MAX_ACK_TIMEOUT);
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Sending message");
if (m_ipStack.sendDatagram(m_destAddr, m_destPort, msg.getMsgBytes(), msg.getMsgLen())) {
m_state = STATE_CLOSED;
m_lastResult = ERROR_SENDING_PACKET;
} else {
m_txSpanTimer.start(MAX_TRANSMIT_WAIT);
m_retransmitTimer.start(m_retransmitTimeoutMillis);
m_state = STATE_RELIABLE_TX;
m_lastResult = NO_ERROR;
}
return m_lastResult;
}
CoapMessageLayer::Result CoapMessageLayer::cancelReliableSend() {
if (m_state != STATE_RELIABLE_TX) {
m_lastResult = ERROR_IMPROPER_STATE;
return m_lastResult;
}
m_state = STATE_CLOSED;
m_lastResult = NO_ERROR;
return m_lastResult;
}
CoapMessageLayer::Result CoapMessageLayer::loop() {
m_lastResult = NO_ERROR;
switch(m_state) {
case STATE_RELIABLE_TX:
// We have sent a CON request.
// We're expecting an ACK or a response.
// See if it's time to give up all hope of getting an ACK.
if (m_txSpanTimer.expired()) {
m_lastResult = ERROR_TX_SPAN_TIME_EXCEEDED;
m_state = STATE_CLOSED;
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("ACK not received within timeout");
break;
}
// See if any messages have come in.
if (m_ipStack.recvDatagram(m_rxBuffer, m_rxLen, m_rxByteCount)) {
m_lastResult = ERROR_RECEIVING_PACKET;
m_state = STATE_CLOSED;
break;
}
// We've received something. See if it's relevant.
if (m_rxByteCount > 0) {
CoapMsg msg(m_rxBuffer, m_rxLen, m_rxByteCount);
// Make sure the message is valid
if (!msg.isValid()) {
rejectMsg(msg);
// We're only interested in messages coming from the host
// we sent the original request to.
} else if (m_ipStack.getRemoteAddress() == m_destAddr) {
switch (msg.getType()) {
case CoapMsg::COAP_ACK:
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("ACK Received");
// Is it ACK'ing the last request we sent?
if (msg.getId() == m_msgID) {
m_state = STATE_CLOSED;
m_lastResult = ACK_RECEIVED;
} else {
// if not, just ignore it.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("MID did not match");
}
break;
case CoapMsg::COAP_RESET:
// Is it rejecting the last request we sent?
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("RST Received");
if (msg.getId() == m_msgID) {
m_state = STATE_CLOSED;
m_lastResult = RESET_RECEIVED;
} else {
// if not, just ignore it.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("MID did not match");
}
break;
case CoapMsg::COAP_CONFIRMABLE:
// It COULD be the response to our request, or
// just some unexpected message.
// We'll let the upper layers decide.
m_prevState = m_state;
m_state = STATE_ACK_PENDING;
m_lastResult = CON_RECEIVED;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("CON Received");
break;
case CoapMsg::COAP_NON_CONFIRMABLE:
// It COULD be the response to our request, or
// just some unexpected message.
// We'll let the upper layers decide.
// That's what Kovatsch et al. show in their FSM.
m_state = STATE_CLOSED;
m_lastResult = NON_RECEIVED;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("NON Received");
break;
}
if (msg.getPayloadLen() > 0) {
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Payload data:");
uint8_t *payload = msg.getPayload();
uint16_t len = msg.getPayloadLen();
for (uint16_t i = 0; i < len; i++) {
TEMBOO_TRACE((char)payload[i]);
}
TEMBOO_TRACELN();
}
} else if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
// It's not from the host we sent the request to, but
// the sending host expects a reply, so explicitly reject it.
rejectMsg(msg);
}
break;
}
// Nothing was received. See if it's time to retransmit.
if (m_retransmitTimer.expired()) {
if (m_retransmitCount >= MAX_RETRANSMIT) {
// We've retried enough. Give up.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("Maximum retransmit reached");
m_lastResult = ERROR_RETRANSMIT_COUNT_EXCEEDED;
m_state = STATE_CLOSED;
} else {
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Retransmit message");
m_retransmitCount++;
m_retransmitTimeoutMillis *= 2;
if (m_ipStack.sendDatagram(m_destAddr, m_destPort, m_msgBytes, m_msgLen)) {
m_state = STATE_CLOSED;
m_lastResult = ERROR_SENDING_PACKET;
} else {
m_retransmitTimer.start(m_retransmitTimeoutMillis);
m_state = STATE_RELIABLE_TX;
m_lastResult = NO_ERROR;
}
}
}
break;
case STATE_ACK_PENDING:
// Nothing to do here but wait for
// the higher layer to accept or reject.
break;
case STATE_WAITING_FOR_CON:
// See if any messages have come in.
if (m_ipStack.recvDatagram(m_rxBuffer, m_rxLen, m_rxByteCount)) {
m_lastResult = ERROR_RECEIVING_PACKET;
m_state = STATE_CLOSED;
break;
}
// We've received something. See if it's relevant.
if (m_rxByteCount > 0) {
CoapMsg msg(m_rxBuffer, m_rxLen, m_rxByteCount);
// Make sure the message is valid
if (!msg.isValid()) {
rejectMsg(msg);
// We're only interested in messages coming from the host
// we sent the original request to.
} else if (m_ipStack.getRemoteAddress() == m_destAddr) {
switch (msg.getType()) {
case CoapMsg::COAP_ACK:
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("ACK Received");
// Is it ACK'ing the last request we sent?
if (msg.getId() == m_msgID) {
m_state = STATE_CLOSED;
m_lastResult = ACK_RECEIVED;
} else {
// if not, just ignore it.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("MID did not match");
}
break;
case CoapMsg::COAP_RESET:
// Is it rejecting the last request we sent?
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("RST Received");
if (msg.getId() == m_msgID) {
m_state = STATE_CLOSED;
m_lastResult = RESET_RECEIVED;
} else {
// if not, just ignore it.
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("MID did not match");
}
break;
case CoapMsg::COAP_CONFIRMABLE:
// It COULD be the response to our request, or
// just some unexpected message.
// We'll let the upper layers decide.
m_prevState = m_state;
m_state = STATE_ACK_PENDING;
m_lastResult = CON_RECEIVED;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("CON Received");
break;
case CoapMsg::COAP_NON_CONFIRMABLE:
// It COULD be the response to our request, or
// just some unexpected message.
// We'll let the upper layers decide.
// That's what Kovatsch et al. show in their FSM.
m_state = STATE_CLOSED;
m_lastResult = NON_RECEIVED;
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("NON Received");
break;
}
if (msg.getPayloadLen() > 0) {
TEMBOO_TRACE("DBG: ");
TEMBOO_TRACELN("Payload data:");
uint8_t *payload = msg.getPayload();
uint16_t len = msg.getPayloadLen();
for (uint16_t i = 0; i < len; i++) {
TEMBOO_TRACE((char)payload[i]);
}
TEMBOO_TRACELN();
}
} else if (msg.getType() == CoapMsg::COAP_CONFIRMABLE) {
// It's not from the host we sent the request to, but
// the sending host expects a reply, so explicitly reject it.
rejectMsg(msg);
}
break;
}
break;
case STATE_CLOSED:
// We haven't sent anything that we're expecting an answer to.
// We haven't received anything that requires an answer.
// Just pump the receiver.
if (m_ipStack.recvDatagram(m_rxBuffer, m_rxLen, m_rxByteCount)) {
m_lastResult = ERROR_RECEIVING_PACKET;
break;
}
// If we received something, handle it.
if (m_rxByteCount > 0) {
CoapMsg msg(m_rxBuffer, m_rxLen, m_rxByteCount);
switch (msg.getType()) {
case CoapMsg::COAP_ACK:
// Not expecting any ACKS, ignore it.
break;
case CoapMsg::COAP_RESET:
// Haven't sent any CONs and we don't send NONs, ignore it.
break;
case CoapMsg::COAP_CONFIRMABLE:
// Let the higher layers deal with this.
m_prevState = m_state;
m_state = STATE_ACK_PENDING;
m_lastResult = CON_RECEIVED;
break;
case CoapMsg::COAP_NON_CONFIRMABLE:
// Let the higher layers deal with this.
m_lastResult = NON_RECEIVED;
break;
}
}
break;
}
return m_lastResult;
}

View File

@@ -0,0 +1,128 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef COAPMESSAGELAYER_H_
#define COAPMESSAGELAYER_H_
#include "TembooCoAPIPStack.h"
#include "TembooTimer.h"
#include "CoapMsg.h"
/**
* CoapMessageLayer is the lowest layer of the CoAP stack. It is responsible for
* transmitting and receiving messages. Specifically, this implementation is
* responsible for handling confirmable (CON) messages and their ACKs.
* It is intended to implement the Message Layer FSM described by Kovatsch et al. in
* https://tools.ietf.org/html/draft-kovatsch-lwig-coap-01
*
* It can send reliable (confirmable or CON) messages and will maintain the necessary
* state information to wait for an acknowledgement. It will handle any necessary
* retransmissions and timeouts until a CON message has been ACK'd (or rejected.)
*
* It can not send unreliable (non-confirmable or NON) messages as currently designed
* because our application does not use NON messages.
*
* It can receive CON and NON messages and will send ACKs or RESETs as required.
*
* Any non-rejected CON or NON messages received are passed up to the Request/Response
* layer (CoapRRLayer) for processing.
*
* Be sure to note the difference between ACK's and Responses. This layer handles
* ACK's, it does not handle Responses. (Responses are handled by the CoapRRLayer class.)
*/
class CoapMessageLayer {
public:
enum State {
STATE_CLOSED,
STATE_RELIABLE_TX,
STATE_ACK_PENDING,
STATE_WAITING_FOR_CON
};
enum Result {
NO_ERROR = 0,
CON_RECEIVED,
NON_RECEIVED,
ACK_RECEIVED,
RESET_RECEIVED,
ERROR_IMPROPER_STATE,
ERROR_NULL_MESSAGE,
ERROR_SENDING_PACKET,
ERROR_RECEIVING_PACKET,
ERROR_RETRANSMIT_COUNT_EXCEEDED,
ERROR_TX_SPAN_TIME_EXCEEDED
};
static const uint32_t ACK_TIMEOUT = 2000;
// MAX_ACK_TIMEOUT = ACK_TIMEOUT * ACK_RANDOM_FACTOR
// RFC7252 suggests ACK_RANDOM_FACTOR = 1.5
static const uint32_t MAX_ACK_TIMEOUT = 3000;
static const uint8_t MAX_RETRANSMIT = 4;
// MAX_TRANSMIT_SPAN = ACK_TIMEOUT * ((2^MAX_RETRANSMIT) - 1) * ACK_RANDOM_FACTOR
static const uint32_t MAX_TRANSMIT_SPAN = 45000;
// MAX_TRANSMIT_WAIT = ACK_TIMEOUT * (2^(MAX_RETRANSMIT + 1) - 1) * ACK_RANDOM_FACTOR
static const uint32_t MAX_TRANSMIT_WAIT = 93000;
CoapMessageLayer(uint8_t* rxBuffer, uint16_t rxLen, TembooCoAPIPStack& ipStack);
Result reliableSend(CoapMsg& msg, IPAddress destAddr, uint16_t destPort);
Result cancelReliableSend();
Result acceptMsg(CoapMsg& msg);
Result acceptMsg(CoapMsg& msg, IPAddress addr, uint16_t port);
Result rejectMsg(CoapMsg& msg);
Result rejectMsg(CoapMsg& msg, IPAddress addr, uint16_t port);
Result loop();
Result getLastResult() {return m_lastResult;}
uint16_t getRXByteCount() {return m_rxByteCount;}
void setState(State state) {m_state = state;}
private:
uint8_t* m_rxBuffer;
uint16_t m_rxLen;
TembooCoAPIPStack& m_ipStack;
State m_state;
State m_prevState;
int m_retransmitCount;
Result m_lastResult;
IPAddress m_destAddr;
uint16_t m_destPort;
uint16_t m_msgID;
uint8_t* m_msgBytes;
uint16_t m_msgLen;
int32_t m_rxByteCount;
TembooTimer m_txSpanTimer;
TembooTimer m_retransmitTimer;
uint32_t m_retransmitTimeoutMillis;
};
#endif

View File

@@ -0,0 +1,714 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <stdint.h>
#include <string.h>
#include "CoapMsg.h"
#include "TembooGlobal.h"
const uint8_t COAP_VERSION = 1;
const uint8_t HEADER_LENGTH = 4;
CoapMsg::CoapMsg(uint8_t* buffer, uint16_t bufferLen) : m_buffer(buffer), m_bufferLen(bufferLen) {
// Starting a new message. All messages have a header.
m_msgLen = HEADER_LENGTH;
memset(m_buffer, 0, m_bufferLen);
m_buffer[0] = COAP_VERSION << 6;
m_buildState = BUILD_BEGIN;
m_lastOptionCode = 0;
}
CoapMsg::CoapMsg(uint8_t* buffer, uint16_t bufferLen, uint16_t packetLen) : m_buffer(buffer), m_bufferLen(bufferLen) {
m_msgLen = packetLen;
m_buildState = BUILD_HAVE_PAYLOAD;
m_lastOptionCode = 0;
}
void CoapMsg::setType(CoapMsg::Type msgType) {
m_buffer[0] &= 0xCF; //11001111
m_buffer[0] |= msgType << 4;
}
CoapMsg::Type CoapMsg::getType() {
return (CoapMsg::Type)((m_buffer[0] & 0x30) >> 4);
}
void CoapMsg::setId(uint16_t msgId) {
m_buffer[2] = msgId >> 8;
m_buffer[3] = msgId & 0x00FF;
}
uint16_t CoapMsg::getId() {
return (m_buffer[2] << 8) + m_buffer[3];
}
CoapMsg::Result CoapMsg::setToken(const uint8_t* token, uint8_t tokenLen){
if (m_buildState >= BUILD_HAVE_TOKEN) {
return COAP_RESULT_BUILD_ORDER;
}
if (tokenLen > 8){
return COAP_RESULT_TOKEN_LENGTH;
}
if ((tokenLen > 0) && (NULL == token)) {
return COAP_RESULT_TOKEN_NULL;
}
if ((m_msgLen + tokenLen) > m_bufferLen) {
return COAP_RESULT_BUFFER_OVERRUN;
}
memcpy(m_buffer + m_msgLen, token, tokenLen);
m_msgLen += tokenLen;
m_buffer[0] &= 0xF0;
m_buffer[0] |= tokenLen;
m_buildState = BUILD_HAVE_TOKEN;
return COAP_RESULT_SUCCESS;
}
uint8_t* CoapMsg::getToken() {
if (getTokenLen() > 0) {
return m_buffer + HEADER_LENGTH;
}
return NULL;
}
uint8_t CoapMsg::getTokenLen() {
return m_buffer[0] & 0x0F;
}
void CoapMsg::setCode(CoapMsg::Code code) {
m_buffer[1] = (uint8_t)code;
}
CoapMsg::Code CoapMsg::getCode() {
return (CoapMsg::Code)(m_buffer[1]);
}
uint16_t CoapMsg::getHTTPStatus() {
uint8_t code = getCode();
// 3 MSbs are the most significant digit (0 - 6)
// 5 LSbs are the two least significant digits (0 - 31)
return (code >> 5) * 100 + (code & 0x1F);
}
CoapMsg::Result CoapMsg::setPayload(const uint8_t* payload, uint16_t payloadLen) {
if (m_buildState >= BUILD_HAVE_PAYLOAD) {
return COAP_RESULT_BUILD_ORDER;
}
if (payloadLen > 0 && payload == NULL) {
return COAP_RESULT_PAYLOAD_NULL;
}
if ((m_msgLen + payloadLen + 1) > m_bufferLen) {
return COAP_RESULT_BUFFER_OVERRUN;
}
// Add the special payload marker flag.
m_buffer[m_msgLen++] = 0xFF;
memcpy(m_buffer + m_msgLen, payload, payloadLen);
m_msgLen += payloadLen;
m_buildState = BUILD_HAVE_PAYLOAD;
return COAP_RESULT_SUCCESS;
}
uint8_t* CoapMsg::getPayload() {
if (m_buildState < BUILD_HAVE_PAYLOAD) {
return NULL;
}
uint8_t* payload = NULL;
uint16_t optionDelta = 0;
uint16_t optionLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
while (i < m_buffer + m_msgLen) {
i = decodeOption(i, &optionDelta, &optionLen);
if ((optionDelta == 15) || (optionLen == 15)) {
//Technically, if either optionDelta or optionLen is 15, then
//both MUST be 15, else it's a malformed message. However,
//we're going to be a little loose with the spec here.
payload = i;
break;
}
}
return payload;
}
uint16_t CoapMsg::getPayloadLen() {
if (m_buildState < BUILD_HAVE_PAYLOAD) {
return 0;
}
uint8_t* payload = getPayload();
if (NULL == payload) {
return 0;
}
return m_buffer + m_msgLen - payload;
}
/*
0 1 2 3 4 5 6 7
+---------------+---------------+
| | |
| Option Delta | Option Length | 1 byte
| | |
+---------------+---------------+
\ \ 0 bytes if OptionDelta <= 12
/ Option Delta / 1 byte if OptionDelte == 13
\ (extended) \ 2 bytes if OptionDelta == 14
+-------------------------------+
\ \ 0 bytes if OptionLength <= 12
/ Option Length / 1 byte if OptionLength == 13
\ (extended) \ 2 bytes if OptionLength == 14
+-------------------------------+
\ \
/ /
\ \
/ Option Value / 0 or more bytes (i.e. OptionLength bytes)
\ \
/ /
\ \
+-------------------------------+
*/
CoapMsg::Result CoapMsg::addOption(CoapMsg::Option optionCode, const uint8_t* optionValue, uint16_t optionLen) {
if (m_buildState > BUILD_HAVE_OPTIONS) {
return COAP_RESULT_BUILD_ORDER;
}
if (m_lastOptionCode > optionCode) {
return COAP_RESULT_BUILD_ORDER;
}
if (optionLen > 0 && NULL == optionValue) {
return COAP_RESULT_OPTION_NULL;
}
CoapMsg::Result rc = validateOption(optionCode, optionValue, optionLen);
if (COAP_RESULT_SUCCESS != rc) {
return rc;
}
// See if there's enough room in the buffer to add this option.
uint16_t byteCount = 1;
uint16_t optionDelta = optionCode - m_lastOptionCode;
if (optionDelta >= 13) {
byteCount++;
}
if (optionDelta >= 269) {
byteCount++;
}
if (optionLen >= 13) {
byteCount++;
}
if (optionLen >= 269) {
byteCount++;
}
byteCount += optionLen;
if (m_msgLen + byteCount > m_bufferLen) {
return COAP_RESULT_BUFFER_OVERRUN;
}
// If we get this far, there's enough room.
byteCount = 0;
if (optionDelta >= 269) {
m_buffer[m_msgLen] = 14 << 4;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionDelta - 269) >> 8;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionDelta - 269) & 0x00FF;
} else if (optionDelta >= 13) {
m_buffer[m_msgLen] = 13 << 4;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionDelta - 13);
} else {
m_buffer[m_msgLen] = optionDelta << 4;
}
if (optionLen >= 269) {
m_buffer[m_msgLen] = (m_buffer[m_msgLen] & 0XF0) | 14;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionLen - 269) >> 8;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionLen - 269) & 0xFF;
} else if (optionLen >= 13) {
m_buffer[m_msgLen] = (m_buffer[m_msgLen] & 0xF0) | 13;
byteCount++;
m_buffer[m_msgLen + byteCount] = (optionLen - 13);
} else {
m_buffer[m_msgLen] = (m_buffer[m_msgLen] & 0xF0) | optionLen;
byteCount++;
}
m_msgLen += byteCount;
if (optionLen > 0) {
memcpy(m_buffer + m_msgLen, optionValue, optionLen);
}
m_msgLen += optionLen;
m_lastOptionCode = optionCode;
return COAP_RESULT_SUCCESS;
}
CoapMsg::Result CoapMsg::validateOption(CoapMsg::Option optionCode, const uint8_t* optionValue, uint16_t optionLen) {
CoapMsg::Result rc = COAP_RESULT_SUCCESS;
switch(optionCode) {
case COAP_OPTION_IF_MATCH:
rc = validateOptionValue(0, 8, optionValue, optionLen);
break;
case COAP_OPTION_URI_HOST:
rc = validateOptionValue(1, 255, optionValue, optionLen);
break;
case COAP_OPTION_ETAG:
rc = validateOptionValue(1, 8, optionValue, optionLen);
break;
case COAP_OPTION_IF_NONE_MATCH:
rc = validateOptionValue(0, 0, optionValue, optionLen);
break;
//TODO: case COAP_OPTION_OBSERVE:
// rc = validateOptionValue(0, 0, optionValue, optionLen);
// break;
case COAP_OPTION_URI_PORT:
rc = validateOptionValue(0, 2, optionValue, optionLen);
break;
case COAP_OPTION_LOCATION_PATH:
rc = validateOptionValue(0, 255, optionValue, optionLen);
break;
case COAP_OPTION_URI_PATH:
rc = validateOptionValue(0, 255, optionValue, optionLen);
break;
case COAP_OPTION_CONTENT_FORMAT:
rc = validateOptionValue(0, 2, optionValue, optionLen);
break;
case COAP_OPTION_MAX_AGE:
rc = validateOptionValue(0, 4, optionValue, optionLen);
break;
case COAP_OPTION_URI_QUERY:
rc = validateOptionValue(0, 255, optionValue, optionLen);
break;
case COAP_OPTION_ACCEPT:
rc = validateOptionValue(0, 2, optionValue, optionLen);
break;
case COAP_OPTION_LOCATION_QUERY:
rc = validateOptionValue(0, 255, optionValue, optionLen);
break;
case COAP_OPTION_BLOCK2:
rc = validateOptionValue(0, 3, optionValue, optionLen);
break;
case COAP_OPTION_BLOCK1:
rc = validateOptionValue(0, 3, optionValue, optionLen);
break;
case COAP_OPTION_SIZE2:
rc = validateOptionValue(0, 4, optionValue, optionLen);
break;
case COAP_OPTION_PROXY_URI:
rc = validateOptionValue(0, 1034, optionValue, optionLen);
break;
case COAP_OPTION_PROXY_SCHEME:
rc = validateOptionValue(1, 255, optionValue, optionLen);
break;
case COAP_OPTION_SIZE1:
rc = validateOptionValue(0, 4, optionValue, optionLen);
break;
default:
rc = COAP_RESULT_OPTION_UNKNOWN;
}
return rc;
}
CoapMsg::Result CoapMsg::validateOptionValue(uint16_t minLen, uint16_t maxLen, const uint8_t* optionValue, uint16_t optionLen) {
if (optionLen < minLen || optionLen > maxLen) {
return COAP_RESULT_OPTION_LENGTH;
}
if (optionLen > 0 && NULL == optionValue) {
return COAP_RESULT_OPTION_NULL;
}
//TODO: Maybe. Validate value format (uint vs string vs opaque)
return COAP_RESULT_SUCCESS;
}
uint8_t* CoapMsg::getMsgBytes() {
return m_buffer;
}
uint16_t CoapMsg::getMsgLen() {
return m_msgLen;
}
uint8_t* CoapMsg::decodeOption(uint8_t* buffer, uint16_t* optionDelta, uint16_t* optionLen) {
*optionDelta = *buffer >> 4;
*optionLen = *buffer & 0x0F;
buffer++;
if (13 == *optionDelta) {
*optionDelta = *buffer++ + 13;
} else if (14 == *optionDelta) {
*optionDelta = *buffer++ << 8;
*optionDelta += *buffer++;
*optionDelta += 269;
}
if (13 == *optionLen) {
*optionLen = *buffer++ + 13;
} else if (14 == *optionLen) {
*optionLen = *buffer++ << 8;
*optionLen += *buffer++;
*optionLen += 269;
}
if (*optionLen != 15) {
buffer += *optionLen;
}
return buffer;
}
uint16_t CoapMsg::getOptionCount(CoapMsg::Option optionCode) {
if (m_buildState < BUILD_HAVE_OPTIONS) {
return 0;
}
uint16_t count = 0;
uint16_t lastOption = 0;
uint16_t optionDelta = 0;
uint16_t optionLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
while (i < (m_buffer + m_msgLen) && *i != 0xFF) {
i = decodeOption(i, &optionDelta, &optionLen);
lastOption += optionDelta;
if (lastOption == optionCode) {
count++;
}
}
return count;
}
uint16_t CoapMsg::getOptionLen(CoapMsg::Option optionCode, uint16_t index) {
uint16_t count = 0;
uint16_t lastOption = 0;
uint16_t optionDelta = 0;
uint16_t optionLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
while (i < (m_buffer + m_msgLen) && *i != 0xFF) {
i = decodeOption(i, &optionDelta, &optionLen);
lastOption += optionDelta;
if (lastOption == optionCode) {
if (count == index) {
break;
}
count++;
}
optionLen = 0;
}
return optionLen;
}
uint8_t* CoapMsg::getOptionValue(CoapMsg::Option optionCode, uint16_t index) {
uint16_t count = 0;
uint16_t lastOption = 0;
uint16_t optionDelta = 0;
uint16_t optionLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
while (i < (m_buffer + m_msgLen) && *i != 0xFF) {
i = decodeOption(i, &optionDelta, &optionLen);
lastOption += optionDelta;
if (lastOption == optionCode) {
if (count == index) {
return i - optionLen;
}
count++;
}
}
return NULL;
}
CoapMsg::Result CoapMsg::getOption(CoapMsg::Option optionCode, uint16_t index, uint8_t*& optionValue, uint16_t& optionLen) {
uint16_t count = 0;
uint16_t lastOption = 0;
uint16_t optionDelta = 0;
uint16_t optLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
while (i < (m_buffer + m_msgLen) && *i != 0xFF) {
i = decodeOption(i, &optionDelta, &optLen);
lastOption += optionDelta;
if (lastOption == optionCode) {
if (count == index) {
optionValue = i - optLen;
optionLen = optLen;
return COAP_RESULT_SUCCESS;
}
count++;
}
}
return COAP_RESULT_OPTION_NOT_FOUND;
}
uint16_t CoapMsg::getBlock1Size() {
return getBlockSize(COAP_OPTION_BLOCK1);
}
uint16_t CoapMsg::getBlock2Size() {
return getBlockSize(COAP_OPTION_BLOCK2);
}
uint16_t CoapMsg::getBlockSize(CoapMsg::Option optionCode) {
if (m_buildState < BUILD_HAVE_OPTIONS) {
return 0;
}
uint16_t optionLen;
uint8_t* optionValue;
if (getOption(optionCode, 0, optionValue, optionLen) != COAP_RESULT_SUCCESS) {
return 0;
}
uint16_t blockLen = 16 << (optionValue[optionLen - 1] & 0x07);
if (blockLen > 1024) {
return 0;
}
return blockLen;
}
uint32_t CoapMsg::getBlock1Num() {
return getBlockNum(COAP_OPTION_BLOCK1);
}
uint32_t CoapMsg::getBlock2Num() {
return getBlockNum(COAP_OPTION_BLOCK2);
}
uint32_t CoapMsg::getBlockNum(CoapMsg::Option optionCode) {
if (m_buildState < BUILD_HAVE_OPTIONS) {
return 0;
}
uint16_t optionLen;
uint8_t* optionValue;
if (getOption(optionCode, 0, optionValue, optionLen) != COAP_RESULT_SUCCESS) {
return 0;
}
int32_t blockNum = 0;
for (;optionLen > 0; optionLen--) {
blockNum <<= 8;
blockNum += *optionValue;
optionValue++;
}
blockNum >>= 4;
return blockNum;
}
bool CoapMsg::getBlock1More() {
return getBlockMore(COAP_OPTION_BLOCK1);
}
bool CoapMsg::getBlock2More() {
return getBlockMore(COAP_OPTION_BLOCK2);
}
bool CoapMsg::getBlockMore(CoapMsg::Option optionCode) {
if (m_buildState < BUILD_HAVE_OPTIONS) {
return 0;
}
uint16_t optionLen;
uint8_t* optionValue;
if (getOption(optionCode, 0, optionValue, optionLen) != COAP_RESULT_SUCCESS) {
return 0;
}
if (optionLen == 0) {
return false;
}
return (optionValue[optionLen-1] & 0x08) > 0;
}
/**
* Convert this (existing received) message into a reset message.
*/
void CoapMsg::convertToReset() {
setType(COAP_RESET);
setCode(COAP_EMPTY);
// Set token length to 0
m_buffer[0] &= 0xF0;
m_msgLen = HEADER_LENGTH;
}
void CoapMsg::convertToEmptyAck() {
setType(COAP_ACK);
setCode(COAP_EMPTY);
m_buffer[0] &= 0xF0;
m_msgLen = HEADER_LENGTH;
}
bool CoapMsg::isValid() {
// check packet size
if (m_msgLen < 4) {
TEMBOO_TRACE("Packet must be a minimum of 4 bytes\n");
return false;
}
// check token length
if (getTokenLen() < 0 || getTokenLen() > 8) {
TEMBOO_TRACE("Invalid token length\n");
return false;
}
int16_t responseClass = m_buffer[1] >> 5;
// check HTTP Code is between 000-599
if (responseClass < 0 || responseClass > 5) {
TEMBOO_TRACE("Invalid code\n");
return false;
}
if (HEADER_LENGTH + getTokenLen() == m_msgLen) {
// nothing else in the packet
return true;
}
uint16_t count =0;
uint16_t lastOption = 0;
uint16_t optionDelta = 0;
uint16_t optionLen = 0;
uint8_t* i = m_buffer + HEADER_LENGTH + getTokenLen();
// validate options
while (i < (m_buffer + m_msgLen) && *i != 0xFF) {
i = decodeOption(i, &optionDelta, &optionLen);
lastOption += optionDelta;
if (validateOption((Option)lastOption, i - optionLen, optionLen)) {
TEMBOO_TRACE("Invalid option\n");
return false;
}
}
// if payload marker exists, make sure there is payload data
if (*i == 0xFF && (i - m_buffer) > m_msgLen) {
return false;
}
return true;
}

View File

@@ -0,0 +1,192 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef COAPMSG_H_
#define COAPMSG_H_
/*
Byte 0 Byte 1 Byte 2 Byte 3
MSB LSB MSB LSB MSB LSB MSB LSB
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|Ver| T | TKL | | Code | | MsgID MSB | | MsgID LSB |
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
#define RESPONSE_CODE(class, detail) ((class << 5) + detail)
class CoapMsg {
public:
enum Type {
COAP_CONFIRMABLE = 0,
COAP_NON_CONFIRMABLE = 1,
COAP_ACK = 2,
COAP_RESET = 3
};
enum Code {
//REQUEST CODES
COAP_EMPTY = 0,
COAP_GET = 1,
COAP_POST = 2,
COAP_PUT = 3,
COAP_DELETE = 4,
//RESPONSE CODES
COAP_CREATED = RESPONSE_CODE(2,1),
COAP_DELETED = RESPONSE_CODE(2,2),
COAP_VALID = RESPONSE_CODE(2,3),
COAP_CHANGED = RESPONSE_CODE(2,4),
COAP_CONTENT = RESPONSE_CODE(2,5),
COAP_CONTINUE = RESPONSE_CODE(2,31),
COAP_BAD_REQUEST = RESPONSE_CODE(4,0),
COAP_UNAUTHORIZED = RESPONSE_CODE(4,1),
COAP_BAD_OPTION = RESPONSE_CODE(4,2),
COAP_FORBIDDEN = RESPONSE_CODE(4,3),
COAP_NOT_FOUND = RESPONSE_CODE(4,4),
COAP_METHOD_NOT_ALLOWED = RESPONSE_CODE(4,5),
COAP_NOT_ACCEPTABLE = RESPONSE_CODE(4,6),
COAP_REQUEST_ENTITY_INCOMPLETE = RESPONSE_CODE(4,8),
COAP_PRECONDITION_FAILED = RESPONSE_CODE(4,12),
COAP_REQUEST_ENTITY_TOO_LARGE = RESPONSE_CODE(4,13),
COAP_UNSUPPORTED_CONTENT_FORMAT = RESPONSE_CODE(4,15),
COAP_INTERNAL_SERVER_ERROR = RESPONSE_CODE(5,0),
COAP_NOT_IMPLEMENTED = RESPONSE_CODE(5,1),
COAP_BAD_GATEWAY = RESPONSE_CODE(5,2),
COAP_SERVICE_UNAVAILABLE = RESPONSE_CODE(5,3),
COAP_GATEWAY_TIMEOUT = RESPONSE_CODE(5,4),
COAP_PROXYING_NOT_SUPPORTED = RESPONSE_CODE(5,5)
};
enum Option {
COAP_OPTION_IF_MATCH = 1,
COAP_OPTION_URI_HOST = 3,
COAP_OPTION_ETAG = 4,
COAP_OPTION_IF_NONE_MATCH = 5,
//TODO: COAP_OPTION_OBSERVE = 6,
COAP_OPTION_URI_PORT = 7,
COAP_OPTION_LOCATION_PATH = 8,
COAP_OPTION_URI_PATH = 11,
COAP_OPTION_CONTENT_FORMAT = 12,
COAP_OPTION_MAX_AGE = 14,
COAP_OPTION_URI_QUERY = 15,
COAP_OPTION_ACCEPT = 17,
COAP_OPTION_LOCATION_QUERY = 20,
COAP_OPTION_BLOCK2 = 23,
COAP_OPTION_BLOCK1 = 27,
COAP_OPTION_SIZE2 = 28,
COAP_OPTION_PROXY_URI = 35,
COAP_OPTION_PROXY_SCHEME = 39,
COAP_OPTION_SIZE1 = 60
};
enum Result {
COAP_RESULT_SUCCESS = 0, // No error.
COAP_RESULT_TOKEN_NULL, // Token length > 0 but NULL pointer given for token value.
COAP_RESULT_TOKEN_LENGTH, // Illegal token length value (> 8).
COAP_RESULT_PAYLOAD_NULL, // Payload length > 0 but NULL pointer given for payload value.
COAP_RESULT_OPTION_UNKNOWN, // An unknown option code was specified.
COAP_RESULT_OPTION_NULL, // Option length > 0 but NULL pointer given for option value.
COAP_RESULT_OPTION_LENGTH, // Illegal length for option specified.
COAP_RESULT_OPTION_NOT_FOUND,// The requested option was not found in the message.
COAP_RESULT_BUFFER_OVERRUN, // Adding data would overrun the packet buffer
COAP_RESULT_BUILD_ORDER, // Message build order incorrect.
COAP_RESULT_INVALID_MSG, // Received message is malformed or invalid.
COAP_RESULT_FAILURE // Operation failed or unspecified error
};
CoapMsg(uint8_t* buffer, uint16_t bufferLen);
CoapMsg(uint8_t* buffer, uint16_t bufferLen, uint16_t packetLen);
void setType(CoapMsg::Type msgType);
CoapMsg::Type getType();
void setId(uint16_t msgId);
uint16_t getId();
void setCode(CoapMsg::Code code);
CoapMsg::Code getCode();
uint16_t getHTTPStatus();
CoapMsg::Result setToken(const uint8_t* token, uint8_t tokenLen);
uint8_t* getToken();
uint8_t getTokenLen();
CoapMsg::Result addOption(CoapMsg::Option optionCode, const uint8_t* optionValue, uint16_t optionLen);
CoapMsg::Result getOption(CoapMsg::Option optionCode, uint16_t index, uint8_t*& optionValue, uint16_t& optionLen);
uint16_t getOptionCount(CoapMsg::Option optionCode);
uint16_t getOptionLen(CoapMsg::Option optionCode, uint16_t index);
uint8_t* getOptionValue(CoapMsg::Option optionCode, uint16_t index);
CoapMsg::Result setPayload(const uint8_t* payload, uint16_t payloadLen);
uint8_t* getPayload();
uint16_t getPayloadLen();
uint8_t* getMsgBytes();
uint16_t getMsgLen();
bool isValid();
uint16_t getBlock1Size();
uint32_t getBlock1Num();
bool getBlock1More();
uint16_t getBlock2Size();
uint32_t getBlock2Num();
bool getBlock2More();
void convertToReset();
void convertToEmptyAck();
protected:
uint8_t* m_buffer;
uint16_t m_bufferLen;
uint16_t m_msgLen;
enum BuildState {
BUILD_BEGIN,
BUILD_HAVE_TOKEN,
BUILD_HAVE_OPTIONS,
BUILD_HAVE_PAYLOAD
};
CoapMsg::BuildState m_buildState;
uint16_t m_lastOptionCode;
protected:
CoapMsg::Result validateOption(CoapMsg::Option optionCode, const uint8_t* optionValue, uint16_t optionLen);
CoapMsg::Result validateOptionValue(uint16_t minLen, uint16_t maxLen, const uint8_t* optionValue, uint16_t optionLen);
uint8_t* decodeOption(uint8_t* buffer, uint16_t* optionDelta, uint16_t* optionLen);
uint16_t getBlockSize(CoapMsg::Option optionCode);
uint32_t getBlockNum(CoapMsg::Option optionCode);
bool getBlockMore(CoapMsg::Option optionCode);
};
#endif //TEMBOOCOAP_H_

View File

@@ -0,0 +1,176 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "CoapRRLayer.h"
CoapRRLayer::CoapRRLayer(CoapMessageLayer& messageLayer, uint8_t* rxBuffer, uint16_t rxBufferLen) :
m_messageLayer(messageLayer),
m_state(STATE_IDLE),
m_lastResult(NO_ERROR),
m_token(NULL),
m_rxBuffer(rxBuffer),
m_rxBufferLen(rxBufferLen) {
}
CoapRRLayer::Result CoapRRLayer::reliableSend(CoapMsg& msg, char* token, IPAddress addr, uint16_t port) {
if (m_state != STATE_IDLE) {
return ERROR_IMPROPER_STATE;
}
if (CoapMessageLayer::NO_ERROR != m_messageLayer.reliableSend(msg, addr, port)) {
return ERROR_SENDING_MSG;
}
m_token = token;
m_state = STATE_WAITING;
return NO_ERROR;
}
bool CoapRRLayer::rxTokenMatches(CoapMsg& msg) {
if (msg.getTokenLen() != strlen(m_token)) {
return false;
}
if (0 != memcmp(msg.getToken(), m_token, strlen(m_token))) {
return false;
}
return true;
}
CoapRRLayer::Result CoapRRLayer::loop() {
m_lastResult = NO_ERROR;
switch (m_state) {
case STATE_IDLE:
// Pump the receiver.
// We're not serving anything, so unless there's an outstanding
// request (which would mean we would be in STATE_WAITING, not STATE_IDLE),
// we're going to reject or ignore any incoming traffic.
switch(m_messageLayer.loop()) {
case CON_RECEIVED:
{
// Explicitly reject any CON messages so the sender will
// quit bugging us with retransmissions.
CoapMsg msg(m_rxBuffer, m_rxBufferLen, m_messageLayer.getRXByteCount());
m_messageLayer.rejectMsg(msg);
break;
}
default:
// Just ignore anything else.
break;
}
break;
case STATE_WAITING:
// We're waiting for a response to an earlier request.
switch(m_messageLayer.loop()) {
case CoapMessageLayer::NO_ERROR:
// Nothing happened. Nothing to do.
break;
case CoapMessageLayer::ACK_RECEIVED:
{
CoapMsg msg(m_rxBuffer, m_rxBufferLen, m_messageLayer.getRXByteCount());
// If it wasn't an empty ack, it's a response.
// And if the token matches, then it's the response we're waiting for.
if (rxTokenMatches(msg)) {
m_lastResult = RESPONSE_RECEIVED;
m_state = STATE_IDLE;
} else {
// if ACK is not empty and tokens don't match, an error has occurred
if (msg.getTokenLen() != 0) {
m_lastResult = ERROR_RECEIVING_RESPONSE;
m_state = STATE_IDLE;
TEMBOO_TRACE("Error: ");
TEMBOO_TRACELN("Msg token did not match");
}
}
break;
}
case CoapMessageLayer::RESET_RECEIVED:
{
// If it was a reset, the message should be empty
// and there will be no token
m_lastResult = RST_RECEIVED;
m_state = STATE_IDLE;
break;
}
case CoapMessageLayer::CON_RECEIVED:
{
// See if this is our response or just some random message.
// The message layer has already confirmed it's from the right host.
CoapMsg msg(m_rxBuffer, m_rxBufferLen, m_messageLayer.getRXByteCount());
// We only accept responses for the current request (i.e. the tokens must match)
if (rxTokenMatches(msg)) {
m_lastResult = RESPONSE_RECEIVED;
m_state = STATE_IDLE;
} else {
m_messageLayer.rejectMsg(msg);
TEMBOO_TRACE("Error: ");
TEMBOO_TRACELN("Msg token did not match");
}
break;
}
case CoapMessageLayer::NON_RECEIVED:
{
CoapMsg msg(m_rxBuffer, m_rxBufferLen, m_messageLayer.getRXByteCount());
if (rxTokenMatches(msg)) {
m_lastResult = RESPONSE_RECEIVED;
m_state = STATE_IDLE;
} else {
// if the token does not match, then an error occurred
m_lastResult = ERROR_RECEIVING_RESPONSE;
m_state = STATE_IDLE;
TEMBOO_TRACE("Error: ");
TEMBOO_TRACELN("Msg token did not match");
}
break;
}
default:
// Anything else indicates a failure of some sort. Check
// the messageLayer lastResult for specifics.
m_lastResult = ERROR_RECEIVING_RESPONSE;
m_state = STATE_IDLE;
}
break;
}
return m_lastResult;
}

View File

@@ -0,0 +1,82 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef COAPRRLAYER_H_
#define COAPRRLAYER_H_
#include "CoapMessageLayer.h"
#include "CoapMsg.h"
/**
* CoapRRLayer (Request/Response layer) is a middle layer of the CoAP stack. It is responsible for
* sending requests and receiving responses to those requests. Note that it does not handle
* ACKs or RESETs. Those are handled at the CoapMessageLayer.
*
* This class is intended to implement the CoAP Client Request/Response Layer FSM as described
* by Kovatsch et al. in https://tools.ietf.org/html/draft-kovatsch-lwig-coap-01
*
* Note that this design only implements the client functionality as our application does not
* serve anything.
*/
class CoapRRLayer {
public:
enum Result {
NO_ERROR = 0,
RESPONSE_RECEIVED,
ACK_RECEIVED,
CON_RECEIVED,
ERROR_IMPROPER_STATE,
ERROR_SENDING_MSG,
ERROR_RECEIVING_RESPONSE,
RST_RECEIVED
};
enum State {
STATE_IDLE,
STATE_WAITING
};
CoapRRLayer(CoapMessageLayer& messageLayer, uint8_t* rxBuffer, uint16_t rxBufferLen);
Result reliableSend(CoapMsg& msg, char* token, IPAddress addr, uint16_t port);
Result send(CoapMsg& msg, char* token, IPAddress addr, uint16_t port);
Result loop();
Result getLastResult() {return m_lastResult;}
void setState(State state) {m_state = state;}
int16_t getRxByteCount() {return m_rxByteCount;}
protected:
CoapMessageLayer& m_messageLayer;
State m_state;
Result m_lastResult;
char* m_token;
uint8_t* m_rxBuffer;
int16_t m_rxByteCount;
uint16_t m_rxBufferLen;
bool rxTokenMatches(CoapMsg& msg);
};
#endif

View File

@@ -0,0 +1,226 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include "DataFormatter.h"
DataFormatter::DataFormatter(const ChoreoInputSet* inputSet, const ChoreoInputExpressionSet* expressionSet, const ChoreoSensorInputSet* sensorSet, const ChoreoOutputSet* outputSet, const ChoreoPreset* preset, const ChoreoDevice* device, const ChoreoDevice* deviceName) :
m_inputFormatter(inputSet),
m_expressionFormatter(expressionSet),
m_sensorFormatter(sensorSet),
m_outputFormatter(outputSet),
m_presetFormatter(preset),
m_deviceTypeFormatter(device, ChoreoDeviceFormatter::DEVICE_TYPE),
m_deviceNameFormatter(deviceName, ChoreoDeviceFormatter::DEVICE_NAME) {
m_inputSet = inputSet;
m_expressionSet = expressionSet;
m_sensorSet = sensorSet;
m_outputSet = outputSet;
m_preset = preset;
reset();
}
void DataFormatter::reset() {
m_nextState = DATA_START;
m_inputFormatter.reset();
m_expressionFormatter.reset();
m_sensorFormatter.reset();
m_outputFormatter.reset();
m_presetFormatter.reset();
m_deviceTypeFormatter.reset();
m_deviceNameFormatter.reset();
}
bool DataFormatter::hasNext() {
return m_nextState != DATA_END;
}
char DataFormatter::next() {
char c;
switch(m_nextState) {
case DATA_START:
c = '{';
// only add device type or device name if
// sensor data is being sent with the Choreo
// If device name is added, no need to send
// device type
if (m_sensorFormatter.hasNext()) {
if (m_deviceNameFormatter.hasNext()) {
m_nextState = FORMATTING_DEVICE_NAME;
} else if (m_deviceTypeFormatter.hasNext()) {
m_nextState = FORMATTING_DEVICE_TYPE;
}
} else {
if (m_inputFormatter.hasNext()) {
m_nextState = FORMATTING_INPUTS;
} else if (m_expressionFormatter.hasNext()) {
m_nextState = FORMATTING_EXPRESSIONS;
} else if (m_sensorFormatter.hasNext()) {
m_nextState = FORMATTING_SENSORS;
} else if (m_outputFormatter.hasNext()) {
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
m_nextState = FORMATTING_PRESET;
} else {
m_nextState = FORMATTING_EMPTY;
}
}
break;
case FORMATTING_DEVICE_TYPE:
if (m_deviceTypeFormatter.hasNext()) {
c = m_deviceTypeFormatter.next();
} else if (m_inputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_INPUTS;
} else if (m_expressionFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_EXPRESSIONS;
} else if (m_sensorFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_SENSORS;
} else if (m_outputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_DEVICE_NAME:
if (m_deviceNameFormatter.hasNext()) {
c = m_deviceNameFormatter.next();
} else if (m_inputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_INPUTS;
} else if (m_expressionFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_EXPRESSIONS;
} else if (m_sensorFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_SENSORS;
} else if (m_outputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_INPUTS:
if (m_inputFormatter.hasNext()) {
c = m_inputFormatter.next();
} else if (m_expressionFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_EXPRESSIONS;
} else if (m_sensorFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_SENSORS;
} else if (m_outputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_EXPRESSIONS:
if (m_expressionFormatter.hasNext()) {
c = m_expressionFormatter.next();
} else if (m_sensorFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_SENSORS;
} else if (m_outputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_SENSORS:
if (m_sensorFormatter.hasNext()) {
c = m_sensorFormatter.next();
} else if (m_outputFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_OUTPUTS;
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_OUTPUTS:
if (m_outputFormatter.hasNext()) {
c = m_outputFormatter.next();
} else if (m_presetFormatter.hasNext()) {
c = ',';
m_nextState = FORMATTING_PRESET;
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_PRESET:
if (m_presetFormatter.hasNext()) {
c = m_presetFormatter.next();
} else {
c = '}';
m_nextState = DATA_END;
}
break;
case FORMATTING_EMPTY:
c = '}';
m_nextState = DATA_END;
break;
case DATA_END:
default:
c = '\0';
break;
}
return c;
}

View File

@@ -0,0 +1,80 @@
/*
###############################################################################
#
# Temboo Arduino library
#
# Copyright 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef DATAFORMATTER_H_
#define DATAFORMATTER_H_
#include "TembooGlobal.h"
#include "ChoreoInputSet.h"
#include "ChoreoInputExpressionSet.h"
#include "ChoreoSensorInputSet.h"
#include "ChoreoOutputSet.h"
#include "ChoreoPreset.h"
#include "ChoreoDevice.h"
#include "ChoreoInputFormatter.h"
#include "ChoreoInputExpressionFormatter.h"
#include "ChoreoSensorInputFormatter.h"
#include "ChoreoOutputFormatter.h"
#include "ChoreoPresetFormatter.h"
#include "ChoreoDeviceFormatter.h"
class DataFormatter {
public:
DataFormatter(const ChoreoInputSet* inputSet, const ChoreoInputExpressionSet* expressionSet, const ChoreoSensorInputSet* sensorSet, const ChoreoOutputSet* outputSet, const ChoreoPreset* preset, const ChoreoDevice* device, const ChoreoDevice* deviceName);
bool hasNext();
char next();
void reset();
private:
const ChoreoInputSet* m_inputSet;
const ChoreoSensorInputSet* m_sensorSet;
const ChoreoInputExpressionSet* m_expressionSet;
const ChoreoOutputSet* m_outputSet;
const ChoreoPreset* m_preset;
ChoreoInputFormatter m_inputFormatter;
ChoreoInputExpressionFormatter m_expressionFormatter;
ChoreoSensorInputFormatter m_sensorFormatter;
ChoreoOutputFormatter m_outputFormatter;
ChoreoPresetFormatter m_presetFormatter;
ChoreoDeviceFormatter m_deviceTypeFormatter;
ChoreoDeviceFormatter m_deviceNameFormatter;
enum State {
DATA_START,
FORMATTING_INPUTS,
FORMATTING_EXPRESSIONS,
FORMATTING_SENSORS,
FORMATTING_OUTPUTS,
FORMATTING_PRESET,
FORMATTING_DEVICE_TYPE,
FORMATTING_DEVICE_NAME,
FORMATTING_EMPTY,
DATA_END
};
State m_nextState;
};
#endif

View File

@@ -0,0 +1,208 @@
/*******************************************************************************
* Copyright (c) 2013, 2014
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Sam Grove - initial API and implementation and/or initial documentation
* Ian Craggs - added attached and detached member functions
* Sam Grove - removed need for FP.cpp
*******************************************************************************/
#ifndef FP_H
#define FP_H
/** Example using the FP Class with global functions
* @code
* #include "mbed.h"
* #include "FP.h"
*
* FP<void,bool>fp;
* DigitalOut myled(LED1);
*
* void handler(bool value)
* {
* myled = value;
* return;
* }
*
* int main()
* {
* fp.attach(&handler);
*
* while(1)
* {
* fp(1);
* wait(0.2);
* fp(0);
* wait(0.2);
* }
* }
* @endcode
*/
/** Example using the FP Class with different class member functions
* @code
* #include "mbed.h"
* #include "FP.h"
*
* FP<void,bool>fp;
* DigitalOut myled(LED4);
*
* class Wrapper
* {
* public:
* Wrapper(){}
*
* void handler(bool value)
* {
* myled = value;
* return;
* }
* };
*
* int main()
* {
* Wrapper wrapped;
* fp.attach(&wrapped, &Wrapper::handler);
*
* while(1)
* {
* fp(1);
* wait(0.2);
* fp(0);
* wait(0.2);
* }
* }
* @endcode
*/
/** Example using the FP Class with member FP and member function
* @code
* #include "mbed.h"
* #include "FP.h"
*
* DigitalOut myled(LED2);
*
* class Wrapper
* {
* public:
* Wrapper()
* {
* fp.attach(this, &Wrapper::handler);
* }
*
* void handler(bool value)
* {
* myled = value;
* return;
* }
*
* FP<void,bool>fp;
* };
*
* int main()
* {
* Wrapper wrapped;
*
* while(1)
* {
* wrapped.fp(1);
* wait(0.2);
* wrapped.fp(0);
* wait(0.2);
* }
* }
* @endcode
*/
/**
* @class FP
* @brief API for managing Function Pointers
*/
template<class retT, class argT>
class FP
{
public:
/** Create the FP object - only one callback can be attached to the object, that is
* a member function or a global function, not both at the same time
*/
FP()
{
obj_callback = 0;
c_callback = 0;
}
/** Add a callback function to the object
* @param item - Address of the initialized object
* @param member - Address of the member function (dont forget the scope that the function is defined in)
*/
template<class T>
void attach(T *item, retT (T::*method)(argT))
{
obj_callback = (FPtrDummy *)(item);
method_callback = (retT (FPtrDummy::*)(argT))(method);
return;
}
/** Add a callback function to the object
* @param function - The address of a globally defined function
*/
void attach(retT (*function)(argT))
{
c_callback = function;
}
/** Invoke the function attached to the class
* @param arg - An argument that is passed into the function handler that is called
* @return The return from the function hanlder called by this class
*/
retT operator()(argT arg) const
{
if( 0 != c_callback ) {
return obj_callback ? (obj_callback->*method_callback)(arg) : (*c_callback)(arg);
}
return (retT)0;
}
/** Determine if an callback is currently hooked
* @return 1 if a method is hooked, 0 otherwise
*/
bool attached()
{
return obj_callback || c_callback;
}
/** Release a function from the callback hook
*/
void detach()
{
obj_callback = 0;
c_callback = 0;
}
private:
// empty type used for casting
class FPtrDummy;
FPtrDummy *obj_callback;
/**
* @union Funciton
* @brief Member or global callback function
*/
union {
retT (*c_callback)(argT); /*!< Footprint for a global function */
retT (FPtrDummy::*method_callback)(argT); /*!< Footprint for a member function */
};
};
#endif

View File

@@ -0,0 +1,954 @@
/*******************************************************************************
* Copyright (c) 2014, 2015 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Ian Craggs - fix for bug 458512 - QoS 2 messages
* Ian Craggs - fix for bug 460389 - send loop uses wrong length
* Ian Craggs - fix for bug 464169 - clearing subscriptions
* Ian Craggs - fix for bug 464551 - enums and ints can be different size
*******************************************************************************/
#if !defined(MQTTCLIENT_H)
#define MQTTCLIENT_H
#include "FP.h"
#include "MQTTPacket.h"
#include "stdio.h"
#include "MQTTLogging.h"
#if !defined(MQTTCLIENT_QOS1)
#define MQTTCLIENT_QOS1 1
#endif
#if !defined(MQTTCLIENT_QOS2)
#define MQTTCLIENT_QOS2 0
#endif
namespace MQTT
{
enum QoS { QOS0, QOS1, QOS2 };
// all failure return codes must be negative
enum returnCode { BUFFER_OVERFLOW = -2, FAILURE = -1, SUCCESS = 0 };
struct Message
{
enum QoS qos;
bool retained;
bool dup;
unsigned short id;
void *payload;
size_t payloadlen;
};
struct MessageData
{
MessageData(MQTTString &aTopicName, struct Message &aMessage) : message(aMessage), topicName(aTopicName)
{ }
struct Message &message;
MQTTString &topicName;
};
class PacketId
{
public:
PacketId()
{
next = 0;
}
int getNext()
{
next = (next == MAX_PACKET_ID) ? 1 : next + 1;
return next;
}
private:
static const int MAX_PACKET_ID = 65535;
int next;
};
/**
* @class Client
* @brief blocking, non-threaded MQTT client API
*
* This version of the API blocks on all method calls, until they are complete. This means that only one
* MQTT request can be in process at any one time.
* @param Network a network class which supports send, receive
* @param Timer a timer class with the methods:
*/
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE = 100, int MAX_MESSAGE_HANDLERS = 5>
class Client
{
public:
typedef void (*messageHandler)(MessageData&);
/** Construct the client
* @param network - pointer to an instance of the Network class - must be connected to the endpoint
* before calling MQTT connect
* @param limits an instance of the Limit class - to alter limits as required
*/
Client(Network& network, unsigned int command_timeout_ms = 30000);
/** Set the default message handling callback - used for any message which does not match a subscription message handler
* @param mh - pointer to the callback function
*/
void setDefaultMessageHandler(messageHandler mh)
{
defaultMessageHandler.attach(mh);
}
/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack
* The nework object must be connected to the network endpoint before calling this
* Default connect options are used
* @return success code -
*/
int connect();
/** MQTT Connect - send an MQTT connect packet down the network and wait for a Connack
* The nework object must be connected to the network endpoint before calling this
* @param options - connect options
* @return success code -
*/
int connect(MQTTPacket_connectData& options);
/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs
* @param topic - the topic to publish to
* @param message - the message to send
* @return success code -
*/
int publish(const char* topicName, Message& message);
/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs
* @param topic - the topic to publish to
* @param payload - the data to send
* @param payloadlen - the length of the data
* @param qos - the QoS to send the publish at
* @param retained - whether the message should be retained
* @return success code -
*/
int publish(const char* topicName, void* payload, size_t payloadlen, enum QoS qos = QOS0, bool retained = false);
/** MQTT Publish - send an MQTT publish packet and wait for all acks to complete for all QoSs
* @param topic - the topic to publish to
* @param payload - the data to send
* @param payloadlen - the length of the data
* @param id - the packet id used - returned
* @param qos - the QoS to send the publish at
* @param retained - whether the message should be retained
* @return success code -
*/
int publish(const char* topicName, void* payload, size_t payloadlen, unsigned short& id, enum QoS qos = QOS1, bool retained = false);
/** MQTT Subscribe - send an MQTT subscribe packet and wait for the suback
* @param topicFilter - a topic pattern which can include wildcards
* @param qos - the MQTT QoS to subscribe at
* @param mh - the callback function to be invoked when a message is received for this subscription
* @return success code -
*/
int subscribe(const char* topicFilter, enum QoS qos, messageHandler mh);
/** MQTT Unsubscribe - send an MQTT unsubscribe packet and wait for the unsuback
* @param topicFilter - a topic pattern which can include wildcards
* @return success code -
*/
int unsubscribe(const char* topicFilter);
/** MQTT Disconnect - send an MQTT disconnect packet, and clean up any state
* @return success code -
*/
int disconnect();
/** A call to this API must be made within the keepAlive interval to keep the MQTT connection alive
* yield can be called if no other MQTT operation is needed. This will also allow messages to be
* received.
* @param timeout_ms the time to wait, in milliseconds
* @return success code - on failure, this means the client has disconnected
*/
int yield(unsigned long timeout_ms = 1000L);
/** Is the client connected?
* @return flag - is the client connected or not?
*/
bool isConnected()
{
return isconnected;
}
private:
void cleanSession();
int cycle(Timer& timer);
int waitfor(int packet_type, Timer& timer);
int keepalive();
int publish(int len, Timer& timer, enum QoS qos);
int decodePacket(int* value, int timeout);
int readPacket(Timer& timer);
int sendPacket(int length, Timer& timer);
int deliverMessage(MQTTString& topicName, Message& message);
bool isTopicMatched(char* topicFilter, MQTTString& topicName);
Network& ipstack;
unsigned long command_timeout_ms;
unsigned char sendbuf[MAX_MQTT_PACKET_SIZE];
unsigned char readbuf[MAX_MQTT_PACKET_SIZE];
Timer last_sent, last_received;
unsigned int keepAliveInterval;
bool ping_outstanding;
bool cleansession;
PacketId packetid;
struct MessageHandlers
{
const char* topicFilter;
FP<void, MessageData&> fp;
} messageHandlers[MAX_MESSAGE_HANDLERS]; // Message handlers are indexed by subscription topic
FP<void, MessageData&> defaultMessageHandler;
bool isconnected;
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
unsigned char pubbuf[MAX_MQTT_PACKET_SIZE]; // store the last publish for sending on reconnect
int inflightLen;
unsigned short inflightMsgid;
enum QoS inflightQoS;
#endif
#if MQTTCLIENT_QOS2
bool pubrel;
#if !defined(MAX_INCOMING_QOS2_MESSAGES)
#define MAX_INCOMING_QOS2_MESSAGES 10
#endif
unsigned short incomingQoS2messages[MAX_INCOMING_QOS2_MESSAGES];
bool isQoS2msgidFree(unsigned short id);
bool useQoS2msgid(unsigned short id);
void freeQoS2msgid(unsigned short id);
#endif
};
}
template<class Network, class Timer, int a, int MAX_MESSAGE_HANDLERS>
void MQTT::Client<Network, Timer, a, MAX_MESSAGE_HANDLERS>::cleanSession()
{
ping_outstanding = false;
for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
messageHandlers[i].topicFilter = 0;
isconnected = false;
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
inflightMsgid = 0;
inflightQoS = QOS0;
#endif
#if MQTTCLIENT_QOS2
pubrel = false;
for (int i = 0; i < MAX_INCOMING_QOS2_MESSAGES; ++i)
incomingQoS2messages[i] = 0;
#endif
}
template<class Network, class Timer, int a, int MAX_MESSAGE_HANDLERS>
MQTT::Client<Network, Timer, a, MAX_MESSAGE_HANDLERS>::Client(Network& network, unsigned int command_timeout_ms) : ipstack(network), packetid()
{
last_sent = Timer();
last_received = Timer();
this->command_timeout_ms = command_timeout_ms;
cleanSession();
}
#if MQTTCLIENT_QOS2
template<class Network, class Timer, int a, int b>
bool MQTT::Client<Network, Timer, a, b>::isQoS2msgidFree(unsigned short id)
{
for (int i = 0; i < MAX_INCOMING_QOS2_MESSAGES; ++i)
{
if (incomingQoS2messages[i] == id)
return false;
}
return true;
}
template<class Network, class Timer, int a, int b>
bool MQTT::Client<Network, Timer, a, b>::useQoS2msgid(unsigned short id)
{
for (int i = 0; i < MAX_INCOMING_QOS2_MESSAGES; ++i)
{
if (incomingQoS2messages[i] == 0)
{
incomingQoS2messages[i] = id;
return true;
}
}
return false;
}
template<class Network, class Timer, int a, int b>
void MQTT::Client<Network, Timer, a, b>::freeQoS2msgid(unsigned short id)
{
for (int i = 0; i < MAX_INCOMING_QOS2_MESSAGES; ++i)
{
if (incomingQoS2messages[i] == id)
{
incomingQoS2messages[i] = 0;
return;
}
}
}
#endif
template<class Network, class Timer, int a, int b>
int MQTT::Client<Network, Timer, a, b>::sendPacket(int length, Timer& timer)
{
int rc = FAILURE,
sent = 0;
while (sent < length && !timer.expired())
{
rc = ipstack.write(&sendbuf[sent], length - sent, timer.left_ms());
if (rc < 0) // there was an error writing the data
break;
sent += rc;
}
if (sent == length)
{
if (this->keepAliveInterval > 0)
last_sent.countdown(this->keepAliveInterval); // record the fact that we have successfully sent the packet
rc = SUCCESS;
}
else
rc = FAILURE;
#if defined(MQTT_DEBUG)
char printbuf[150];
DEBUG("Rc %d from sending packet %s\n", rc, MQTTFormat_toServerString(printbuf, sizeof(printbuf), sendbuf, length));
#endif
return rc;
}
template<class Network, class Timer, int a, int b>
int MQTT::Client<Network, Timer, a, b>::decodePacket(int* value, int timeout)
{
unsigned char c;
int multiplier = 1;
int len = 0;
const int MAX_NO_OF_REMAINING_LENGTH_BYTES = 4;
*value = 0;
do
{
int rc = MQTTPACKET_READ_ERROR;
if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES)
{
rc = MQTTPACKET_READ_ERROR; /* bad data */
goto exit;
}
rc = ipstack.read(&c, 1, timeout);
if (rc != 1)
goto exit;
*value += (c & 127) * multiplier;
multiplier *= 128;
} while ((c & 128) != 0);
exit:
return len;
}
/**
* If any read fails in this method, then we should disconnect from the network, as on reconnect
* the packets can be retried.
* @param timeout the max time to wait for the packet read to complete, in milliseconds
* @return the MQTT packet type, or -1 if none
*/
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::readPacket(Timer& timer)
{
int rc = FAILURE;
MQTTHeader header = {0};
int len = 0;
int rem_len = 0;
/* 1. read the header byte. This has the packet type in it */
if (ipstack.read(readbuf, 1, timer.left_ms()) != 1)
goto exit;
len = 1;
/* 2. read the remaining length. This is variable in itself */
decodePacket(&rem_len, timer.left_ms());
len += MQTTPacket_encode(readbuf + 1, rem_len); /* put the original remaining length into the buffer */
if (rem_len > (MAX_MQTT_PACKET_SIZE - len))
{
rc = BUFFER_OVERFLOW;
}
/* 3. read the rest of the buffer using a callback to supply the rest of the data */
if (rc == BUFFER_OVERFLOW) {
if (rem_len > 0 && (ipstack.read(readbuf + len, MAX_MQTT_PACKET_SIZE - len, timer.left_ms()) != (MAX_MQTT_PACKET_SIZE - len)))
goto exit;
readbuf[MAX_MQTT_PACKET_SIZE-1] = '\0';
rem_len = rem_len - MAX_MQTT_PACKET_SIZE - len;
while(rem_len--) {
unsigned char c;
ipstack.read(&c, 1, timer.left_ms() );
}
}
else {
if (rem_len > 0 && (ipstack.read(readbuf + len, rem_len, timer.left_ms()) != rem_len))
goto exit;
header.byte = readbuf[0];
rc = header.bits.type;
}
if (this->keepAliveInterval > 0)
last_received.countdown(this->keepAliveInterval); // record the fact that we have successfully received a packet
exit:
#if defined(MQTT_DEBUG)
if (rc >= 0)
{
char printbuf[50];
DEBUG("Rc %d from receiving packet %s\n", rc, MQTTFormat_toClientString(printbuf, sizeof(printbuf), readbuf, len));
}
#endif
return rc;
}
// assume topic filter and name is in correct format
// # can only be at end
// + and # can only be next to separator
template<class Network, class Timer, int a, int b>
bool MQTT::Client<Network, Timer, a, b>::isTopicMatched(char* topicFilter, MQTTString& topicName)
{
char* curf = topicFilter;
char* curn = topicName.lenstring.data;
char* curn_end = curn + topicName.lenstring.len;
while (*curf && curn < curn_end)
{
if (*curn == '/' && *curf != '/')
break;
if (*curf != '+' && *curf != '#' && *curf != *curn)
break;
if (*curf == '+')
{ // skip until we meet the next separator, or end of string
char* nextpos = curn + 1;
while (nextpos < curn_end && *nextpos != '/')
nextpos = ++curn + 1;
}
else if (*curf == '#')
curn = curn_end - 1; // skip until end of string
curf++;
curn++;
};
return (curn == curn_end) && (*curf == '\0');
}
template<class Network, class Timer, int a, int MAX_MESSAGE_HANDLERS>
int MQTT::Client<Network, Timer, a, MAX_MESSAGE_HANDLERS>::deliverMessage(MQTTString& topicName, Message& message)
{
int rc = FAILURE;
// we have to find the right message handler - indexed by topic
for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
{
if (messageHandlers[i].topicFilter != 0 && (MQTTPacket_equals(&topicName, (char*)messageHandlers[i].topicFilter) ||
isTopicMatched((char*)messageHandlers[i].topicFilter, topicName)))
{
if (messageHandlers[i].fp.attached())
{
MessageData md(topicName, message);
messageHandlers[i].fp(md);
rc = SUCCESS;
}
}
}
if (rc == FAILURE && defaultMessageHandler.attached())
{
MessageData md(topicName, message);
defaultMessageHandler(md);
rc = SUCCESS;
}
return rc;
}
template<class Network, class Timer, int a, int b>
int MQTT::Client<Network, Timer, a, b>::yield(unsigned long timeout_ms)
{
int rc = SUCCESS;
Timer timer = Timer();
timer.countdown_ms(timeout_ms);
while (!timer.expired())
{
if (cycle(timer) < 0)
{
rc = FAILURE;
break;
}
}
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::cycle(Timer& timer)
{
/* get one piece of work off the wire and one pass through */
// read the socket, see what work is due
int packet_type = readPacket(timer);
int rc = SUCCESS;
if (packet_type == BUFFER_OVERFLOW) {
MQTTHeader header = {0};
header.byte = readbuf[0];
packet_type = header.bits.type;
}
int len = 0;
switch (packet_type)
{
case FAILURE:
case BUFFER_OVERFLOW:
rc = packet_type;
break;
case CONNACK:
case PUBACK:
case SUBACK:
break;
case PUBLISH:
{
MQTTString topicName = MQTTString_initializer;
Message msg;
int intQoS;
if (MQTTDeserialize_publish((unsigned char*)&msg.dup, &intQoS, (unsigned char*)&msg.retained, (unsigned short*)&msg.id, &topicName,
(unsigned char**)&msg.payload, (int*)&msg.payloadlen, readbuf, MAX_MQTT_PACKET_SIZE) != 1)
goto exit;
msg.qos = (enum QoS)intQoS;
#if MQTTCLIENT_QOS2
if (msg.qos != QOS2)
#endif
deliverMessage(topicName, msg);
#if MQTTCLIENT_QOS2
else if (isQoS2msgidFree(msg.id))
{
if (useQoS2msgid(msg.id))
deliverMessage(topicName, msg);
else
WARN("Maximum number of incoming QoS2 messages exceeded");
}
#endif
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
if (msg.qos != QOS0)
{
if (msg.qos == QOS1)
len = MQTTSerialize_ack(sendbuf, MAX_MQTT_PACKET_SIZE, PUBACK, 0, msg.id);
else if (msg.qos == QOS2)
len = MQTTSerialize_ack(sendbuf, MAX_MQTT_PACKET_SIZE, PUBREC, 0, msg.id);
if (len <= 0)
rc = FAILURE;
else
rc = sendPacket(len, timer);
if (rc == FAILURE)
goto exit; // there was a problem
}
break;
#endif
}
#if MQTTCLIENT_QOS2
case PUBREC:
case PUBREL:
unsigned short mypacketid;
unsigned char dup, type;
if (MQTTDeserialize_ack(&type, &dup, &mypacketid, readbuf, MAX_MQTT_PACKET_SIZE) != 1)
rc = FAILURE;
else if ((len = MQTTSerialize_ack(sendbuf, MAX_MQTT_PACKET_SIZE,
(packet_type == PUBREC) ? PUBREL : PUBCOMP, 0, mypacketid)) <= 0)
rc = FAILURE;
else if ((rc = sendPacket(len, timer)) != SUCCESS) // send the PUBREL packet
rc = FAILURE; // there was a problem
if (rc == FAILURE)
goto exit; // there was a problem
if (packet_type == PUBREL)
freeQoS2msgid(mypacketid);
break;
case PUBCOMP:
break;
#endif
case PINGRESP:
ping_outstanding = false;
break;
}
keepalive();
exit:
if (rc == SUCCESS)
rc = packet_type;
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::keepalive()
{
int rc = FAILURE;
if (keepAliveInterval == 0)
{
rc = SUCCESS;
goto exit;
}
if (last_sent.expired() || last_received.expired())
{
if (!ping_outstanding)
{
Timer timer = Timer(1000);
int len = MQTTSerialize_pingreq(sendbuf, MAX_MQTT_PACKET_SIZE);
if (len > 0 && (rc = sendPacket(len, timer)) == SUCCESS) // send the ping packet
ping_outstanding = true;
}
}
exit:
return rc;
}
// only used in single-threaded mode where one command at a time is in process
template<class Network, class Timer, int a, int b>
int MQTT::Client<Network, Timer, a, b>::waitfor(int packet_type, Timer& timer)
{
int rc = FAILURE;
do
{
if (timer.expired())
break; // we timed out
}
while ((rc = cycle(timer)) != packet_type);
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::connect(MQTTPacket_connectData& options)
{
Timer connect_timer = Timer(command_timeout_ms);
int rc = FAILURE;
int len = 0;
if (isconnected) // don't send connect packet again if we are already connected
goto exit;
this->keepAliveInterval = options.keepAliveInterval;
this->cleansession = options.cleansession;
if ((len = MQTTSerialize_connect(sendbuf, MAX_MQTT_PACKET_SIZE, &options)) <= 0)
goto exit;
if ((rc = sendPacket(len, connect_timer)) != SUCCESS) // send the connect packet
goto exit; // there was a problem
if (this->keepAliveInterval > 0)
last_received.countdown(this->keepAliveInterval);
// this will be a blocking call, wait for the connack
if (waitfor(CONNACK, connect_timer) == CONNACK)
{
unsigned char connack_rc = 255;
bool sessionPresent = false;
if (MQTTDeserialize_connack((unsigned char*)&sessionPresent, &connack_rc, readbuf, MAX_MQTT_PACKET_SIZE) == 1)
rc = connack_rc;
else
rc = FAILURE;
}
else
rc = FAILURE;
#if MQTTCLIENT_QOS2
// resend any inflight publish
if (inflightMsgid > 0 && inflightQoS == QOS2 && pubrel)
{
if ((len = MQTTSerialize_ack(sendbuf, MAX_MQTT_PACKET_SIZE, PUBREL, 0, inflightMsgid)) <= 0)
rc = FAILURE;
else
rc = publish(len, connect_timer, inflightQoS);
}
else
#endif
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
if (inflightMsgid > 0)
{
memcpy(sendbuf, pubbuf, MAX_MQTT_PACKET_SIZE);
rc = publish(inflightLen, connect_timer, inflightQoS);
}
#endif
exit:
if (rc == SUCCESS)
isconnected = true;
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::connect()
{
MQTTPacket_connectData default_options = MQTTPacket_connectData_initializer;
return connect(default_options);
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int MAX_MESSAGE_HANDLERS>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, MAX_MESSAGE_HANDLERS>::subscribe(const char* topicFilter, enum QoS qos, messageHandler messageHandler)
{
int rc = FAILURE;
Timer timer = Timer(command_timeout_ms);
int len = 0;
MQTTString topic = {(char*)topicFilter, {0, 0}};
if (!isconnected)
goto exit;
len = MQTTSerialize_subscribe(sendbuf, MAX_MQTT_PACKET_SIZE, 0, packetid.getNext(), 1, &topic, (int*)&qos);
if (len <= 0)
goto exit;
if ((rc = sendPacket(len, timer)) != SUCCESS) // send the subscribe packet
goto exit; // there was a problem
if (waitfor(SUBACK, timer) == SUBACK) // wait for suback
{
int count = 0, grantedQoS = -1;
unsigned short mypacketid;
if (MQTTDeserialize_suback(&mypacketid, 1, &count, &grantedQoS, readbuf, MAX_MQTT_PACKET_SIZE) == 1)
rc = grantedQoS; // 0, 1, 2 or 0x80
if (rc != 0x80)
{
for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
{
if (messageHandlers[i].topicFilter == 0)
{
messageHandlers[i].topicFilter = topicFilter;
messageHandlers[i].fp.attach(messageHandler);
rc = 0;
break;
}
}
}
}
else
rc = FAILURE;
exit:
if (rc != SUCCESS)
cleanSession();
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int MAX_MESSAGE_HANDLERS>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, MAX_MESSAGE_HANDLERS>::unsubscribe(const char* topicFilter)
{
int rc = FAILURE;
Timer timer = Timer(command_timeout_ms);
MQTTString topic = {(char*)topicFilter, {0, 0}};
int len = 0;
if (!isconnected)
goto exit;
if ((len = MQTTSerialize_unsubscribe(sendbuf, MAX_MQTT_PACKET_SIZE, 0, packetid.getNext(), 1, &topic)) <= 0)
goto exit;
if ((rc = sendPacket(len, timer)) != SUCCESS) // send the unsubscribe packet
goto exit; // there was a problem
if (waitfor(UNSUBACK, timer) == UNSUBACK)
{
unsigned short mypacketid; // should be the same as the packetid above
if (MQTTDeserialize_unsuback(&mypacketid, readbuf, MAX_MQTT_PACKET_SIZE) == 1)
{
rc = 0;
// remove the subscription message handler associated with this topic, if there is one
for (int i = 0; i < MAX_MESSAGE_HANDLERS; ++i)
{
if (strcmp(messageHandlers[i].topicFilter, topicFilter) == 0)
{
messageHandlers[i].topicFilter = 0;
break;
}
}
}
}
else
rc = FAILURE;
exit:
if (rc != SUCCESS)
cleanSession();
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::publish(int len, Timer& timer, enum QoS qos)
{
int rc;
if ((rc = sendPacket(len, timer)) != SUCCESS) // send the publish packet
goto exit; // there was a problem
#if MQTTCLIENT_QOS1
if (qos == QOS1)
{
if (waitfor(PUBACK, timer) == PUBACK)
{
unsigned short mypacketid;
unsigned char dup, type;
if (MQTTDeserialize_ack(&type, &dup, &mypacketid, readbuf, MAX_MQTT_PACKET_SIZE) != 1)
rc = FAILURE;
else if (inflightMsgid == mypacketid)
inflightMsgid = 0;
}
else
rc = FAILURE;
}
#elif MQTTCLIENT_QOS2
else if (qos == QOS2)
{
if (waitfor(PUBCOMP, timer) == PUBCOMP)
{
unsigned short mypacketid;
unsigned char dup, type;
if (MQTTDeserialize_ack(&type, &dup, &mypacketid, readbuf, MAX_MQTT_PACKET_SIZE) != 1)
rc = FAILURE;
else if (inflightMsgid == mypacketid)
inflightMsgid = 0;
}
else
rc = FAILURE;
}
#endif
exit:
if (rc != SUCCESS)
cleanSession();
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::publish(const char* topicName, void* payload, size_t payloadlen, unsigned short& id, enum QoS qos, bool retained)
{
int rc = FAILURE;
Timer timer = Timer(command_timeout_ms);
MQTTString topicString = MQTTString_initializer;
int len = 0;
if (!isconnected)
goto exit;
topicString.cstring = (char*)topicName;
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
if (qos == QOS1 || qos == QOS2)
id = packetid.getNext();
#endif
len = MQTTSerialize_publish(sendbuf, MAX_MQTT_PACKET_SIZE, 0, qos, retained, id,
topicString, (unsigned char*)payload, payloadlen);
if (len <= 0)
goto exit;
#if MQTTCLIENT_QOS1 || MQTTCLIENT_QOS2
if (!cleansession)
{
memcpy(pubbuf, sendbuf, len);
inflightMsgid = id;
inflightLen = len;
inflightQoS = qos;
#if MQTTCLIENT_QOS2
pubrel = false;
#endif
}
#endif
rc = publish(len, timer, qos);
exit:
return rc;
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::publish(const char* topicName, void* payload, size_t payloadlen, enum QoS qos, bool retained)
{
unsigned short id = 0; // dummy - not used for anything
return publish(topicName, payload, payloadlen, id, qos, retained);
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::publish(const char* topicName, Message& message)
{
return publish(topicName, message.payload, message.payloadlen, message.qos, message.retained);
}
template<class Network, class Timer, int MAX_MQTT_PACKET_SIZE, int b>
int MQTT::Client<Network, Timer, MAX_MQTT_PACKET_SIZE, b>::disconnect()
{
int rc = FAILURE;
Timer timer = Timer(command_timeout_ms); // we might wait for incomplete incoming publishes to complete
int len = MQTTSerialize_disconnect(sendbuf, MAX_MQTT_PACKET_SIZE);
if (len > 0)
rc = sendPacket(len, timer); // send the disconnect packet
if (cleansession)
cleanSession();
else
isconnected = false;
return rc;
}
#endif

View File

@@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Xiang Rong - 442039 Add makefile to Embedded C client
*******************************************************************************/
#ifndef MQTTCONNECT_H_
#define MQTTCONNECT_H_
#if !defined(DLLImport)
#define DLLImport
#endif
#if !defined(DLLExport)
#define DLLExport
#endif
typedef union
{
unsigned char all; /**< all connect flags */
#if defined(REVERSED)
struct
{
unsigned int username : 1; /**< 3.1 user name */
unsigned int password : 1; /**< 3.1 password */
unsigned int willRetain : 1; /**< will retain setting */
unsigned int willQoS : 2; /**< will QoS value */
unsigned int will : 1; /**< will flag */
unsigned int cleansession : 1; /**< clean session flag */
unsigned int : 1; /**< unused */
} bits;
#else
struct
{
unsigned int : 1; /**< unused */
unsigned int cleansession : 1; /**< cleansession flag */
unsigned int will : 1; /**< will flag */
unsigned int willQoS : 2; /**< will QoS value */
unsigned int willRetain : 1; /**< will retain setting */
unsigned int password : 1; /**< 3.1 password */
unsigned int username : 1; /**< 3.1 user name */
} bits;
#endif
} MQTTConnectFlags; /**< connect flags byte */
/**
* Defines the MQTT "Last Will and Testament" (LWT) settings for
* the connect packet.
*/
typedef struct
{
/** The eyecatcher for this structure. must be MQTW. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** The LWT topic to which the LWT message will be published. */
MQTTString topicName;
/** The LWT payload. */
MQTTString message;
/**
* The retained flag for the LWT message (see MQTTAsync_message.retained).
*/
unsigned char retained;
/**
* The quality of service setting for the LWT message (see
* MQTTAsync_message.qos and @ref qos).
*/
char qos;
} MQTTPacket_willOptions;
#define MQTTPacket_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, {NULL, {0, NULL}}, {NULL, {0, NULL}}, 0, 0 }
typedef struct
{
/** The eyecatcher for this structure. must be MQTC. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1
*/
unsigned char MQTTVersion;
MQTTString clientID;
unsigned short keepAliveInterval;
unsigned char cleansession;
unsigned char willFlag;
MQTTPacket_willOptions will;
MQTTString username;
MQTTString password;
} MQTTPacket_connectData;
typedef union
{
unsigned char all; /**< all connack flags */
#if defined(REVERSED)
struct
{
unsigned int sessionpresent : 1; /**< session present flag */
unsigned int : 7; /**< unused */
} bits;
#else
struct
{
unsigned int : 7; /**< unused */
unsigned int sessionpresent : 1; /**< session present flag */
} bits;
#endif
} MQTTConnackFlags; /**< connack flags byte */
#define MQTTPacket_connectData_initializer { {'M', 'Q', 'T', 'C'}, 0, 4, {NULL, {0, NULL}}, 60, 1, 0, \
MQTTPacket_willOptions_initializer, {NULL, {0, NULL}}, {NULL, {0, NULL}} }
DLLExport int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options);
DLLExport int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len);
DLLExport int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent);
DLLExport int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen);
DLLExport int MQTTSerialize_disconnect(unsigned char* buf, int buflen);
DLLExport int MQTTSerialize_pingreq(unsigned char* buf, int buflen);
#endif /* MQTTCONNECT_H_ */

View File

@@ -0,0 +1,214 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Determines the length of the MQTT connect packet that would be produced using the supplied connect options.
* @param options the options to be used to build the connect packet
* @return the length of buffer needed to contain the serialized version of the packet
*/
int MQTTSerialize_connectLength(MQTTPacket_connectData* options)
{
int len = 0;
FUNC_ENTRY;
if (options->MQTTVersion == 3)
len = 12; /* variable depending on MQTT or MQIsdp */
else if (options->MQTTVersion == 4)
len = 10;
len += MQTTstrlen(options->clientID)+2;
if (options->willFlag)
len += MQTTstrlen(options->will.topicName)+2 + MQTTstrlen(options->will.message)+2;
if (options->username.cstring || options->username.lenstring.data)
len += MQTTstrlen(options->username)+2;
if (options->password.cstring || options->password.lenstring.data)
len += MQTTstrlen(options->password)+2;
FUNC_EXIT_RC(len);
return len;
}
/**
* Serializes the connect options into the buffer.
* @param buf the buffer into which the packet will be serialized
* @param len the length in bytes of the supplied buffer
* @param options the options to be used to build the connect packet
* @return serialized length, or error if 0
*/
int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options)
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
MQTTConnectFlags flags = {0};
int len = 0;
int rc = -1;
FUNC_ENTRY;
if (MQTTPacket_len(len = MQTTSerialize_connectLength(options)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = CONNECT;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, len); /* write remaining length */
if (options->MQTTVersion == 4)
{
writeCString(&ptr, "MQTT");
writeChar(&ptr, (char) 4);
}
else
{
writeCString(&ptr, "MQIsdp");
writeChar(&ptr, (char) 3);
}
flags.all = 0;
flags.bits.cleansession = options->cleansession;
flags.bits.will = (options->willFlag) ? 1 : 0;
if (flags.bits.will)
{
flags.bits.willQoS = options->will.qos;
flags.bits.willRetain = options->will.retained;
}
if (options->username.cstring || options->username.lenstring.data)
flags.bits.username = 1;
if (options->password.cstring || options->password.lenstring.data)
flags.bits.password = 1;
writeChar(&ptr, flags.all);
writeInt(&ptr, options->keepAliveInterval);
writeMQTTString(&ptr, options->clientID);
if (options->willFlag)
{
writeMQTTString(&ptr, options->will.topicName);
writeMQTTString(&ptr, options->will.message);
}
if (flags.bits.username)
writeMQTTString(&ptr, options->username);
if (flags.bits.password)
writeMQTTString(&ptr, options->password);
rc = ptr - buf;
exit: FUNC_EXIT_RC(rc);
return rc;
}
/**
* Deserializes the supplied (wire) buffer into connack data - return code
* @param sessionPresent the session present flag returned (only for MQTT 3.1.1)
* @param connack_rc returned integer value of the connack return code
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param len the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen;
MQTTConnackFlags flags = {0};
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != CONNACK)
goto exit;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (enddata - curdata < 2)
goto exit;
flags.all = readChar(&curdata);
*sessionPresent = flags.bits.sessionpresent;
*connack_rc = readChar(&curdata);
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes a 0-length packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @param packettype the message type
* @return serialized length, or error if 0
*/
int MQTTSerialize_zero(unsigned char* buf, int buflen, unsigned char packettype)
{
MQTTHeader header = {0};
int rc = -1;
unsigned char *ptr = buf;
FUNC_ENTRY;
if (buflen < 2)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = packettype;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 0); /* write remaining length */
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes a disconnect packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @return serialized length, or error if 0
*/
int MQTTSerialize_disconnect(unsigned char* buf, int buflen)
{
return MQTTSerialize_zero(buf, buflen, DISCONNECT);
}
/**
* Serializes a disconnect packet into the supplied buffer, ready for writing to a socket
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer, to avoid overruns
* @return serialized length, or error if 0
*/
int MQTTSerialize_pingreq(unsigned char* buf, int buflen)
{
return MQTTSerialize_zero(buf, buflen, PINGREQ);
}

View File

@@ -0,0 +1,148 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "StackTrace.h"
#include "MQTTPacket.h"
#include <string.h>
#define min(a, b) ((a < b) ? a : b)
/**
* Validates MQTT protocol name and version combinations
* @param protocol the MQTT protocol name as an MQTTString
* @param version the MQTT protocol version number, as in the connect packet
* @return correct MQTT combination? 1 is true, 0 is false
*/
int MQTTPacket_checkVersion(MQTTString* protocol, int version)
{
int rc = 0;
if (version == 3 && memcmp(protocol->lenstring.data, "MQIsdp",
min(6, protocol->lenstring.len)) == 0)
rc = 1;
else if (version == 4 && memcmp(protocol->lenstring.data, "MQTT",
min(4, protocol->lenstring.len)) == 0)
rc = 1;
return rc;
}
/**
* Deserializes the supplied (wire) buffer into connect data structure
* @param data the connect data structure to be filled out
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param len the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len)
{
MQTTHeader header = {0};
MQTTConnectFlags flags = {0};
unsigned char* curdata = buf;
unsigned char* enddata = &buf[len];
int rc = 0;
MQTTString Protocol;
int version;
int mylen = 0;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != CONNECT)
goto exit;
curdata += MQTTPacket_decodeBuf(curdata, &mylen); /* read remaining length */
if (!readMQTTLenString(&Protocol, &curdata, enddata) ||
enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */
goto exit;
version = (int)readChar(&curdata); /* Protocol version */
/* If we don't recognize the protocol version, we don't parse the connect packet on the
* basis that we don't know what the format will be.
*/
if (MQTTPacket_checkVersion(&Protocol, version))
{
flags.all = readChar(&curdata);
data->cleansession = flags.bits.cleansession;
data->keepAliveInterval = readInt(&curdata);
if (!readMQTTLenString(&data->clientID, &curdata, enddata))
goto exit;
data->willFlag = flags.bits.will;
if (flags.bits.will)
{
data->will.qos = flags.bits.willQoS;
data->will.retained = flags.bits.willRetain;
if (!readMQTTLenString(&data->will.topicName, &curdata, enddata) ||
!readMQTTLenString(&data->will.message, &curdata, enddata))
goto exit;
}
if (flags.bits.username)
{
if (enddata - curdata < 3 || !readMQTTLenString(&data->username, &curdata, enddata))
goto exit; /* username flag set, but no username supplied - invalid */
if (flags.bits.password &&
(enddata - curdata < 3 || !readMQTTLenString(&data->password, &curdata, enddata)))
goto exit; /* password flag set, but no password supplied - invalid */
}
else if (flags.bits.password)
goto exit; /* password flag set without username - invalid */
rc = 1;
}
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes the connack packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param connack_rc the integer connack return code to be used
* @param sessionPresent the MQTT 3.1.1 sessionPresent flag
* @return serialized length, or error if 0
*/
int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent)
{
MQTTHeader header = {0};
int rc = 0;
unsigned char *ptr = buf;
MQTTConnackFlags flags = {0};
FUNC_ENTRY;
if (buflen < 2)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = CONNACK;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */
flags.all = 0;
flags.bits.sessionpresent = sessionPresent;
writeChar(&ptr, flags.all);
writeChar(&ptr, connack_rc);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,107 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "StackTrace.h"
#include "MQTTPacket.h"
#include <string.h>
#define min(a, b) ((a < b) ? 1 : 0)
/**
* Deserializes the supplied (wire) buffer into publish data
* @param dup returned integer - the MQTT dup flag
* @param qos returned integer - the MQTT QoS value
* @param retained returned integer - the MQTT retained flag
* @param packetid returned integer - the MQTT packet identifier
* @param topicName returned MQTTString - the MQTT topic in the publish
* @param payload returned byte buffer - the MQTT publish payload
* @param payloadlen returned integer - the length of the MQTT payload
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return error code. 1 is success
*/
int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName,
unsigned char** payload, int* payloadlen, unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen = 0;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != PUBLISH)
goto exit;
*dup = header.bits.dup;
*qos = header.bits.qos;
*retained = header.bits.retain;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (!readMQTTLenString(topicName, &curdata, enddata) ||
enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */
goto exit;
if (*qos > 0)
*packetid = readInt(&curdata);
*payloadlen = enddata - curdata;
*payload = curdata;
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Deserializes the supplied (wire) buffer into an ack
* @param packettype returned integer - the MQTT packet type
* @param dup returned integer - the MQTT dup flag
* @param packetid returned integer - the MQTT packet identifier
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen;
FUNC_ENTRY;
header.byte = readChar(&curdata);
*dup = header.bits.dup;
*packettype = header.bits.type;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (enddata - curdata < 2)
goto exit;
*packetid = readInt(&curdata);
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,256 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "StackTrace.h"
#include "MQTTPacket.h"
#include <string.h>
const char* MQTTPacket_names[] =
{
"RESERVED", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL",
"PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK",
"PINGREQ", "PINGRESP", "DISCONNECT"
};
const char* MQTTPacket_getName(unsigned short packetid)
{
return MQTTPacket_names[packetid];
}
int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data)
{
int strindex = 0;
strindex = snprintf(strbuf, strbuflen,
"CONNECT MQTT version %d, client id %.*s, clean session %d, keep alive %d",
(int)data->MQTTVersion, data->clientID.lenstring.len, data->clientID.lenstring.data,
(int)data->cleansession, data->keepAliveInterval);
if (data->willFlag)
strindex += snprintf(&strbuf[strindex], strbuflen - strindex,
", will QoS %d, will retain %d, will topic %.*s, will message %.*s",
data->will.qos, data->will.retained,
data->will.topicName.lenstring.len, data->will.topicName.lenstring.data,
data->will.message.lenstring.len, data->will.message.lenstring.data);
if (data->username.lenstring.data && data->username.lenstring.len > 0)
strindex += snprintf(&strbuf[strindex], strbuflen - strindex,
", user name %.*s", data->username.lenstring.len, data->username.lenstring.data);
if (data->password.lenstring.data && data->password.lenstring.len > 0)
strindex += snprintf(&strbuf[strindex], strbuflen - strindex,
", password %.*s", data->password.lenstring.len, data->password.lenstring.data);
return strindex;
}
int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent)
{
int strindex = snprintf(strbuf, strbuflen, "CONNACK session present %d, rc %d", sessionPresent, connack_rc);
return strindex;
}
int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained,
unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen)
{
int strindex = snprintf(strbuf, strbuflen,
"PUBLISH dup %d, QoS %d, retained %d, packet id %d, topic %.*s, payload length %d, payload %.*s",
dup, qos, retained, packetid,
(topicName.lenstring.len < 20) ? topicName.lenstring.len : 20, topicName.lenstring.data,
payloadlen, (payloadlen < 20) ? payloadlen : 20, payload);
return strindex;
}
int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid)
{
int strindex = snprintf(strbuf, strbuflen, "%s, packet id %d", MQTTPacket_names[packettype], packetid);
if (dup)
strindex += snprintf(strbuf + strindex, strbuflen - strindex, ", dup %d", dup);
return strindex;
}
int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count,
MQTTString topicFilters[], int requestedQoSs[])
{
return snprintf(strbuf, strbuflen,
"SUBSCRIBE dup %d, packet id %d count %d topic %.*s qos %d",
dup, packetid, count,
topicFilters[0].lenstring.len, topicFilters[0].lenstring.data,
requestedQoSs[0]);
}
int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs)
{
return snprintf(strbuf, strbuflen,
"SUBACK packet id %d count %d granted qos %d", packetid, count, grantedQoSs[0]);
}
int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[])
{
return snprintf(strbuf, strbuflen,
"UNSUBSCRIBE dup %d, packet id %d count %d topic %.*s",
dup, packetid, count,
topicFilters[0].lenstring.len, topicFilters[0].lenstring.data);
}
char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen)
{
int index = 0;
int rem_length = 0;
MQTTHeader header = {0};
header.byte = buf[index++];
index += MQTTPacket_decodeBuf(&buf[index], &rem_length);
switch (header.bits.type)
{
case CONNACK:
{
unsigned char sessionPresent, connack_rc;
if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) == 1)
MQTTStringFormat_connack(strbuf, strbuflen, connack_rc, sessionPresent);
}
break;
case PUBLISH:
{
unsigned char dup, retained, *payload;
unsigned short packetid;
int qos, payloadlen;
MQTTString topicName = MQTTString_initializer;
if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName,
&payload, &payloadlen, buf, buflen) == 1)
MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid,
topicName, payload, payloadlen);
}
break;
case PUBACK:
case PUBREC:
case PUBREL:
case PUBCOMP:
{
unsigned char packettype, dup;
unsigned short packetid;
if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1)
MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid);
}
break;
case SUBACK:
{
unsigned short packetid;
int maxcount = 1, count = 0;
int grantedQoSs[1];
if (MQTTDeserialize_suback(&packetid, maxcount, &count, grantedQoSs, buf, buflen) == 1)
MQTTStringFormat_suback(strbuf, strbuflen, packetid, count, grantedQoSs);
}
break;
case UNSUBACK:
{
unsigned short packetid;
if (MQTTDeserialize_unsuback(&packetid, buf, buflen) == 1)
MQTTStringFormat_ack(strbuf, strbuflen, UNSUBACK, 0, packetid);
}
break;
case PINGREQ:
case PINGRESP:
case DISCONNECT:
snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]);
break;
}
return strbuf;
}
char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen)
{
int index = 0;
int rem_length = 0;
MQTTHeader header = {0};
header.byte = buf[index++];
index += MQTTPacket_decodeBuf(&buf[index], &rem_length);
switch (header.bits.type)
{
case CONNECT:
{
MQTTPacket_connectData data;
int rc;
if ((rc = MQTTDeserialize_connect(&data, buf, buflen)) == 1)
MQTTStringFormat_connect(strbuf, strbuflen, &data);
}
break;
case PUBLISH:
{
unsigned char dup, retained, *payload;
unsigned short packetid;
int qos, payloadlen;
MQTTString topicName = MQTTString_initializer;
if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName,
&payload, &payloadlen, buf, buflen) == 1)
MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid,
topicName, payload, payloadlen);
}
break;
case PUBACK:
case PUBREC:
case PUBREL:
case PUBCOMP:
{
unsigned char packettype, dup;
unsigned short packetid;
if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1)
MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid);
}
break;
case SUBSCRIBE:
{
unsigned char dup;
unsigned short packetid;
int maxcount = 1, count = 0;
MQTTString topicFilters[1];
int requestedQoSs[1];
if (MQTTDeserialize_subscribe(&dup, &packetid, maxcount, &count,
topicFilters, requestedQoSs, buf, buflen) == 1)
MQTTStringFormat_subscribe(strbuf, strbuflen, dup, packetid, count, topicFilters, requestedQoSs);;
}
break;
case UNSUBSCRIBE:
{
unsigned char dup;
unsigned short packetid;
int maxcount = 1, count = 0;
MQTTString topicFilters[1];
if (MQTTDeserialize_unsubscribe(&dup, &packetid, maxcount, &count, topicFilters, buf, buflen) == 1)
MQTTStringFormat_unsubscribe(strbuf, strbuflen, dup, packetid, count, topicFilters);
}
break;
case PINGREQ:
case PINGRESP:
case DISCONNECT:
snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]);
break;
}
strbuf[strbuflen] = '\0';
return strbuf;
}

View File

@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTFORMAT_H)
#define MQTTFORMAT_H
#include "StackTrace.h"
#include "MQTTPacket.h"
const char* MQTTPacket_getName(unsigned short packetid);
int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data);
int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent);
int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained,
unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen);
int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid);
int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count,
MQTTString topicFilters[], int requestedQoSs[]);
int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs);
int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[]);
char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen);
char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen);
#endif

View File

@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/*******************************************************************************
* Modified for Arduino environment by Temboo
* Joe Planisky - replaced fancy fprintf stuff with Serial.print(ln)
*******************************************************************************/
#if !defined(MQTT_LOGGING_H)
#define MQTT_LOGGING_H
#if !defined(DEBUG)
#define DEBUG(msg) {Serial.print("DEBUG: "); Serial.println(msg);}
#endif
#if !defined(LOG)
#define LOG(msg) {Serial.print("LOG: "); Serial.println(msg);}
#endif
#if !defined(WARN)
#define WARN(msg) {Serial.print("WARN: "); Serial.println(msg);}
#endif
#if !defined(ERROR)
#define ERROR() {Serial.print("ERROR: "); Serial.println(msg);}
#endif
#endif

View File

@@ -0,0 +1,410 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Sergio R. Caprile - non-blocking packet read functions for stream transport
*******************************************************************************/
#include "StackTrace.h"
#include "MQTTPacket.h"
#include <string.h>
/**
* Encodes the message length according to the MQTT algorithm
* @param buf the buffer into which the encoded data is written
* @param length the length to be encoded
* @return the number of bytes written to buffer
*/
int MQTTPacket_encode(unsigned char* buf, int length)
{
int rc = 0;
FUNC_ENTRY;
do
{
char d = length % 128;
length /= 128;
/* if there are more digits to encode, set the top bit of this digit */
if (length > 0)
d |= 0x80;
buf[rc++] = d;
} while (length > 0);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Decodes the message length according to the MQTT algorithm
* @param getcharfn pointer to function to read the next character from the data source
* @param value the decoded length returned
* @return the number of bytes read from the socket
*/
int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value)
{
unsigned char c;
int multiplier = 1;
int len = 0;
#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4
FUNC_ENTRY;
*value = 0;
do
{
int rc = MQTTPACKET_READ_ERROR;
if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES)
{
rc = MQTTPACKET_READ_ERROR; /* bad data */
goto exit;
}
rc = (*getcharfn)(&c, 1);
if (rc != 1)
goto exit;
*value += (c & 127) * multiplier;
multiplier *= 128;
} while ((c & 128) != 0);
exit:
FUNC_EXIT_RC(len);
return len;
}
int MQTTPacket_len(int rem_len)
{
rem_len += 1; /* header byte */
/* now remaining_length field */
if (rem_len < 128)
rem_len += 1;
else if (rem_len < 16384)
rem_len += 2;
else if (rem_len < 2097151)
rem_len += 3;
else
rem_len += 4;
return rem_len;
}
static unsigned char* bufptr;
int bufchar(unsigned char* c, int count)
{
int i;
for (i = 0; i < count; ++i)
*c = *bufptr++;
return count;
}
int MQTTPacket_decodeBuf(unsigned char* buf, int* value)
{
bufptr = buf;
return MQTTPacket_decode(bufchar, value);
}
/**
* Calculates an integer from two bytes read from the input buffer
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @return the integer value calculated
*/
int readInt(unsigned char** pptr)
{
unsigned char* ptr = *pptr;
int len = 256*(*ptr) + (*(ptr+1));
*pptr += 2;
return len;
}
/**
* Reads one character from the input buffer.
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @return the character read
*/
char readChar(unsigned char** pptr)
{
char c = **pptr;
(*pptr)++;
return c;
}
/**
* Writes one character to an output buffer.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param c the character to write
*/
void writeChar(unsigned char** pptr, char c)
{
**pptr = c;
(*pptr)++;
}
/**
* Writes an integer as 2 bytes to an output buffer.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param anInt the integer to write
*/
void writeInt(unsigned char** pptr, int anInt)
{
**pptr = (unsigned char)(anInt / 256);
(*pptr)++;
**pptr = (unsigned char)(anInt % 256);
(*pptr)++;
}
/**
* Writes a "UTF" string to an output buffer. Converts C string to length-delimited.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param string the C string to write
*/
void writeCString(unsigned char** pptr, const char* string)
{
int len = strlen(string);
writeInt(pptr, len);
memcpy(*pptr, string, len);
*pptr += len;
}
int getLenStringLen(char* ptr)
{
int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1));
return len;
}
void writeMQTTString(unsigned char** pptr, MQTTString mqttstring)
{
if (mqttstring.lenstring.len > 0)
{
writeInt(pptr, mqttstring.lenstring.len);
memcpy(*pptr, mqttstring.lenstring.data, mqttstring.lenstring.len);
*pptr += mqttstring.lenstring.len;
}
else if (mqttstring.cstring)
writeCString(pptr, mqttstring.cstring);
else
writeInt(pptr, 0);
}
/**
* @param mqttstring the MQTTString structure into which the data is to be read
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param enddata pointer to the end of the data: do not read beyond
* @return 1 if successful, 0 if not
*/
int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata)
{
int rc = 0;
FUNC_ENTRY;
/* the first two bytes are the length of the string */
if (enddata - (*pptr) > 1) /* enough length to read the integer? */
{
mqttstring->lenstring.len = readInt(pptr); /* increments pptr to point past length */
if (&(*pptr)[mqttstring->lenstring.len] <= enddata)
{
mqttstring->lenstring.data = (char*)*pptr;
*pptr += mqttstring->lenstring.len;
rc = 1;
}
}
mqttstring->cstring = NULL;
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Return the length of the MQTTstring - C string if there is one, otherwise the length delimited string
* @param mqttstring the string to return the length of
* @return the length of the string
*/
int MQTTstrlen(MQTTString mqttstring)
{
int rc = 0;
if (mqttstring.cstring)
rc = strlen(mqttstring.cstring);
else
rc = mqttstring.lenstring.len;
return rc;
}
/**
* Compares an MQTTString to a C string
* @param a the MQTTString to compare
* @param bptr the C string to compare
* @return boolean - equal or not
*/
int MQTTPacket_equals(MQTTString* a, char* bptr)
{
int alen = 0,
blen = 0;
char *aptr;
if (a->cstring)
{
aptr = a->cstring;
alen = strlen(a->cstring);
}
else
{
aptr = a->lenstring.data;
alen = a->lenstring.len;
}
blen = strlen(bptr);
return (alen == blen) && (strncmp(aptr, bptr, alen) == 0);
}
/**
* Helper function to read packet data from some source into a buffer
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param getfn pointer to a function which will read any number of bytes from the needed source
* @return integer MQTT packet type, or -1 on error
* @note the whole message must fit into the caller's buffer
*/
int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int))
{
int rc = -1;
MQTTHeader header = {0};
int len = 0;
int rem_len = 0;
/* 1. read the header byte. This has the packet type in it */
if ((*getfn)(buf, 1) != 1)
goto exit;
len = 1;
/* 2. read the remaining length. This is variable in itself */
MQTTPacket_decode(getfn, &rem_len);
len += MQTTPacket_encode(buf + 1, rem_len); /* put the original remaining length back into the buffer */
/* 3. read the rest of the buffer using a callback to supply the rest of the data */
if((rem_len + len) > buflen)
goto exit;
if ((*getfn)(buf + len, rem_len) != rem_len)
goto exit;
header.byte = buf[0];
rc = header.bits.type;
exit:
return rc;
}
/**
* Decodes the message length according to the MQTT algorithm, non-blocking
* @param trp pointer to a transport structure holding what is needed to solve getting data from it
* @param value the decoded length returned
* @return integer the number of bytes read from the socket, 0 for call again, or -1 on error
*/
static int MQTTPacket_decodenb(MQTTTransport *trp)
{
unsigned char c;
int rc = MQTTPACKET_READ_ERROR;
FUNC_ENTRY;
if(trp->len == 0){ /* initialize on first call */
trp->multiplier = 1;
trp->rem_len = 0;
}
do {
int frc;
if (++(trp->len) > MAX_NO_OF_REMAINING_LENGTH_BYTES)
goto exit;
if ((frc=(*trp->getfn)(trp->sck, &c, 1)) == -1)
goto exit;
if (frc == 0){
rc = 0;
goto exit;
}
trp->rem_len += (c & 127) * trp->multiplier;
trp->multiplier *= 128;
} while ((c & 128) != 0);
rc = trp->len;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Helper function to read packet data from some source into a buffer, non-blocking
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param trp pointer to a transport structure holding what is needed to solve getting data from it
* @return integer MQTT packet type, 0 for call again, or -1 on error
* @note the whole message must fit into the caller's buffer
*/
int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp)
{
int rc = -1, frc;
MQTTHeader header = {0};
switch(trp->state){
default:
trp->state = 0;
/*FALLTHROUGH*/
case 0:
/* read the header byte. This has the packet type in it */
if ((frc=(*trp->getfn)(trp->sck, buf, 1)) == -1)
goto exit;
if (frc == 0)
return 0;
trp->len = 0;
++trp->state;
/*FALLTHROUGH*/
/* read the remaining length. This is variable in itself */
case 1:
if((frc=MQTTPacket_decodenb(trp)) == MQTTPACKET_READ_ERROR)
goto exit;
if(frc == 0)
return 0;
trp->len = 1 + MQTTPacket_encode(buf + 1, trp->rem_len); /* put the original remaining length back into the buffer */
if((trp->rem_len + trp->len) > buflen)
goto exit;
++trp->state;
/*FALLTHROUGH*/
case 2:
/* read the rest of the buffer using a callback to supply the rest of the data */
if ((frc=(*trp->getfn)(trp->sck, buf + trp->len, trp->rem_len)) == -1)
goto exit;
if (frc == 0)
return 0;
trp->rem_len -= frc;
trp->len += frc;
if(trp->rem_len)
return 0;
header.byte = buf[0];
rc = header.bits.type;
break;
}
exit:
trp->state = 0;
return rc;
}

View File

@@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Xiang Rong - 442039 Add makefile to Embedded C client
*******************************************************************************/
#ifndef MQTTPACKET_H_
#define MQTTPACKET_H_
#if defined(__cplusplus) /* If this is a C++ compiler, use C linkage */
extern "C" {
#endif
#if defined(WIN32_DLL) || defined(WIN64_DLL)
#define DLLImport __declspec(dllimport)
#define DLLExport __declspec(dllexport)
#elif defined(LINUX_SO)
#define DLLImport extern
#define DLLExport __attribute__ ((visibility ("default")))
#else
#define DLLImport
#define DLLExport
#endif
enum errors
{
MQTTPACKET_BUFFER_TOO_SHORT = -2,
MQTTPACKET_READ_ERROR = -1,
MQTTPACKET_READ_COMPLETE
};
enum msgTypes
{
CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL,
PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK,
PINGREQ, PINGRESP, DISCONNECT
};
/**
* Bitfields for the MQTT header byte.
*/
typedef union
{
unsigned char byte; /**< the whole byte */
#if defined(REVERSED)
struct
{
unsigned int type : 4; /**< message type nibble */
unsigned int dup : 1; /**< DUP flag bit */
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
unsigned int retain : 1; /**< retained flag bit */
} bits;
#else
struct
{
unsigned int retain : 1; /**< retained flag bit */
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
unsigned int dup : 1; /**< DUP flag bit */
unsigned int type : 4; /**< message type nibble */
} bits;
#endif
} MQTTHeader;
typedef struct
{
int len;
char* data;
} MQTTLenString;
typedef struct
{
char* cstring;
MQTTLenString lenstring;
} MQTTString;
#define MQTTString_initializer {NULL, {0, NULL}}
int MQTTstrlen(MQTTString mqttstring);
#include "MQTTConnect.h"
#include "MQTTPublish.h"
#include "MQTTSubscribe.h"
#include "MQTTUnsubscribe.h"
#include "MQTTFormat.h"
int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char type, unsigned char dup, unsigned short packetid);
int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen);
int MQTTPacket_len(int rem_len);
int MQTTPacket_equals(MQTTString* a, char* b);
int MQTTPacket_encode(unsigned char* buf, int length);
int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value);
int MQTTPacket_decodeBuf(unsigned char* buf, int* value);
int readInt(unsigned char** pptr);
char readChar(unsigned char** pptr);
void writeChar(unsigned char** pptr, char c);
void writeInt(unsigned char** pptr, int anInt);
int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata);
void writeCString(unsigned char** pptr, const char* string);
void writeMQTTString(unsigned char** pptr, MQTTString mqttstring);
DLLExport int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int));
typedef struct {
int (*getfn)(void *, unsigned char*, int); /* must return -1 for error, 0 for call again, or the number of bytes read */
void *sck; /* pointer to whatever the system may use to identify the transport */
int multiplier;
int rem_len;
int len;
char state;
}MQTTTransport;
int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp);
#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */
}
#endif
#endif /* MQTTPACKET_H_ */

View File

@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Xiang Rong - 442039 Add makefile to Embedded C client
*******************************************************************************/
#ifndef MQTTPUBLISH_H_
#define MQTTPUBLISH_H_
#if !defined(DLLImport)
#define DLLImport
#endif
#if !defined(DLLExport)
#define DLLExport
#endif
DLLExport int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid,
MQTTString topicName, unsigned char* payload, int payloadlen);
DLLExport int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName,
unsigned char** payload, int* payloadlen, unsigned char* buf, int len);
DLLExport int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid);
DLLExport int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid);
DLLExport int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid);
#endif /* MQTTPUBLISH_H_ */

View File

@@ -0,0 +1,168 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Determines the length of the MQTT publish packet that would be produced using the supplied parameters
* @param qos the MQTT QoS of the publish (packetid is omitted for QoS 0)
* @param topicName the topic name to be used in the publish
* @param payloadlen the length of the payload to be sent
* @return the length of buffer needed to contain the serialized version of the packet
*/
int MQTTSerialize_publishLength(int qos, MQTTString topicName, int payloadlen)
{
int len = 0;
len += 2 + MQTTstrlen(topicName) + payloadlen;
if (qos > 0)
len += 2; /* packetid */
return len;
}
/**
* Serializes the supplied publish data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param dup integer - the MQTT dup flag
* @param qos integer - the MQTT QoS value
* @param retained integer - the MQTT retained flag
* @param packetid integer - the MQTT packet identifier
* @param topicName MQTTString - the MQTT topic in the publish
* @param payload byte buffer - the MQTT publish payload
* @param payloadlen integer - the length of the MQTT payload
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid,
MQTTString topicName, unsigned char* payload, int payloadlen)
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_publishLength(qos, topicName, payloadlen)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.bits.type = PUBLISH;
header.bits.dup = dup;
header.bits.qos = qos;
header.bits.retain = retained;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeMQTTString(&ptr, topicName);
if (qos > 0)
writeInt(&ptr, packetid);
memcpy(ptr, payload, payloadlen);
ptr += payloadlen;
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes the ack packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param type the MQTT packet type
* @param dup the MQTT dup flag
* @param packetid the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char packettype, unsigned char dup, unsigned short packetid)
{
MQTTHeader header = {0};
int rc = 0;
unsigned char *ptr = buf;
FUNC_ENTRY;
if (buflen < 4)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.bits.type = packettype;
header.bits.dup = dup;
header.bits.qos = 0;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */
writeInt(&ptr, packetid);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes a puback packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBACK, packetid, 0);
}
/**
* Serializes a pubrel packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBREL, packetid, dup);
}
/**
* Serializes a pubrel packet into the supplied buffer.
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @return serialized length, or error if 0
*/
int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid)
{
return MQTTSerialize_ack(buf, buflen, PUBCOMP, packetid, 0);
}

View File

@@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Xiang Rong - 442039 Add makefile to Embedded C client
*******************************************************************************/
#ifndef MQTTSUBSCRIBE_H_
#define MQTTSUBSCRIBE_H_
#if !defined(DLLImport)
#define DLLImport
#endif
#if !defined(DLLExport)
#define DLLExport
#endif
DLLExport int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[], int requestedQoSs[]);
DLLExport int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid,
int maxcount, int* count, MQTTString topicFilters[], int requestedQoSs[], unsigned char* buf, int len);
DLLExport int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs);
DLLExport int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int len);
#endif /* MQTTSUBSCRIBE_H_ */

View File

@@ -0,0 +1,137 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Determines the length of the MQTT subscribe packet that would be produced using the supplied parameters
* @param count the number of topic filter strings in topicFilters
* @param topicFilters the array of topic filter strings to be used in the publish
* @return the length of buffer needed to contain the serialized version of the packet
*/
int MQTTSerialize_subscribeLength(int count, MQTTString topicFilters[])
{
int i;
int len = 2; /* packetid */
for (i = 0; i < count; ++i)
len += 2 + MQTTstrlen(topicFilters[i]) + 1; /* length + topic + req_qos */
return len;
}
/**
* Serializes the supplied subscribe data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied bufferr
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @param count - number of members in the topicFilters and reqQos arrays
* @param topicFilters - array of topic filter names
* @param requestedQoSs - array of requested QoS
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, int count,
MQTTString topicFilters[], int requestedQoSs[])
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = 0;
int i = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_subscribeLength(count, topicFilters)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = SUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeInt(&ptr, packetid);
for (i = 0; i < count; ++i)
{
writeMQTTString(&ptr, topicFilters[i]);
writeChar(&ptr, requestedQoSs[i]);
}
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Deserializes the supplied (wire) buffer into suback data
* @param packetid returned integer - the MQTT packet identifier
* @param maxcount - the maximum number of members allowed in the grantedQoSs array
* @param count returned integer - number of members in the grantedQoSs array
* @param grantedQoSs returned array of integers - the granted qualities of service
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int* count, int grantedQoSs[], unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != SUBACK)
goto exit;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
if (enddata - curdata < 2)
goto exit;
*packetid = readInt(&curdata);
*count = 0;
while (curdata < enddata)
{
if (*count > maxcount)
{
rc = -1;
goto exit;
}
grantedQoSs[(*count)++] = readChar(&curdata);
}
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,112 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Deserializes the supplied (wire) buffer into subscribe data
* @param dup integer returned - the MQTT dup flag
* @param packetid integer returned - the MQTT packet identifier
* @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays
* @param count - number of members in the topicFilters and requestedQoSs arrays
* @param topicFilters - array of topic filter names
* @param requestedQoSs - array of requested QoS
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[],
int requestedQoSs[], unsigned char* buf, int buflen)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = -1;
int mylen = 0;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != SUBSCRIBE)
goto exit;
*dup = header.bits.dup;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
*packetid = readInt(&curdata);
*count = 0;
while (curdata < enddata)
{
if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata))
goto exit;
if (curdata >= enddata) /* do we have enough data to read the req_qos version byte? */
goto exit;
requestedQoSs[*count] = readChar(&curdata);
(*count)++;
}
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes the supplied suback data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @param count - number of members in the grantedQoSs array
* @param grantedQoSs - array of granted QoS
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs)
{
MQTTHeader header = {0};
int rc = -1;
unsigned char *ptr = buf;
int i;
FUNC_ENTRY;
if (buflen < 2 + count)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = SUBACK;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 2 + count); /* write remaining length */
writeInt(&ptr, packetid);
for (i = 0; i < count; ++i)
writeChar(&ptr, grantedQoSs[i]);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Xiang Rong - 442039 Add makefile to Embedded C client
*******************************************************************************/
#ifndef MQTTUNSUBSCRIBE_H_
#define MQTTUNSUBSCRIBE_H_
#if !defined(DLLImport)
#define DLLImport
#endif
#if !defined(DLLExport)
#define DLLExport
#endif
DLLExport int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[]);
DLLExport int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int max_count, int* count, MQTTString topicFilters[],
unsigned char* buf, int len);
DLLExport int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid);
DLLExport int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int len);
#endif /* MQTTUNSUBSCRIBE_H_ */

View File

@@ -0,0 +1,106 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Determines the length of the MQTT unsubscribe packet that would be produced using the supplied parameters
* @param count the number of topic filter strings in topicFilters
* @param topicFilters the array of topic filter strings to be used in the publish
* @return the length of buffer needed to contain the serialized version of the packet
*/
int MQTTSerialize_unsubscribeLength(int count, MQTTString topicFilters[])
{
int i;
int len = 2; /* packetid */
for (i = 0; i < count; ++i)
len += 2 + MQTTstrlen(topicFilters[i]); /* length + topic*/
return len;
}
/**
* Serializes the supplied unsubscribe data into the supplied buffer, ready for sending
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @param dup integer - the MQTT dup flag
* @param packetid integer - the MQTT packet identifier
* @param count - number of members in the topicFilters array
* @param topicFilters - array of topic filter names
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid,
int count, MQTTString topicFilters[])
{
unsigned char *ptr = buf;
MQTTHeader header = {0};
int rem_len = 0;
int rc = -1;
int i = 0;
FUNC_ENTRY;
if (MQTTPacket_len(rem_len = MQTTSerialize_unsubscribeLength(count, topicFilters)) > buflen)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = UNSUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */;
writeInt(&ptr, packetid);
for (i = 0; i < count; ++i)
writeMQTTString(&ptr, topicFilters[i]);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Deserializes the supplied (wire) buffer into unsuback data
* @param packetid returned integer - the MQTT packet identifier
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return error code. 1 is success, 0 is failure
*/
int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int buflen)
{
unsigned char type = 0;
unsigned char dup = 0;
int rc = 0;
FUNC_ENTRY;
rc = MQTTDeserialize_ack(&type, &dup, packetid, buf, buflen);
if (type == UNSUBACK)
rc = 1;
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "MQTTPacket.h"
#include "StackTrace.h"
#include <string.h>
/**
* Deserializes the supplied (wire) buffer into unsubscribe data
* @param dup integer returned - the MQTT dup flag
* @param packetid integer returned - the MQTT packet identifier
* @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays
* @param count - number of members in the topicFilters and requestedQoSs arrays
* @param topicFilters - array of topic filter names
* @param buf the raw buffer data, of the correct length determined by the remaining length field
* @param buflen the length in bytes of the data in the supplied buffer
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[],
unsigned char* buf, int len)
{
MQTTHeader header = {0};
unsigned char* curdata = buf;
unsigned char* enddata = NULL;
int rc = 0;
int mylen = 0;
FUNC_ENTRY;
header.byte = readChar(&curdata);
if (header.bits.type != UNSUBSCRIBE)
goto exit;
*dup = header.bits.dup;
curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */
enddata = curdata + mylen;
*packetid = readInt(&curdata);
*count = 0;
while (curdata < enddata)
{
if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata))
goto exit;
(*count)++;
}
rc = 1;
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Serializes the supplied unsuback data into the supplied buffer, ready for sending
* @param buf the buffer into which the packet will be serialized
* @param buflen the length in bytes of the supplied buffer
* @param packetid integer - the MQTT packet identifier
* @return the length of the serialized data. <= 0 indicates error
*/
int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid)
{
MQTTHeader header = {0};
int rc = 0;
unsigned char *ptr = buf;
FUNC_ENTRY;
if (buflen < 2)
{
rc = MQTTPACKET_BUFFER_TOO_SHORT;
goto exit;
}
header.byte = 0;
header.bits.type = UNSUBACK;
writeChar(&ptr, header.byte); /* write header */
ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */
writeInt(&ptr, packetid);
rc = ptr - buf;
exit:
FUNC_EXIT_RC(rc);
return rc;
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2014 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
* Ian Craggs - fix for bug #434081
*******************************************************************************/
#ifndef STACKTRACE_H_
#define STACKTRACE_H_
#include <stdio.h>
#define NOSTACKTRACE 1
#if defined(NOSTACKTRACE)
#define FUNC_ENTRY
#define FUNC_ENTRY_NOLOG
#define FUNC_ENTRY_MED
#define FUNC_ENTRY_MAX
#define FUNC_EXIT
#define FUNC_EXIT_NOLOG
#define FUNC_EXIT_MED
#define FUNC_EXIT_MAX
#define FUNC_EXIT_RC(x)
#define FUNC_EXIT_MED_RC(x)
#define FUNC_EXIT_MAX_RC(x)
#else
#if defined(WIN32)
#define inline __inline
#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM)
#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1)
#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM)
#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM)
#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM)
#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, -1)
#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM)
#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM)
#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM)
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM)
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM)
#else
#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM)
#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1)
#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM)
#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM)
#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM)
#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1)
#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM)
#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM)
#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM)
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM)
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM)
void StackTrace_entry(const char* name, int line, int trace);
void StackTrace_exit(const char* name, int line, void* return_value, int trace);
void StackTrace_printStack(FILE* dest);
char* StackTrace_get(unsigned long);
#endif
#endif
#endif /* STACKTRACE_H_ */

View File

@@ -0,0 +1,101 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOCOAPIPSTACK_H_
#define TEMBOOCOAPIPSTACK_H_
#include <string.h>
#include <Udp.h>
#include "TembooGlobal.h"
class TembooCoAPIPStack
{
public:
enum Error {
SUCCESS = 0,
ERROR_RESOLVING,
ERROR_WRITING,
ERROR_SENDING,
ERROR_RECEIVING
};
TembooCoAPIPStack(UDP& udp) : m_udp(udp) {}
int sendDatagram(IPAddress address, uint16_t port, uint8_t* data, size_t len) {
if (0 == m_udp.beginPacket(address, port)) {
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("UDP begin");
return ERROR_RESOLVING;
}
uint16_t c = m_udp.write(data, len);
if (len != c) {
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("UDP write");
Serial.println(len);
Serial.println(c);
Serial.println((char*)data);
return ERROR_WRITING;
}
if (0 == m_udp.endPacket()) {
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("UDP send");
return ERROR_SENDING;
}
return SUCCESS;
}
int recvDatagram(uint8_t* buffer, size_t maxLen, int32_t& count) {
memset(buffer, 0, maxLen);
count = 0;
if (m_udp.parsePacket() > 0) {
count = m_udp.read(buffer, maxLen);
if (count < 0) {
TEMBOO_TRACE("ERROR: ");
TEMBOO_TRACELN("UDP read");
return ERROR_RECEIVING;
}
}
return SUCCESS;
}
IPAddress getRemoteAddress() {
return m_udp.remoteIP();
}
uint16_t getRemotePort() {
return m_udp.remotePort();
}
protected:
UDP& m_udp;
};
#endif

View File

@@ -0,0 +1,203 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "TembooCoAPSession.h"
#include "tmbhmac.h"
#include "DataFormatter.h"
#include "TembooTags.h"
#define QSEND(x) if(!qsend((x))) { goto SendFailed;}
const size_t MAX_HOST_LEN = 64;
const size_t MAX_URL_PATH_LEN = 100;
const char EOL[] = "\r\n";
const char POST[] = "POST ";
const char POSTAMBLE[] = " HTTP/1.0"; // Prevent host from using chunked encoding in response.
const char HEADER_HOST[] = "Host: ";
const char HEADER_ACCEPT[] = "Accept: application/xml";
const char HEADER_ORG[] = "x-temboo-domain: /";
const char HEADER_DOM[] = "/master";
const char HEADER_CONTENT_LENGTH[] = "Content-Length: ";
const char HEADER_TIME[] = "x-temboo-time: ";
const char BASE_CHOREO_URI[] = "/arcturus-web/api-1.0/ar";
const char HEADER_AUTH[] = "x-temboo-authentication: ";
const char HEADER_CONTENT_TYPE[] = "Content-Type: text/plain";
const char TEMBOO_DOMAIN[] = ".temboolive.com";
const char SDK_ID[] = "?source_id=CoAPGateway_1";
unsigned long TembooCoAPSession::s_timeOffset = 0;
TembooCoAPSession::TembooCoAPSession(TembooCoAPClient& client) : m_client(client) {
}
void TembooCoAPSession::setTime(unsigned long currentTime) {
s_timeOffset = currentTime - (millis()/1000);
}
unsigned long TembooCoAPSession::getTime() {
return s_timeOffset + (millis()/1000);
}
int TembooCoAPSession::executeChoreo(
uint16_t requestId,
const char* accountName,
const char* appKeyName,
const char* appKeyValue,
const char* path,
const ChoreoInputSet& inputSet,
const ChoreoInputExpressionSet& expressionSet,
const ChoreoSensorInputSet& sensorSet,
const ChoreoOutputSet& outputSet,
const ChoreoPreset& preset,
const ChoreoDevice& deviceType,
const ChoreoDevice& deviceName){
DataFormatter fmt(&inputSet, &expressionSet, &sensorSet,&outputSet, &preset, &deviceType, &deviceName);
char auth[HMAC_HEX_SIZE_BYTES + 1];
char timeStr[11];
char requestIdStr[5];
uint16toa(requestId, requestIdStr);
// We use the current time-of-day as salt on the app key.
// We keep track of time-of-day by getting the current time
// from the server and applying an offset (the length of time
// we've been running.)
uint32toa((uint32_t)TembooCoAPSession::getTime(), timeStr);
getAuth(fmt, appKeyValue, timeStr, auth);
m_client.clearData();
QSEND(TAG_REQUEST_ID);
QSEND(requestIdStr);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_ACCOUNT_NAME);
QSEND(accountName);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_APP_KEY_NAME);
QSEND(appKeyName);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_TIME);
QSEND(timeStr);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_AUTH);
QSEND(auth);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_CHOREO_ID);
QSEND(path);
QSEND(TAG_ELEMENT_SEPARATOR);
QSEND(TAG_DATA);
fmt.reset();
while(fmt.hasNext()) {
QSEND(fmt.next());
}
QSEND(TAG_END_REQUEST);
return m_client.sendChoreoRequest();
SendFailed:
TEMBOO_TRACELN("FAIL");
return FAILURE;
}
uint16_t TembooCoAPSession::getAuth(DataFormatter& fmt, const char* appKeyValue, const char* salt, char* result) const {
// We need the length of the data for other things, and
// this method is a convenient place to calculate it.
uint16_t len = 0;
HMAC hmac;
//combine the salt and the key and give it to the HMAC calculator
size_t keyLength = strlen(appKeyValue) + strlen(salt);
char key[keyLength + 1];
strcpy(key, salt);
strcat(key, appKeyValue);
hmac.init((uint8_t*)key, keyLength);
// process the data a block at a time.
uint8_t buffer[HMAC_BLOCK_SIZE_BYTES];
int blockCount = 0;
fmt.reset();
while(fmt.hasNext()) {
uint8_t c = fmt.next();
len++;
buffer[blockCount++] = c;
if (blockCount == HMAC_BLOCK_SIZE_BYTES) {
hmac.process(buffer, blockCount);
blockCount = 0;
}
}
hmac.process(buffer, blockCount);
// Finalize the HMAC calculation and store the (ASCII HEX) value in *result.
hmac.finishHex(result);
// Return the number of characters processed.
return len;
}
bool TembooCoAPSession::qsend(const char* s) {
char c = *s++;
bool rc = true;
while(c != '\0' && rc) {
rc = qsend(c);
c = *s++;
}
return rc;
}
bool TembooCoAPSession::qsend(char c) {
// Never send a nul character.
if ('\0' != c) {
if (TembooCoAPClient::NO_ERROR == m_client.write(c)) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,115 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library
#
# Copyright (C) 2017, Temboo Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
#
###############################################################################
*/
#ifndef TEMBOOCOAPSESSIONCLASS_H_
#define TEMBOOCOAPSESSIONCLASS_H_
#include <stdint.h>
#include <Arduino.h>
#include "TembooCoAPEdgeDevice.h"
#include "TembooGlobal.h"
#ifndef TEMBOO_SEND_QUEUE_SIZE
#define TEMBOO_SEND_QUEUE_SIZE (1000)
#endif
class ChoreoInputSet;
class ChoreoOutputSet;
class ChoreoPreset;
class DataFormatter;
class TembooCoAPSession {
public:
//TembooSession constructor
//client: REQUIRED TembooCoAPClient client object.
TembooCoAPSession(TembooCoAPClient& client);
//executeChoreo sends a choreo execution request to the Temboo system.
// Does not wait for a response (that's a job for whoever owns the Client.)
//accountName: the name of the user's account at Temboo.
//appKeyName: the name of an application key in the user's account to use
// for this execution (analogous to a user name).
//appKeyValue: the value of the application key named in appKeyName.
// Used to authenticate the user (analogous to a password)
//path: The full path to the choreo to be executed (relative to the root of the
// user's account.)
//inputSet: the set of inputs needed by the choreo.
// May be an empty ChoreoInputSet.
//outputSet: the set of output filters to be applied to the choreo results.
// May be an empty ChoreoOutputSet
//preset: the ChoreoPreset to be used with the choreo execution.
// May be an empty ChoreoPreset.
int executeChoreo(
uint16_t requestId,
const char* accountName,
const char* appKeyName,
const char* appKeyValue,
const char* path,
const ChoreoInputSet& inputSet,
const ChoreoInputExpressionSet& expressionSet,
const ChoreoSensorInputSet& sensorSet,
const ChoreoOutputSet& outputSet,
const ChoreoPreset& preset,
const ChoreoDevice& deviceType,
const ChoreoDevice& deviceName);
// setTime sets the current time in Unix timestamp format. Needed for execution request authentication.
// NOTE: This method is usually called by TembooChoreo.run() with the current time returned by
// an error response from the Temboo system, thus automatically setting the time. However, it
// MAY be called from user code if the particular board has a way of determining the current
// time in the proper format.
// currentTime: the number of seconds since 1970-01-01 00:00:00 UTC.
static void setTime(unsigned long currentTime);
//getTime returns the current time in Unix timestamp format (seconds since 1970-01-01 00:00:00 UTC).
// Only valid after setTime has been called.
static unsigned long getTime();
enum Error {
SUCCESS = 0,
FAILURE
};
private:
TembooCoAPClient& m_client;
static unsigned long s_timeOffset;
// calculate the authentication code value of the formatted request body
// using the salted application key value as the key.
// Returns the number of characters processed (i.e. the length of the request body)
uint16_t getAuth(DataFormatter& fmt, const char* appKeyValue, const char* salt, char* hexAuth) const;
// queue an entire nul-terminated char array
// from RAM one byte at a time.
// returns true on success, false on failure
bool qsend(const char*);
// queue a single character to be sent.
// returns true on success, false on failure
bool qsend(char);
};
#endif

Some files were not shown because too many files have changed in this diff Show More