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,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

View File

@@ -0,0 +1,100 @@
/*
###############################################################################
#
# 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 "TembooOneWire.h"
#include "TembooDS18B20.h"
TembooOneWire* g_wire;
int tembooDS18B20Read(void* sensorConfig) {
uint8_t scratchPad[9];
TembooDS18B20Config* config = (TembooDS18B20Config*) sensorConfig;
g_wire->reset();
g_wire->select(config->channel);
// tells device to convert temperature to scratchpad to read
g_wire->write(0x44, config->parasite);
// delay 750ms for conversion to occur, but wait for 1 second just in case
delay(config->conversionDelayMillis);
// begin reading data
g_wire->reset();
// select the device we're requesting data from
g_wire->select(config->channel);
// send message to device requesting data
g_wire->write(0xBE);
for(int i = 0; i < 9; i++) {
scratchPad[i] = g_wire->read();
}
int16_t temp = (((int16_t)scratchPad[1]) << 8) + (int16_t)scratchPad[0];
return temp;
}
void tembooDS18B20Begin(__attribute__((unused)) void* sensorConfig) {
// all of the initializtion is done in init
return;
}
uint8_t* tembooDS18B20GetSensorChannel(void* sensorConfig) {
TembooDS18B20Config* config = (TembooDS18B20Config*) sensorConfig;
return config->channel;
}
int tembooDS18B20GetSensorPin(void* sensorConfig) {
TembooDS18B20Config* config = (TembooDS18B20Config*) sensorConfig;
return config->pin;
}
void tembooDS18B20Init(TembooDS18B20Config* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue) {
sensorConfig->pin = pin;
sensorConfig->conversionDelayMillis = 1000;
// create OneWire object
if (NULL == g_wire) {
g_wire = new TembooOneWire(pin);
}
// reset search before searching for device
g_wire->reset_search();
// find device connected to the wire
g_wire->search(sensorConfig->channel);
if (g_wire->crc8(sensorConfig->channel, 7) != sensorConfig->channel[7]) {
// invalid address
}
// begin to check if sensor is in parasite mode
g_wire->reset();
g_wire->select(sensorConfig->channel);
g_wire->write(0xB4);
sensorConfig->parasite = g_wire->read_bit() == 0;
g_wire->reset();
tembooSensor->sensorConfig = sensorConfig;
tembooSensor->read = tembooDS18B20Read;
tembooSensor->begin = tembooDS18B20Begin;
tembooSensor->write = NULL;
tembooSensor->getSensorPin = tembooDS18B20GetSensorPin;
tembooSensor->getSensorChannel = tembooDS18B20GetSensorChannel;
tembooSensor->defaultValue = defaultValue;
}

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 __TEMBOODS18B20__
#define __TEMBOODS18B20__
#include "TembooWebSocketRequestHandles.h"
typedef struct TembooDS18B20Config{
uint8_t channel[8];
int pin;
uint16_t conversionDelayMillis;
bool parasite;
} TembooDS18B20Config;
void tembooDS18B20Init(TembooDS18B20Config* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue);
void tembooDS18B20Begin(void* sensorConfig);
uint8_t* tembooDS18B20GetSensorChannel(void* sensorConfig);
int tembooDS18B20GetSensorPin(void* sensorConfig);
int tembooDS18B20Read(void* sensorConfig);
#endif /* defined(__TEMBOODS18B20__) */

View File

@@ -0,0 +1,109 @@
/*
###############################################################################
#
# 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 "TembooGPIO.h"
int tembooDigitalRead(void* sensorConfig) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
if (config->mode == OUTPUT) {
return config->currentValue;
}
return digitalRead(config->pin);
}
void tembooDigitalWrite(void* sensorConfig, int val) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
digitalWrite(config->pin, val);
config->currentValue = val;
}
int tembooAnalogRead(void* sensorConfig) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
if (config->mode == OUTPUT) {
return config->currentValue;
}
return analogRead(config->pin);
}
void tembooAnalogWrite(void* sensorConfig, int val) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
analogWrite(config->pin, val);
config->currentValue = val;
}
void tembooGPIOBegin(__attribute__((unused)) void* sensorConfig) {
// all of the initializtion is done in init
return;
}
uint8_t* tembooGPIOGetSensorChannel(void* sensorConfig) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
return config->channel;
}
int tembooGPIOGetSensorPin(void* sensorConfig) {
TembooGPIOConfig* config = (TembooGPIOConfig*) sensorConfig;
return config->pin;
}
void tembooDigitalGPIOInit(TembooGPIOConfig* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue, int mode) {
sensorConfig->pin = pin;
sensorConfig->channel = NULL;
sensorConfig->mode = mode;
pinMode(pin, mode);
if (mode == OUTPUT) {
// this will set the current value
tembooDigitalWrite(sensorConfig, defaultValue);
tembooSensor->write = tembooDigitalWrite;
} else {
sensorConfig->currentValue = tembooDigitalRead(sensorConfig);
tembooSensor->write = NULL;
}
tembooSensor->sensorConfig = sensorConfig;
tembooSensor->read = tembooDigitalRead;
tembooSensor->begin = tembooGPIOBegin;
tembooSensor->getSensorPin = tembooGPIOGetSensorPin;
tembooSensor->getSensorChannel = tembooGPIOGetSensorChannel;
tembooSensor->defaultValue = defaultValue;
}
void tembooAnalogGPIOInit(TembooGPIOConfig* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue, int mode) {
sensorConfig->pin = pin;
sensorConfig->channel = NULL;
sensorConfig->mode = mode;
pinMode(pin, mode);
if (mode == OUTPUT) {
// this will set the current value
tembooAnalogWrite(sensorConfig, defaultValue);
tembooSensor->write = tembooAnalogWrite;
} else {
sensorConfig->currentValue = tembooAnalogRead(sensorConfig);
tembooSensor->write = NULL;
}
tembooSensor->sensorConfig = sensorConfig;
tembooSensor->read = tembooAnalogRead;
tembooSensor->begin = tembooGPIOBegin;
tembooSensor->getSensorPin = tembooGPIOGetSensorPin;
tembooSensor->getSensorChannel = tembooGPIOGetSensorChannel;
tembooSensor->defaultValue = defaultValue;
}

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 __TEMBOOGPIO__
#define __TEMBOOGPIO__
#include "TembooWebSocketRequestHandles.h"
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct TembooGPIOConfig{
uint8_t* channel;
int pin;
int currentValue;
int mode;
} TembooGPIOConfig;
void tembooGPIOBegin(void* sensorConfig);
uint8_t* tembooGPIOGetSensorChannel(void* sensorConfig);
int tembooGPIOGetSensorPin(void* sensorConfig);
void tembooDigitalGPIOInit(TembooGPIOConfig* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue, int mode);
int tembooDigitalRead(void* sensorConfig);
void tembooDigitalWrite(void* sensorConfig, int val);
void tembooAnalogGPIOInit(TembooGPIOConfig* sensorConfig, TembooSensor* tembooSensor, int pin, int defaultValue, int mode);
int tembooAnalogRead(void* sensorConfig);
void tembooAnalogWrite(void* sensorConfig, int val);
#if defined(__cplusplus)
}
#endif
#endif /* defined(__TEMBOOGPIO__) */

View File

@@ -0,0 +1,48 @@
/*
###############################################################################
#
# 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 "TembooGlobal.h"
char* uint16toa(uint16_t value, char* dest) {
return uint32toa(value, dest);
}
char* uint32toa(uint32_t value, char* dest) {
char* end = dest;
do {
*end++ = (value % 10) + '0';
} while (value /= 10);
*end = '\0';
end--;
char c;
char* begin = dest;
while(end > begin) {
c = *end;
*end = *begin;
*begin = c;
end--;
begin++;
}
return dest;
}

View File

@@ -0,0 +1,53 @@
/*
###############################################################################
#
# 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 TEMBOOGLOBAL_H_
#define TEMBOOGLOBAL_H_
#include <Arduino.h>
//#define TEMBOO_VERBOSE
#ifdef TEMBOO_VERBOSE
#define TEMBOO_TRACE(x) Serial.print(x)
#define TEMBOO_TRACE_BYTES(x,c) Serial.write((const uint8_t*)x,c)
#define TEMBOO_TRACELN(x) Serial.println(x)
#else
#define TEMBOO_TRACE(x)
#define TEMBOO_TRACE_BYTES(x,c)
#define TEMBOO_TRACELN(x)
#endif
#ifdef __cplusplus
extern "C" {
#endif
char* uint16toa(uint16_t value, char* dest);
char* uint32toa(uint32_t value, char* dest);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,96 @@
/*
###############################################################################
#
# 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 TEMBOOBASEIPSTACK_H_
#define TEMBOOBASEIPSTACK_H_
#include <Client.h>
static const int WRITE_CHUNK_SIZE = 64;
class TembooMQTTIPStack
{
public:
TembooMQTTIPStack(Client& client) : m_client(client) {
}
int connect(const char* hostname, int port) {
return m_client.connect(hostname, port);
}
bool isConnected() {
return m_client.connected();
}
int disconnect() {
m_client.stop();
return 0;
}
int read(unsigned char* buffer, int len, int timeoutMillis) {
m_client.setTimeout(timeoutMillis);
size_t count = m_client.readBytes((char*)buffer, len);
// Not sure this is a totally good idea.
// (Stream defaults to 1000 mS timeouts.)
m_client.setTimeout(1000);
return count;
}
int write(unsigned char* buffer, int len, int timeout) {
// It's possible for write to get called with a 0 timeout,
// in which case we want to make at least one attempt to send
// the data.
timeout = timeout == 0 ? 1 : timeout;
m_client.setTimeout(timeout);
// Arduino's WiFiClient::write can accept only a limited
// number of characters on each call, so we must make sure
// we pass less than this limit. Note that this isn't necessary
// for Arduino's EthernetClient. However, instead of trying to
// differentiate between clients, we do it for both.
// It SHOULDN'T impact performance in any significant way, but
// if you're using a client that doesn't have this limitation and
// the idea bothers you, feel free to eliminate the min() call or
// else set WRITE_CHUNK_SIZE to a large number.
// Note that the caller (in this case, MQTT::Client::sendPacket)
// is responsible for calling this method repeatedly until all
// bytes have been written.
size_t count = m_client.write(buffer, min(len, WRITE_CHUNK_SIZE));
// Not sure this is a totally good idea.
// (Stream defaults to 1000 mS timeouts.)
m_client.setTimeout(1000);
return count;
}
protected:
Client& m_client;
};
#endif

View File

@@ -0,0 +1,193 @@
/*
###############################################################################
#
# 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.
#
###############################################################################
*/
#include <string.h>
#include <stdlib.h>
#include <avr/pgmspace.h>
#include "TembooMQTTSession.h"
#include "tmbhmac.h"
#include "DataFormatter.h"
#include "TembooTags.h"
#define QSEND(x) if(!qsend((x))) { goto SendFailed;}
unsigned long TembooMQTTSession::s_timeOffset = 0;
TembooMQTTSession::TembooMQTTSession(TembooMQTTClient& client) : m_client(client) {
}
void TembooMQTTSession::setTime(unsigned long currentTime) {
s_timeOffset = currentTime - (millis()/1000);
}
unsigned long TembooMQTTSession::getTime() {
return s_timeOffset + (millis()/1000);
}
int TembooMQTTSession::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)TembooMQTTSession::getTime(), timeStr);
getAuth(fmt, appKeyValue, timeStr, auth);
if (m_client.isConnected()) {
m_sendQueueDepth = 0;
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);
// Format and send the body of the request
QSEND(TAG_DATA);
fmt.reset();
while(fmt.hasNext()) {
QSEND(fmt.next());
}
QSEND(TAG_END_REQUEST);
return m_client.sendChoreoRequest(this->m_sendQueue, this->m_sendQueueDepth);
SendFailed:
TEMBOO_TRACELN("FAIL");
return TEMBOO_ERROR_FAILURE;
} else {
TEMBOO_TRACELN("FAIL");
return TEMBOO_ERROR_FAILURE;
}
}
uint16_t TembooMQTTSession::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 TembooMQTTSession::qsend(const char* s) {
char c = *s++;
bool rc = true;
while(c != '\0' && rc) {
rc = qsend(c);
c = *s++;
}
return rc;
}
bool TembooMQTTSession::qsend(char c) {
if (m_sendQueueDepth >= TEMBOO_SEND_QUEUE_SIZE) {
return false;
}
// Never send a nul character.
if ('\0' != c) {
m_sendQueue[m_sendQueueDepth++] = c;
}
return true;
}

View File

@@ -0,0 +1,112 @@
/*
###############################################################################
#
# 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 TEMBOOMQTTSESSIONCLASS_H_
#define TEMBOOMQTTSESSIONCLASS_H_
#include <stdint.h>
#include <Arduino.h>
#include "TembooMQTTEdgeDevice.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 TembooMQTTSession {
public:
//TembooMQTTSession constructor
//client: REQUIRED TembooMQTTClient client object.
TembooMQTTSession(TembooMQTTClient& 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();
private:
TembooMQTTClient& m_client;
size_t m_sendQueueDepth;
char m_sendQueue[TEMBOO_SEND_QUEUE_SIZE];
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

View File

@@ -0,0 +1,580 @@
/*
Copyright (c) 2007, Jim Studt (original old version - many contributors since)
The latest version of this library may be found at:
http://www.pjrc.com/teensy/td_libs_OneWire.html
OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
January 2010. At the time, it was in need of many bug fixes, but had
been abandoned the original author (Jim Studt). None of the known
contributors were interested in maintaining OneWire. Paul typically
works on OneWire every 6 to 12 months. Patches usually wait that
long. If anyone is interested in more actively maintaining OneWire,
please contact Paul.
Temboo Version:
Class renamed to resolve a distribution issue.
Version 2.3:
Unknonw chip fallback mode, Roger Clark
Teensy-LC compatibility, Paul Stoffregen
Search bug fix, Love Nystrom
Version 2.2:
Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
Fix DS18B20 example negative temperature
Fix DS18B20 example's low res modes, Ken Butcher
Improve reset timing, Mark Tillotson
Add const qualifiers, Bertrik Sikken
Add initial value input to crc16, Bertrik Sikken
Add target_search() function, Scott Roberts
Version 2.1:
Arduino 1.0 compatibility, Paul Stoffregen
Improve temperature example, Paul Stoffregen
DS250x_PROM example, Guillermo Lovato
PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
Improvements from Glenn Trewitt:
- crc16() now works
- check_crc16() does all of calculation/checking work.
- Added read_bytes() and write_bytes(), to reduce tedious loops.
- Added ds2408 example.
Delete very old, out-of-date readme file (info is here)
Version 2.0: Modifications by Paul Stoffregen, January 2010:
http://www.pjrc.com/teensy/td_libs_OneWire.html
Search fix from Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Use direct optimized I/O in all cases
Disable interrupts during timing critical sections
(this solves many random communication errors)
Disable interrupts during read-modify-write I/O
Reduce RAM consumption by eliminating unnecessary
variables and trimming many to 8 bits
Optimize both crc8 - table version moved to flash
Modified to work with larger numbers of devices - avoids loop.
Tested in Arduino 11 alpha with 12 sensors.
26 Sept 2008 -- Robin James
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
Updated to work with arduino-0008 and to include skip() as of
2007/07/06. --RJL20
Modified to calculate the 8-bit CRC directly, avoiding the need for
the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
-- Tom Pollard, Jan 23, 2008
Jim Studt's original library was modified by Josh Larios.
Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Much of the code was inspired by Derek Yerger's code, though I don't
think much of that remains. In any event that was..
(copyleft) 2006 by Derek Yerger - Free to distribute freely.
The CRC code was excerpted and inspired by the Dallas Semiconductor
sample code bearing this copyright.
//---------------------------------------------------------------------------
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//--------------------------------------------------------------------------
*/
#include "TembooOneWire.h"
TembooOneWire::TembooOneWire(uint8_t pin)
{
pinMode(pin, INPUT);
bitmask = PIN_TO_BITMASK(pin);
baseReg = PIN_TO_BASEREG(pin);
#if ONEWIRE_SEARCH
reset_search();
#endif
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return a 0;
//
// Returns 1 if a device asserted a presence pulse, 0 otherwise.
//
uint8_t TembooOneWire::reset(void)
{
IO_REG_TYPE mask = bitmask;
volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg;
uint8_t r;
uint8_t retries = 125;
noInterrupts();
DIRECT_MODE_INPUT(reg, mask);
interrupts();
// wait until the wire is high... just in case
do {
if (--retries == 0) return 0;
delayMicroseconds(2);
} while ( !DIRECT_READ(reg, mask));
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
interrupts();
delayMicroseconds(480);
noInterrupts();
DIRECT_MODE_INPUT(reg, mask); // allow it to float
delayMicroseconds(70);
r = !DIRECT_READ(reg, mask);
interrupts();
delayMicroseconds(410);
return r;
}
//
// Write a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
void TembooOneWire::write_bit(uint8_t v)
{
IO_REG_TYPE mask=bitmask;
volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg;
if (v & 1) {
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
delayMicroseconds(10);
DIRECT_WRITE_HIGH(reg, mask); // drive output high
interrupts();
delayMicroseconds(55);
} else {
noInterrupts();
DIRECT_WRITE_LOW(reg, mask);
DIRECT_MODE_OUTPUT(reg, mask); // drive output low
delayMicroseconds(65);
DIRECT_WRITE_HIGH(reg, mask); // drive output high
interrupts();
delayMicroseconds(5);
}
}
//
// Read a bit. Port and bit is used to cut lookup time and provide
// more certain timing.
//
uint8_t TembooOneWire::read_bit(void)
{
IO_REG_TYPE mask=bitmask;
volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg;
uint8_t r;
noInterrupts();
DIRECT_MODE_OUTPUT(reg, mask);
DIRECT_WRITE_LOW(reg, mask);
delayMicroseconds(3);
DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
delayMicroseconds(10);
r = DIRECT_READ(reg, mask);
interrupts();
delayMicroseconds(53);
return r;
}
//
// Write a byte. The writing code uses the active drivers to raise the
// pin high, if you need power after the write (e.g. DS18S20 in
// parasite power mode) then set 'power' to 1, otherwise the pin will
// go tri-state at the end of the write to avoid heating in a short or
// other mishap.
//
void TembooOneWire::write(uint8_t v, uint8_t power /* = 0 */) {
uint8_t bitMask;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
TembooOneWire::write_bit( (bitMask & v)?1:0);
}
if ( !power) {
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
DIRECT_WRITE_LOW(baseReg, bitmask);
interrupts();
}
}
void TembooOneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
for (uint16_t i = 0 ; i < count ; i++)
write(buf[i]);
if (!power) {
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
DIRECT_WRITE_LOW(baseReg, bitmask);
interrupts();
}
}
//
// Read a byte
//
uint8_t TembooOneWire::read() {
uint8_t bitMask;
uint8_t r = 0;
for (bitMask = 0x01; bitMask; bitMask <<= 1) {
if ( TembooOneWire::read_bit()) r |= bitMask;
}
return r;
}
void TembooOneWire::read_bytes(uint8_t *buf, uint16_t count) {
for (uint16_t i = 0 ; i < count ; i++)
buf[i] = read();
}
//
// Do a ROM select
//
void TembooOneWire::select(const uint8_t rom[8])
{
uint8_t i;
write(0x55); // Choose ROM
for (i = 0; i < 8; i++) write(rom[i]);
}
//
// Do a ROM skip
//
void TembooOneWire::skip()
{
write(0xCC); // Skip ROM
}
void TembooOneWire::depower()
{
noInterrupts();
DIRECT_MODE_INPUT(baseReg, bitmask);
interrupts();
}
#if ONEWIRE_SEARCH
//
// You need to use this function to start a search again from the beginning.
// You do not need to do it for the first search, though you could.
//
void TembooOneWire::reset_search()
{
// reset the search state
LastDiscrepancy = 0;
LastDeviceFlag = FALSE;
LastFamilyDiscrepancy = 0;
for(int i = 7; ; i--) {
ROM_NO[i] = 0;
if ( i == 0) break;
}
}
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
//
void TembooOneWire::target_search(uint8_t family_code)
{
// set the search state to find SearchFamily type devices
ROM_NO[0] = family_code;
for (uint8_t i = 1; i < 8; i++)
ROM_NO[i] = 0;
LastDiscrepancy = 64;
LastFamilyDiscrepancy = 0;
LastDeviceFlag = FALSE;
}
//
// Perform a search. If this function returns a '1' then it has
// enumerated the next device and you may retrieve the ROM from the
// OneWire::address variable. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then a 0 is returned. If a new device is found then
// its address is copied to newAddr. Use OneWire::reset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return TRUE : device found, ROM number in ROM_NO buffer
// FALSE : device not found, end of search
//
uint8_t TembooOneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
{
uint8_t id_bit_number;
uint8_t last_zero, rom_byte_number, search_result;
uint8_t id_bit, cmp_id_bit;
unsigned char rom_byte_mask, search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = 0;
// if the last call was not the last one
if (!LastDeviceFlag)
{
// 1-Wire reset
if (!reset())
{
// reset the search
LastDiscrepancy = 0;
LastDeviceFlag = FALSE;
LastFamilyDiscrepancy = 0;
return FALSE;
}
// issue the search command
if (search_mode == true) {
write(0xF0); // NORMAL SEARCH
} else {
write(0xEC); // CONDITIONAL SEARCH
}
// loop to do the search
do
{
// read a bit and its complement
id_bit = read_bit();
cmp_id_bit = read_bit();
// check for no devices on 1-wire
if ((id_bit == 1) && (cmp_id_bit == 1))
break;
else
{
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit)
search_direction = id_bit; // bit write value for search
else
{
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < LastDiscrepancy)
search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
else
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == LastDiscrepancy);
// if 0 was picked then record its position in LastZero
if (search_direction == 0)
{
last_zero = id_bit_number;
// check for Last discrepancy in family
if (last_zero < 9)
LastFamilyDiscrepancy = last_zero;
}
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction == 1)
ROM_NO[rom_byte_number] |= rom_byte_mask;
else
ROM_NO[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
write_bit(search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0)
{
rom_byte_number++;
rom_byte_mask = 1;
}
}
}
while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65))
{
// search successful so set LastDiscrepancy,LastDeviceFlag,search_result
LastDiscrepancy = last_zero;
// check for last device
if (LastDiscrepancy == 0)
LastDeviceFlag = TRUE;
search_result = TRUE;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !ROM_NO[0])
{
LastDiscrepancy = 0;
LastDeviceFlag = FALSE;
LastFamilyDiscrepancy = 0;
search_result = FALSE;
} else {
for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
}
return search_result;
}
#endif
#if ONEWIRE_CRC
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
//
#if ONEWIRE_CRC8_TABLE
// This table comes from Dallas sample code where it is freely reusable,
// though Copyright (C) 2000 Dallas Semiconductor Corporation
static const uint8_t PROGMEM dscrc_table[] = {
0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220,
35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98,
190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7,
219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154,
101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36,
248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185,
140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139,
87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53};
//
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
// and the registers. (note: this might better be done without to
// table, it would probably be smaller and certainly fast enough
// compared to all those delayMicrosecond() calls. But I got
// confused, so I use this table from the examples.)
//
uint8_t TembooOneWire::crc8(const uint8_t *addr, uint8_t len)
{
uint8_t crc = 0;
while (len--) {
crc = pgm_read_byte(dscrc_table + (crc ^ *addr++));
}
return crc;
}
#else
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but much smaller, than the lookup table.
//
uint8_t TembooOneWire::crc8(const uint8_t *addr, uint8_t len)
{
uint8_t crc = 0;
while (len--) {
#if defined(__AVR__)
crc = _crc_ibutton_update(crc, *addr++);
#else
uint8_t inbyte = *addr++;
for (uint8_t i = 8; i; i--) {
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix) crc ^= 0x8C;
inbyte >>= 1;
}
#endif
}
return crc;
}
#endif
#if ONEWIRE_CRC16
bool TembooOneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
{
crc = ~crc16(input, len, crc);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
uint16_t TembooOneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
{
#if defined(__AVR__)
for (uint16_t i = 0 ; i < len ; i++) {
crc = _crc16_update(crc, input[i]);
}
#else
static const uint8_t oddparity[16] =
{ 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
for (uint16_t i = 0 ; i < len ; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
#endif
return crc;
}
#endif
#endif

View File

@@ -0,0 +1,371 @@
#ifndef TembooOneWire_h
#define TembooOneWire_h
#include <inttypes.h>
#if defined(__AVR__)
#include <util/crc16.h>
#endif
#if ARDUINO >= 100
#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc
#else
#include "WProgram.h" // for delayMicroseconds
#include "pins_arduino.h" // for digitalPinToBitMask, etc
#endif
// You can exclude certain features from OneWire. In theory, this
// might save some space. In practice, the compiler automatically
// removes unused code (technically, the linker, using -fdata-sections
// and -ffunction-sections when compiling, and Wl,--gc-sections
// when linking), so most of these will not result in any code size
// reduction. Well, unless you try to use the missing features
// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
// is the exception, because it selects a fast but large algorithm
// or a small but slow algorithm.
// you can exclude onewire_search by defining that to 0
#ifndef ONEWIRE_SEARCH
#define ONEWIRE_SEARCH 1
#endif
// You can exclude CRC checks altogether by defining this to 0
#ifndef ONEWIRE_CRC
#define ONEWIRE_CRC 1
#endif
// Select the table-lookup method of computing the 8-bit CRC
// by setting this to 1. The lookup table enlarges code size by
// about 250 bytes. It does NOT consume RAM (but did in very
// old versions of OneWire). If you disable this, a slower
// but very compact algorithm is used.
#ifndef ONEWIRE_CRC8_TABLE
#define ONEWIRE_CRC8_TABLE 1
#endif
// You can allow 16-bit CRC checks by defining this to 1
// (Note that ONEWIRE_CRC must also be 1.)
#ifndef ONEWIRE_CRC16
#define ONEWIRE_CRC16 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
// Platform specific I/O definitions
#if defined(__AVR__)
#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM asm("r30")
#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin) (1)
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) (*((base)+512))
#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
#elif defined(__MKL26Z64__)
#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
// If you have trouble with OneWire on Arduino Due, please check the
// status of delayMicroseconds() before reporting a bug in OneWire!
#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
#ifndef PROGMEM
#define PROGMEM
#endif
#ifndef pgm_read_byte
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
#endif
#elif defined(__PIC32MX__)
#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
#elif defined(ARDUINO_ARCH_ESP8266)
#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
#define PIN_TO_BITMASK(pin) (1 << pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS
#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS
#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS
#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS
#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS
#elif defined(__SAMD21G18A__)
#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
#elif defined(RBL_NRF51822)
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
#include "scss_registers.h"
#include "portable.h"
#include "avr/pgmspace.h"
#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
#define DIR_OFFSET_SS 0x01
#define DIR_OFFSET_SOC 0x04
#define EXT_PORT_OFFSET_SS 0x0A
#define EXT_PORT_OFFSET_SOC 0x50
/* GPIO registers base address */
#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
#define PIN_TO_BITMASK(pin) pin
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
static inline __attribute__((always_inline))
IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
IO_REG_TYPE ret;
if (SS_GPIO == GPIO_TYPE(pin)) {
ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
} else {
ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
}
return ((ret >> GPIO_ID(pin)) & 0x01);
}
static inline __attribute__((always_inline))
void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
} else {
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
} else {
MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
} else {
MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
}
}
static inline __attribute__((always_inline))
void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
{
if (SS_GPIO == GPIO_TYPE(pin)) {
WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
} else {
MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
}
}
#define DIRECT_READ(base, pin) directRead(base, pin)
#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
#else
#define PIN_TO_BASEREG(pin) (0)
#define PIN_TO_BITMASK(pin) (pin)
#define IO_REG_TYPE unsigned int
#define IO_REG_ASM
#define DIRECT_READ(base, pin) digitalRead(pin)
#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
#endif
class TembooOneWire
{
private:
IO_REG_TYPE bitmask;
volatile IO_REG_TYPE *baseReg;
#if ONEWIRE_SEARCH
// global search state
unsigned char ROM_NO[8];
uint8_t LastDiscrepancy;
uint8_t LastFamilyDiscrepancy;
uint8_t LastDeviceFlag;
#endif
public:
TembooOneWire( uint8_t pin);
// Perform a 1-Wire reset cycle. Returns 1 if a device responds
// with a presence pulse. Returns 0 if there is no device or the
// bus is shorted or otherwise held low for more than 250uS
uint8_t reset(void);
// Issue a 1-Wire rom select command, you do the reset first.
void select(const uint8_t rom[8]);
// Issue a 1-Wire rom skip command, to address all on bus.
void skip(void);
// Write a byte. If 'power' is one then the wire is held high at
// the end for parasitically powered devices. You are responsible
// for eventually depowering it by calling depower() or doing
// another read or write.
void write(uint8_t v, uint8_t power = 0);
void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
// Read a byte.
uint8_t read(void);
void read_bytes(uint8_t *buf, uint16_t count);
// Write a bit. The bus is always left powered at the end, see
// note in write() about that.
void write_bit(uint8_t v);
// Read a bit.
uint8_t read_bit(void);
// Stop forcing power onto the bus. You only need to do this if
// you used the 'power' flag to write() or used a write_bit() call
// and aren't about to do another read or write. You would rather
// not leave this powered if you don't have to, just in case
// someone shorts your bus.
void depower(void);
#if ONEWIRE_SEARCH
// Clear the search state so that if will start from the beginning again.
void reset_search();
// Setup the search to find the device type 'family_code' on the next call
// to search(*newAddr) if it is present.
void target_search(uint8_t family_code);
// Look for the next device. Returns 1 if a new address has been
// returned. A zero might mean that the bus is shorted, there are
// no devices, or you have already retrieved all of them. It
// might be a good idea to check the CRC to make sure you didn't
// get garbage. The order is deterministic. You will always get
// the same devices in the same order.
uint8_t search(uint8_t *newAddr, bool search_mode = true);
#endif
#if ONEWIRE_CRC
// Compute a Dallas Semiconductor 8 bit CRC, these are used in the
// ROM and scratchpad registers.
static uint8_t crc8(const uint8_t *addr, uint8_t len);
#if ONEWIRE_CRC16
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return True, iff the CRC matches.
static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
#endif
#endif
};
#endif

View File

@@ -0,0 +1,273 @@
/*
###############################################################################
#
# 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 <stdlib.h>
#include <avr/pgmspace.h>
#include "TembooSession.h"
#include "tmbhmac.h"
#include "DataFormatter.h"
static const char EOL[] PROGMEM = "\r\n";
static const char POST[] PROGMEM = "POST ";
static const char POSTAMBLE[] PROGMEM = " HTTP/1.0"; // Prevent host from using chunked encoding in response.
static const char HEADER_HOST[] PROGMEM = "Host: ";
static const char HEADER_ACCEPT[] PROGMEM = "Accept: application/xml";
static const char HEADER_ORG[] PROGMEM = "x-temboo-domain: /";
static const char HEADER_DOM[] PROGMEM = "/master";
static const char HEADER_CONTENT_LENGTH[] PROGMEM = "Content-Length: ";
static const char HEADER_TIME[] PROGMEM = "x-temboo-time: ";
static const char BASE_CHOREO_URI[] PROGMEM = "/arcturus-web/api-1.0/ar";
static const char HEADER_AUTH[] PROGMEM = "x-temboo-authentication: ";
static const char HEADER_CONTENT_TYPE[] PROGMEM = "Content-Type: text/plain";
static const char TEMBOO_DOMAIN[] PROGMEM = ".temboolive.com";
static const char SDK_ID[] PROGMEM = "?source_id=";
static const char SDK_ID_DEFAULT[] PROGMEM = "ArduinoSDK1.2.0";
static const char HTTP[] PROGMEM = "http://";
unsigned long TembooSession::s_timeOffset = 0;
TembooSession::TembooSession(Client& client,
IPAddress serverAddr,
uint16_t port) : m_client(client) {
m_addr = serverAddr;
m_port = port;
m_sendQueueDepth = 0;
}
void TembooSession::setTime(unsigned long currentTime) {
s_timeOffset = currentTime - (millis()/1000);
}
unsigned long TembooSession::getTime() {
return s_timeOffset + (millis()/1000);
}
int TembooSession::executeChoreo(
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 buffer[11];
// 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)TembooSession::getTime(), buffer);
uint16_t contentLength = getAuth(fmt, appKeyValue, buffer, auth);
if (m_client.connected()) {
m_client.stop();
}
m_client.flush();
int connected = 0;
TEMBOO_TRACE("Connecting: ");
// reserve space for the "host" string sufficient to hold either the
// (dotted-quad) IP address + port, or the default <account>.temboolive.com
// host string.
int hostLen = strlen_P(TEMBOO_DOMAIN) + strlen(accountName) + 1;
char host[hostLen];
// Construct the "host" string from the account name and the temboo domain name.
strcpy(host, accountName);
strcat_P(host, TEMBOO_DOMAIN);
bool useProxy = false;
if (m_addr == INADDR_NONE) {
TEMBOO_TRACELN(host);
connected = m_client.connect(host, m_port);
} else {
TEMBOO_TRACELN(host);
connected = m_client.connect(m_addr, m_port);
useProxy = true;
}
if (connected) {
TEMBOO_TRACELN("OK. req:");
qsendProgmem(POST);
if(useProxy) {
qsendProgmem(HTTP);
qsend(host);
}
qsendProgmem(BASE_CHOREO_URI);
qsend(path);
qsendProgmem(SDK_ID);
if (deviceType.isEmpty()) {
qsendProgmem(SDK_ID_DEFAULT);
} else {
qsend(deviceType.getName());
}
qsendlnProgmem(POSTAMBLE);
// Send our custom authentication header
// (app-key-name:hmac)
qsendProgmem(HEADER_AUTH);
qsend(appKeyName);
qsend(":");
qsendln(auth);
// send the standard host header
qsendProgmem(HEADER_HOST);
qsendln(host);
// send the standard accept header
qsendlnProgmem(HEADER_ACCEPT);
// send our custom account name neader
qsendProgmem(HEADER_ORG);
qsend(accountName);
qsendlnProgmem(HEADER_DOM);
// send the standard content type header
qsendlnProgmem(HEADER_CONTENT_TYPE);
// send our custom client time header
qsendProgmem(HEADER_TIME);
qsendln(buffer);
// send the standard content length header
qsendProgmem(HEADER_CONTENT_LENGTH);
qsendln(uint16toa(contentLength, buffer));
qsendProgmem(EOL);
// Format and send the body of the request
fmt.reset();
while(fmt.hasNext()) {
qsend(fmt.next());
}
qsendProgmem(EOL);
qflush();
return 0;
} else {
TEMBOO_TRACELN("FAIL");
return 1;
}
}
uint16_t TembooSession::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;
}
void TembooSession::qsend(const char* s) {
char c = *s++;
while(c != '\0') {
qsend(c);
c = *s++;
}
}
void TembooSession::qsendProgmem(const char* s) {
char c = pgm_read_byte(s++);
while(c != '\0') {
qsend(c);
c = pgm_read_byte(s++);
}
}
void TembooSession::qsend(char c) {
m_sendQueue[m_sendQueueDepth++] = c;
if (m_sendQueueDepth >= TEMBOO_SEND_QUEUE_SIZE) {
qflush();
}
}
void TembooSession::qflush() {
m_client.write((const uint8_t*)m_sendQueue, m_sendQueueDepth);
TEMBOO_TRACE_BYTES(m_sendQueue, m_sendQueueDepth);
m_sendQueueDepth = 0;
}
void TembooSession::qsendln(const char* s) {
qsend(s);
qsendProgmem(EOL);
}
void TembooSession::qsendlnProgmem(const char* s) {
qsendProgmem(s);
qsendProgmem(EOL);
}

View File

@@ -0,0 +1,141 @@
/*
###############################################################################
#
# 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 TEMBOOSESSIONCLASS_H_
#define TEMBOOSESSIONCLASS_H_
#include <stdint.h>
#include <Arduino.h>
#include <IPAddress.h>
#include <Client.h>
#include "TembooGlobal.h"
#ifndef TEMBOO_SEND_QUEUE_SIZE
// Some network interfaces (i.e. ethernet or WiFi shields) can only accept
// a limited amount of data. If you try to send more than the limit, the excess
// is just lost. However, sending one character at a time is very inefficient.
// To deal with this situation, we queue up TEMBOO_SEND_QUEUE_SIZE bytes to send
// to the network device at one time. This is a compromise between RAM usage
// and performance.
#define TEMBOO_SEND_QUEUE_SIZE (32)
#endif
class ChoreoInputSet;
class ChoreoInputExpressionSet;
class ChoreoSensorInputSet;
class ChoreoOutputSet;
class ChoreoPreset;
class ChoreoDevice;
class DataFormatter;
class TembooSession {
public:
//TembooSession constructor
//client: REQUIRED TCP/IP client object. Usually either an EthernetClient or a WiFiClient
//IPAddress: OPTIONAL IP address of the server to connect to. Usually only used for testing.
//port: OPTIONAL port number to use with the IPAddress. Usually only used for testing.
TembooSession(Client& client, IPAddress serverAddr=INADDR_NONE, uint16_t port=80);
//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(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();
private:
static unsigned long s_timeOffset;
IPAddress m_addr;
uint16_t m_port;
Client& m_client;
char m_sendQueue[TEMBOO_SEND_QUEUE_SIZE];
size_t m_sendQueueDepth;
// 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 followed by a newline.
void qsendln(const char* str);
// queue an entire nul-terminated char array
// from flash memory (PROGMEM) one byte at a time,
// followed by a newline.
void qsendlnProgmem(const char* str);
// queue an entire nul-terminated char array
// from RAM one byte at a time.
void qsend(const char*);
// queue an entire nul-terminated char array
// from flash memory (PROGMEM) one byte at a time.
void qsendProgmem(const char*);
// queue a single character to be sent when the queue is full.
void qsend(char);
// send the current contents of the send queue to the client.
void qflush();
};
#endif

View File

@@ -0,0 +1,37 @@
/*
###############################################################################
#
# Temboo CoAP Edge Device library for Arduino
#
# 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 TEMBOOTAGS_H_
#define TEMBOOTAGS_H_
#define TAG_REQUEST_ID ('R')
#define TAG_ACCOUNT_NAME ('N')
#define TAG_APP_KEY_NAME ('K')
#define TAG_TIME ('T')
#define TAG_AUTH ('A')
#define TAG_CHOREO_ID ('C')
#define TAG_DATA ('X')
#define TAG_END_REQUEST ('!')
#define TAG_ELEMENT_SEPARATOR ('|')
#define TAG_VALUE_SEPARATOR (':')
#endif //TEMBOOTAGS_H_

View File

@@ -0,0 +1,62 @@
/*
###############################################################################
#
# 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 TEMBOOTIMER_H_
#define TEMBOOTIMER_H_
class TembooTimer {
public:
TembooTimer() {
this->m_startTimeMillis = 0;
this->m_durationMillis = 0;
}
TembooTimer(uint32_t m_durationMillis) {
this->start(m_durationMillis);
}
bool expired() {
return leftMillis() == 0;
}
void start(uint32_t m_durationMillis) {
this->m_startTimeMillis = getMillis();
this->m_durationMillis = m_durationMillis;
}
uint32_t leftMillis() {
uint32_t elapsedMillis = getMillis() - this->m_startTimeMillis;
return elapsedMillis < this->m_durationMillis ? (this->m_durationMillis - elapsedMillis) : 0;
}
protected:
uint32_t getMillis() {
return millis();
}
uint32_t m_startTimeMillis;
uint32_t m_durationMillis;
};
#endif /* TEMBOOTIMER_H_ */

View File

@@ -0,0 +1,307 @@
/*
###############################################################################
#
# Temboo 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 <ctype.h>
#include "TembooWebSocketRequestHandles.h"
#include "TembooGlobal.h"
static WSMessageRequest handleMessageRequest(char** saveptr);
static WSMessageRequest handleIntervalRequest(char** saveptr);
static WSMessageRequest handleSetRequest(char** saveptr, TembooSensor** sensorTable, int sensorTableDepth);
static WSMessageRequest handleGetRequest(char** saveptr, TembooSensor** sensorTable, int sensorTableDepth);
static WSMessageRequest handleMonitoringUpdateRequest(char**saveptr);
static const uint16_t WS_MAX_PAYLOAD_SIZE = 126;
WSMessageRequest handleGetRequest(char** saveptr, TembooSensor** sensorTable, int sensorTableDepth) {
char* textField = strtok_r(NULL, "|", &(*saveptr));
int pinNum = -1;
int pinVal = -1;
WSMessageRequest rc = WS_REQUEST_ERROR;
// strlen should catch the + sign
while (textField != NULL && strlen(textField) >= 2) {
if (*textField == 'P') {
// if pinNum is already set, then an extra pin number
// has been added to the request incorrectly
if (pinNum != -1) {
logTembooDebug("Invalid Get request\n");
return WS_REQUEST_ERROR;
}
size_t i = 0;
// make sure the pin value is made of digits
while (i < strlen(&textField[1])) {
if (isdigit(textField[1 + i++]) == 0) {
logTembooDebug(("Invalid pin number\n"));
return WS_REQUEST_ERROR;
}
}
pinNum = atoi(&textField[1]);
// make sure pin number is valid, set to -1 if error
if (pinNum > MAX_PIN_NUMBER) {
logTembooDebug(("Invalid pin number\n"));
pinNum = -1;
}
} else if (*textField == '+') {
break;
} else {
logTembooDebug("Invalid message key\n");
return WS_REQUEST_ERROR;
}
textField = strtok_r(NULL, "|", &(*saveptr));
}
if (pinNum >= 0) {
//obtain the data and send to Temboo
int i = 0;
for (i = 0; i < sensorTableDepth; i++) {
if (sensorTable[i]->getSensorPin(sensorTable[i]->sensorConfig) == pinNum) {
pinVal = sensorTable[i]->read(sensorTable[i]->sensorConfig);
rc = WS_GET_REQUEST;
break;
}
}
}
if (rc != WS_REQUEST_ERROR) {
// Send pinVal
addWebSocketPinData(pinNum, pinVal, true);
}
return rc;
}
WSMessageRequest handleSetRequest(char** saveptr, TembooSensor** sensorTable, int sensorTableDepth) {
int pinNum = -1;
int pinVal = -1;
bool pinValSet = false;
WSMessageRequest rc = WS_REQUEST_ERROR;
char* textField = strtok_r(NULL, "|", &(*saveptr));
while (textField != NULL && strlen(textField) >= 2) {
if (*textField == 'P') {
// if pinNum is already set, then an extra pin number
// has been added to the request incorrectly
if (pinNum != -1) {
logTembooDebug("Invalid Set request\n");
return WS_REQUEST_ERROR;
}
size_t i = 0;
// make sure the pin value is made of digits
while (i < strlen(&textField[1])) {
if (isdigit(textField[1 + i++]) == 0) {
logTembooDebug(("Invalid pin number\n"));
return WS_REQUEST_ERROR;
}
}
pinNum = atoi(&textField[1]);
// make sure pin number is valid, set to -1 if error
if (pinNum > MAX_PIN_NUMBER) {
logTembooDebug(("Invalid pin number\n"));
return WS_REQUEST_ERROR;
}
} else if (*textField == 'V') {
// if pinVal is already set, then an extra pin value
// has been added to the request incorrectly
if (pinValSet) {
logTembooDebug("Invalid Set request\n");
return WS_REQUEST_ERROR;
}
size_t i = 0;
// make sure the pin value is made of digits
while (i < strlen(&textField[1])) {
if (isdigit(textField[1 + i++]) == 0) {
logTembooDebug(("Invalid pin value\n"));
return WS_REQUEST_ERROR;
}
}
pinVal = atoi(&textField[1]);
// make sure pin value is valid, pinValSet will remain false
if (pinVal > MAX_PIN_VALUE) {
logTembooDebug(("Invalid pin value\n"));
return WS_REQUEST_ERROR;
} else {
pinValSet = true;
}
} else if (*textField == '+') {
break;
} else {
logTembooDebug("Invalid message key\n");
return WS_REQUEST_ERROR;
}
textField = strtok_r(NULL, "|", &(*saveptr));
}
if (pinNum >= 0 && pinValSet) {
int i = 0;
for (; i < sensorTableDepth; i++) {
if (sensorTable[i]->getSensorPin(sensorTable[i]->sensorConfig) == pinNum) {
if (sensorTable[i]->write != NULL) {
sensorTable[i]->write(sensorTable[i]->sensorConfig, pinVal);
rc = WS_SET_REQUEST;
}
break;
}
}
}
if (rc != WS_REQUEST_ERROR) {
// with pinval found, send the data to Temboo
addWebSocketPinData(pinNum, pinVal, true);
}
return rc;
}
// Parse out the error message while ignoring escaped characters.
// This functions acts like strstr where it will move saveptr
// to the index past the delimeter ('|' in this case), add '\0'
// where the delimeter is, and return the pointer of the error message
char* getErrorMessage(char** saveptr) {
if (saveptr == NULL || *saveptr == NULL) {
return NULL;
}
char* tok = *saveptr;
size_t len = strlen(*saveptr);
(*saveptr)++;
if (**saveptr == '\0') {
return NULL;
}
while (**saveptr != '\0') {
// check for delimiter
if (**saveptr == '\\') {
// check if we're escaping a special character
switch (*(*saveptr+1)) {
case '+':
case '|':
case '\\':
// shift string over 1 and increment
memmove(*saveptr,*saveptr+1, --len);
(*saveptr)++;
break;
default:
// increment pointer and decrement str length
(*saveptr)++;
len--;
break;
}
} else if (**saveptr == '|') {
// end of message
**saveptr = '\0';
(*saveptr)++;
return tok;
} else {
// increment pointer and decrement str length
(*saveptr)++;
len--;
}
}
// return string when if the end of the text is found
return tok;
}
WSMessageRequest handleMessageRequest(char** saveptr) {
char* textField = getErrorMessage(&(*saveptr));
WSMessageRequest rc = WS_REQUEST_ERROR;
if (textField != NULL && strlen(textField) >= 2) {
if (*textField == 'T') {
logTembooDebug(&textField[1]);
rc = WS_MESSAGE_REQUEST;
}
}
return rc;
}
WSMessageRequest handleMonitoringUpdateRequest(char** saveptr) {
char* textField = strtok_r(NULL, "|", &(*saveptr));
WSMessageRequest rc = WS_REQUEST_ERROR;
if (textField != NULL && strlen(textField) >= 2) {
if (*textField == 'T') {
if (!strcmp(&textField[1], "true")) {
rc = WS_UPDATE_CONNECTED;
} else if (!strcmp(&textField[1], "false")) {
rc = WS_UPDATE_DISCONNECTED;
} else {
rc = WS_REQUEST_ERROR;
}
}
}
return rc;
}
WSMessageRequest handleIntervalRequest(char** saveptr) {
char* textField = strtok_r(NULL, "|", &(*saveptr));
WSMessageRequest rc = WS_REQUEST_ERROR;
if (textField != NULL && strlen(textField) >= 2) {
if (*textField == 'V') {
if (isdigit(textField[1]) && strlen(&textField[1]) < 6) {
updateIntervalTime(atoi(&textField[1]));
rc = WS_INTERVAL_REQUEST;
}
}
}
return rc;
}
WSMessageRequest handleResponse(uint8_t* request, TembooSensor** sensorTable, int sensorTableDepth, bool connectionStatus) {
// parse response to find request type
char* saveptr = NULL;
char* requestField = strtok_r((char*)request, "|", &saveptr);
WSMessageRequest rc = WS_REQUEST_ERROR;
while (requestField != NULL) {
if (*requestField == 'M') {
switch (requestField[1]) {
// only read web socket messages if device is connected
// to the web socket
case 'G':
// send pin data to Temboo
if (connectionStatus) {
rc = handleGetRequest(&saveptr, sensorTable, sensorTableDepth);
}
break;
case 'S':
// change state of a pin
if (connectionStatus) {
rc = handleSetRequest(&saveptr, sensorTable, sensorTableDepth);
}
break;
case 'E':
// print message
rc = handleMessageRequest(&saveptr);
break;
case 'U':
rc = handleMonitoringUpdateRequest(&saveptr);
break;
case 'I':
rc = handleIntervalRequest(&saveptr);
break;
default:
// return error
rc = WS_REQUEST_ERROR;
break;
}
}
if (rc == WS_REQUEST_ERROR) {
break;
}
requestField = strtok_r(NULL, "|", &saveptr);
}
return rc;
}

View File

@@ -0,0 +1,78 @@
/*
###############################################################################
#
# 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 __TembooWebSocketRequestHandles__
#define __TembooWebSocketRequestHandles__
#include <Arduino.h>
#if defined(__cplusplus)
extern "C" {
#endif
#ifndef MAX_PIN_NUMBER
#define MAX_PIN_NUMBER 256
#endif
#ifndef MAX_PIN_VALUE
#define MAX_PIN_VALUE 255
#endif
typedef enum {
WS_GET_REQUEST = 0,
WS_SET_REQUEST,
WS_DATA_REQUEST,
WS_MESSAGE_REQUEST,
WS_RR_REQUEST,
WS_SESSION_ID_REQUEST,
WS_REQUEST_ERROR,
WS_UPDATE_CONNECTED,
WS_UPDATE_DISCONNECTED,
WS_NO_MESSAGE,
WS_INTERVAL_REQUEST
} WSMessageRequest;
typedef struct TembooSensor{
void* sensorConfig;
int (*read)(void* sensorConfig);
void (*write)(void* sensorConfig, int val);
void (*begin)(void* sensorConfig);
int (*getSensorPin)(void*sensorConfig);
uint8_t* (*getSensorChannel)(void*sensorConfig);
int defaultValue;
} TembooSensor;
typedef TembooSensor TembooActuator;
void addWebSocketPinData(int pin, int pinVal, bool requestResponse);
void updateIntervalTime(int intervalTime);
// Send frame
WSMessageRequest handleResponse(uint8_t* request, TembooSensor** sensors, int sensorTableDepth, bool connectionStatus);
void logTembooDebug(const char *c);
#if defined(__cplusplus)
}
#endif
#endif /* defined(__TembooWebSocketRequestHandles__) */

View File

@@ -0,0 +1,107 @@
/*
###############################################################################
#
# 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 <avr/pgmspace.h>
#include "tmbhmac.h"
HMAC::HMAC() {
}
HMAC::HMAC(const uint8_t* key, uint32_t keyLength) {
init(key, keyLength);
}
void HMAC::init(const uint8_t* key, uint32_t keyLength) {
m_key = key;
m_keyLength = keyLength;
uint8_t iKeyPad[HMAC_BLOCK_SIZE_BYTES];
constructKeyPad(iKeyPad, key, keyLength, (uint8_t)0x36);
m_md5.init();
m_md5.process(iKeyPad, HMAC_BLOCK_SIZE_BYTES);
}
void HMAC::process(const uint8_t* msg, uint32_t msgLength) {
// hmac = hash(o_key_pad + hash(i_key_pad + message))
// continue hashing the message
m_md5.process(msg, msgLength);
}
void HMAC::finish(uint8_t* dest) {
//hmac = hash(o_key_pad + hash(i_key_pad + message))
//
// Construct the o_key_pad
uint8_t finalBlock[HMAC_BLOCK_SIZE_BYTES + HMAC_HASH_SIZE_BYTES];
constructKeyPad(finalBlock, m_key, m_keyLength, (uint8_t)0x5C);
m_md5.finish(finalBlock + HMAC_BLOCK_SIZE_BYTES);
m_md5.init();
m_md5.process(finalBlock, HMAC_BLOCK_SIZE_BYTES + HMAC_HASH_SIZE_BYTES);
m_md5.finish(dest);
}
void HMAC::finishHex(char* dest) {
uint8_t binDest[HMAC_HASH_SIZE_BYTES];
finish(binDest);
toHex(binDest, dest);
}
void HMAC::toHex(uint8_t* hmac, char* dest) {
static const char hex[17] PROGMEM = "0123456789abcdef";
uint16_t i;
for (i = 0; i < HMAC_HASH_SIZE_BYTES; i++) {
dest[i*2] = pgm_read_byte(&hex[hmac[i] >> 4]);
dest[(i*2) + 1] = pgm_read_byte(&hex[hmac[i] & 0x0F]);
}
dest[HMAC_HASH_SIZE_BYTES * 2] = '\0';
}
/*
* dest MUST be big enough to hold HMAC_BLOCK_SIZE_BYTES
*/
void HMAC::constructKeyPad(uint8_t* dest, const uint8_t* key, uint32_t keyLength, uint8_t padByte) {
if (keyLength > HMAC_BLOCK_SIZE_BYTES) {
// If the key is bigger than 1 block,
// replace the key with the hash of the key.
MD5 md5;
md5.process(key, keyLength);
md5.finish(dest);
keyLength = HMAC_HASH_SIZE_BYTES;
} else {
// If the key length is <= to the HMAC block length,
// just use the key as-is.
memcpy(dest, key, keyLength);
}
// pad the remaining space with 0s
if (keyLength < HMAC_BLOCK_SIZE_BYTES) {
memset(dest + keyLength, 0, HMAC_BLOCK_SIZE_BYTES-keyLength);
}
for (int i = 0; i < HMAC_BLOCK_SIZE_BYTES; i++) {
dest[i] ^= padByte;
}
}

View File

@@ -0,0 +1,57 @@
/*
###############################################################################
#
# 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 TMBHMAC_H_
#define TMBHMAC_H_
#include "tmbmd5.h"
#include "TembooGlobal.h"
#define HMAC_HASH_SIZE_BYTES (MD5_HASH_SIZE_BYTES)
#define HMAC_BLOCK_SIZE_BYTES (MD5_BLOCK_SIZE_BYTES)
#define HMAC_HEX_SIZE_BYTES (HMAC_HASH_SIZE_BYTES * 2)
class HMAC
{
public:
HMAC();
HMAC(const uint8_t* key, uint32_t keyLength);
void init(const uint8_t* key, uint32_t keyLength);
void process(const uint8_t* msg, uint32_t msgLength);
void finish(uint8_t* dest);
void finishHex(char* dest);
enum {
HMAC_OK = 0,
HMAC_ERROR,
HMAC_FAIL_TESTVECTOR
};
private:
MD5 m_md5;
const uint8_t* m_key;
uint32_t m_keyLength;
void constructKeyPad(uint8_t* dest, const uint8_t* key, uint32_t keyLength, uint8_t padByte);
void toHex(uint8_t* hmac, char* dest);
};
#endif

View File

@@ -0,0 +1,213 @@
/*
###############################################################################
#
# 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 <avr/pgmspace.h>
#include "tmbmd5.h"
static const uint8_t Worder[64] PROGMEM = {
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
1,6,11,0,5,10,15,4,9,14,3,8,13,2,7,12,
5,8,11,14,1,4,7,10,13,0,3,6,9,12,15,2,
0,7,14,5,12,3,10,1,8,15,6,13,4,11,2,9
};
static const uint8_t Rorder[64] PROGMEM = {
7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,
5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,
4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,
6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21
};
static const uint32_t Korder[64] PROGMEM = {
0xd76aa478UL, 0xe8c7b756UL, 0x242070dbUL, 0xc1bdceeeUL, 0xf57c0fafUL, 0x4787c62aUL, 0xa8304613UL, 0xfd469501UL,
0x698098d8UL, 0x8b44f7afUL, 0xffff5bb1UL, 0x895cd7beUL, 0x6b901122UL, 0xfd987193UL, 0xa679438eUL, 0x49b40821UL,
0xf61e2562UL, 0xc040b340UL, 0x265e5a51UL, 0xe9b6c7aaUL, 0xd62f105dUL, 0x02441453UL, 0xd8a1e681UL, 0xe7d3fbc8UL,
0x21e1cde6UL, 0xc33707d6UL, 0xf4d50d87UL, 0x455a14edUL, 0xa9e3e905UL, 0xfcefa3f8UL, 0x676f02d9UL, 0x8d2a4c8aUL,
0xfffa3942UL, 0x8771f681UL, 0x6d9d6122UL, 0xfde5380cUL, 0xa4beea44UL, 0x4bdecfa9UL, 0xf6bb4b60UL, 0xbebfbc70UL,
0x289b7ec6UL, 0xeaa127faUL, 0xd4ef3085UL, 0x04881d05UL, 0xd9d4d039UL, 0xe6db99e5UL, 0x1fa27cf8UL, 0xc4ac5665UL,
0xf4292244UL, 0x432aff97UL, 0xab9423a7UL, 0xfc93a039UL, 0x655b59c3UL, 0x8f0ccc92UL, 0xffeff47dUL, 0x85845dd1UL,
0x6fa87e4fUL, 0xfe2ce6e0UL, 0xa3014314UL, 0x4e0811a1UL, 0xf7537e82UL, 0xbd3af235UL, 0x2ad7d2bbUL, 0xeb86d391UL
};
MD5::MD5() {
init();
}
void MD5::init() {
m_state[0] = 0x67452301UL;
m_state[1] = 0xefcdab89UL;
m_state[2] = 0x98badcfeUL;
m_state[3] = 0x10325476UL;
m_bufLength = 0;
m_msgLengthBits = 0;
}
int MD5::compress(const uint8_t* buf) {
uint32_t a;
uint32_t b;
uint32_t c;
uint32_t d;
uint32_t i;
uint32_t W[16];
uint32_t t;
// Copy data into W[0..15] in an endian-agnostic way
for (i = 0; i < 16; i++) {
W[i] = ((uint32_t)(buf[3])) << 24
| ((uint32_t)(buf[2])) << 16
| ((uint32_t)(buf[1])) << 8
| ((uint32_t)(buf[0]));
buf += 4;
}
// copy current state
a = m_state[0];
b = m_state[1];
c = m_state[2];
d = m_state[3];
for (i = 0; i < 16; ++i) {
FF(&a,b,c,d,W[pgm_read_byte(&Worder[i])],pgm_read_byte(&Rorder[i]),pgm_read_dword(&Korder[i]));
t = d; d = c; c = b; b = a; a = t;
}
for (; i < 32; ++i) {
GG(&a,b,c,d,W[pgm_read_byte(&Worder[i])],pgm_read_byte(&Rorder[i]),pgm_read_dword(&Korder[i]));
t = d; d = c; c = b; b = a; a = t;
}
for (; i < 48; ++i) {
HH(&a,b,c,d,W[pgm_read_byte(&Worder[i])],pgm_read_byte(&Rorder[i]),pgm_read_dword(&Korder[i]));
t = d; d = c; c = b; b = a; a = t;
}
for (; i < 64; ++i) {
II(&a,b,c,d,W[pgm_read_byte(&Worder[i])],pgm_read_byte(&Rorder[i]),pgm_read_dword(&Korder[i]));
t = d; d = c; c = b; b = a; a = t;
}
m_state[0] = m_state[0] + a;
m_state[1] = m_state[1] + b;
m_state[2] = m_state[2] + c;
m_state[3] = m_state[3] + d;
return MD5::MD5_OK;
}
int MD5::process (const uint8_t* msg, uint32_t msgLengthBytes) {
uint32_t n;
int err;
if (m_bufLength >= sizeof(m_buf)) {
return MD5::MD5_INVALID_ARG;
}
while (msgLengthBytes > 0) {
// Process the input msg in 64 byte chunks
if (m_bufLength == 0 && msgLengthBytes >= 64) {
err = compress (msg);
if (err != MD5::MD5_OK) {
return err;
}
m_msgLengthBits += 64 * 8;
msg += 64;
msgLengthBytes -= 64;
} else {
n = 64 - m_bufLength;
if (msgLengthBytes < n) {
n = msgLengthBytes;
}
memcpy(m_buf + m_bufLength, msg, (size_t)n);
m_bufLength += n;
msg += n;
msgLengthBytes -= n;
if (m_bufLength == 64) {
err = compress (m_buf);
if (err != MD5::MD5_OK) {
return err;
}
m_msgLengthBits += 64 * 8;
m_bufLength = 0;
}
}
}
return MD5::MD5_OK;
}
int MD5::finish(uint8_t* out) {
int i;
if (m_bufLength >= sizeof(m_buf)) {
return MD5::MD5_INVALID_ARG;
}
m_msgLengthBits += m_bufLength * 8;
// append a '1' bit (right-padded with zeros)
m_buf[m_bufLength++] = (uint8_t)0x80;
// if the bufLength is > 56 bytes, pad with zeros then compress.
// Then fall back to padding with zeros and length encoding like normal.
if (m_bufLength > 56) {
while (m_bufLength < 64) {
m_buf[m_bufLength++] = (uint8_t)0;
}
compress(m_buf);
m_bufLength = 0;
}
// pad with zeroes up to 56 bytes.
// (Why 56? because we store the 8-byte length at the end.)
// (What if bufLength == 56? Perfect! No padding prior to 8-byte length needed.)
while (m_bufLength < 56) {
m_buf[m_bufLength++] = (uint8_t)0;
}
// add the length in an endian-agnostic way
m_buf[56] = (uint8_t)((m_msgLengthBits ) & 255);
m_buf[57] = (uint8_t)((m_msgLengthBits >> 8) & 255);
m_buf[58] = (uint8_t)((m_msgLengthBits >> 16) & 255);
m_buf[59] = (uint8_t)((m_msgLengthBits >> 24) & 255);
m_buf[60] = (uint8_t)((m_msgLengthBits >> 32) & 255);
m_buf[61] = (uint8_t)((m_msgLengthBits >> 40) & 255);
m_buf[62] = (uint8_t)((m_msgLengthBits >> 48) & 255);
m_buf[63] = (uint8_t)((m_msgLengthBits >> 56) & 255);
compress(m_buf);
// copy the state to the output in an endian-agnostic way
for (i = 0; i < 4; i++) {
out[0] = m_state[i] & 255;
out[1] = (m_state[i] >> 8) & 255;
out[2] = (m_state[i] >> 16) & 255;
out[3] = (m_state[i] >> 24) & 255;
out += 4;
}
return MD5::MD5_OK;
}

View File

@@ -0,0 +1,72 @@
/*
###############################################################################
#
# 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 TMBMD5_H_
#define TMBMD5_H_
#include <stdint.h>
#include "TembooGlobal.h"
#define MD5_HASH_SIZE_BITS (128)
#define MD5_HASH_SIZE_BYTES (MD5_HASH_SIZE_BITS/8)
#define MD5_BLOCK_SIZE_BITS (512)
#define MD5_BLOCK_SIZE_BYTES (MD5_BLOCK_SIZE_BITS/8)
#define ROL(x, y) ( (((uint32_t)(x)<<(uint32_t)((y)&31)) | (((uint32_t)(x)&0xFFFFFFFFUL)>>(uint32_t)(32-((y)&31)))) & 0xFFFFFFFFUL)
class MD5 {
public:
MD5();
void init();
int process(const uint8_t* in, uint32_t inlen);
int finish(uint8_t* hash);
enum {
MD5_OK = 0,
MD5_ERROR,
MD5_INVALID_ARG,
MD5_FAIL_TESTVECTOR
};
private:
uint64_t m_msgLengthBits;
uint32_t m_state[4];
uint32_t m_bufLength;
uint8_t m_buf[64];
int compress(const uint8_t* buf);
inline uint32_t F_(uint32_t x, uint32_t y, uint32_t z) { return (z ^ (x & (y ^ z))); }
inline uint32_t G_(uint32_t x, uint32_t y, uint32_t z) { return (y ^ (z & (y ^ x))); }
inline uint32_t H_(uint32_t x, uint32_t y, uint32_t z) { return (x^y^z); }
inline uint32_t I_(uint32_t x, uint32_t y, uint32_t z) { return (y^(x|(~z))); }
inline void FF(uint32_t* a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t s, uint32_t t) { *a = (*a + F_(b,c,d) + M + t); *a = ROL(*a, s) + b; }
inline void GG(uint32_t* a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t s, uint32_t t) { *a = (*a + G_(b,c,d) + M + t); *a = ROL(*a, s) + b; }
inline void HH(uint32_t* a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t s, uint32_t t) { *a = (*a + H_(b,c,d) + M + t); *a = ROL(*a, s) + b; }
inline void II(uint32_t* a, uint32_t b, uint32_t c, uint32_t d, uint32_t M, uint32_t s, uint32_t t) { *a = (*a + I_(b,c,d) + M + t); *a = ROL(*a, s) + b; }
};
#endif