538 lines
16 KiB
C++
538 lines
16 KiB
C++
//*******************************************************************************
|
|
/// @file
|
|
/// @brief
|
|
/// SmartThingsThingShield Arduino Library
|
|
/// @section License
|
|
/// (C) Copyright 2013 Physical Graph
|
|
///
|
|
/// Updates by Daniel Ogorchock 12/30/2014 - Arduino Mega 2560 HW Serial support
|
|
/// -Numerous performance and size optimizations (helpful on UNO with only 2K SRAM)
|
|
/// -Arduino UNO should use the SoftwareSerial library Constructor since the UNO has
|
|
/// only one Hardware UART port ("Serial") which is used by the USB port for
|
|
/// programming and debugging typically. UNO can use the Hardware "Serial"
|
|
/// if desired, but USB programming and debug will be troublesome.
|
|
/// Leonardo and Mega can use SoftwareSerial BUT cannot use Pin3 for Rx - use
|
|
/// Pin10 for Rx and add jumper from Pin10 to Pin3.
|
|
/// -Arduino LEONARDO should use the Hardware Serial Constructor since it has 1 UART
|
|
/// separate from the USB port. "Serial1" port uses pins 0(Rx) and 1(Tx).
|
|
/// -Arduino MEGA should use the Hardware Serial Constructor since it has 4 UARTs.
|
|
/// "Serial3" port uses pins 14(Tx) and 15(Rx). Wire Pin14 to Pin2 and Pin15 to Pin3.
|
|
/// -Be certain to not use Pins 2 & 3 in your Arduino sketch for I/O since they are
|
|
/// electrically connected to the ThingShield if set to D2/D3.
|
|
/// -Note - Pin6 is reserved by the ThingShield as well. Best to avoid using it.
|
|
/// -The SoftwareSerial library has the following known limitations:
|
|
/// - If using multiple software serial ports, only one can receive data at a time.
|
|
/// - Not all pins on the Mega and Mega 2560 support change interrupts, so only
|
|
/// the following can be used for RX: 10, 11, 12, 13, 14, 15, 50, 51, 52, 53,
|
|
/// A8(62), A9(63), A10(64), A11(65), A12(66), A13(67), A14(68), A15(69).
|
|
/// - Not all pins on the Leonardo and Micro support change interrupts, so only
|
|
/// the following can be used for RX : 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI).
|
|
/// -2016-06-04 Dan Ogorchock Added improved support for Arduino Leonardo
|
|
/// -2017-02-04 Dan Ogorchock Modified to be a subclass of new SmartThings base class
|
|
/// -2017-02-08 Dan Ogorchock Cleaned up. Now uses HardwareSerial* objects directly.
|
|
//*******************************************************************************
|
|
#include "SmartThingsThingShield.h"
|
|
|
|
namespace st
|
|
{
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::debugPrintBuffer(String prefix, uint8_t * pBuf, uint_fast8_t nBuf)
|
|
{
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(prefix);
|
|
for (uint_fast8_t i = 0; i < nBuf; i++)
|
|
{
|
|
Serial.print(char(pBuf[i]));
|
|
}
|
|
Serial.println();
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
bool SmartThingsThingShield::isRxLine(uint8_t * pLine)
|
|
{
|
|
// line starts with "T00000000:RX"
|
|
return ((pLine[0] == 'T') && (pLine[9] == ':') && (pLine[10] == 'R') && (pLine[11] == 'X'));
|
|
}
|
|
|
|
//*******************************************************************************
|
|
bool SmartThingsThingShield::isAsciiHex(uint8_t ascii)
|
|
{
|
|
bool retVal = false;
|
|
if (
|
|
((ascii >= 'A') && (ascii <= 'F')) ||
|
|
((ascii >= 'a') && (ascii <= 'f')) ||
|
|
((ascii >= '0') && (ascii <= '9'))
|
|
)
|
|
{
|
|
retVal = true;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
//*******************************************************************************
|
|
/// @note this function doesn't check for hex validity before converting
|
|
//*******************************************************************************
|
|
uint8_t SmartThingsThingShield::asciiToHexU8(uint8_t pAscii[2])
|
|
{
|
|
uint8_t hex;
|
|
hex = ((pAscii[0] - (((pAscii[0] >> 6) & 0x1) * 0x37)) & 0xF);
|
|
hex <<= 4;
|
|
hex |= ((pAscii[1] - (((pAscii[1] >> 6) & 0x1) * 0x37)) & 0xF);
|
|
return hex;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
uint_fast8_t SmartThingsThingShield::translatePayload(uint8_t *pBuf, uint_fast8_t nBuf)
|
|
{
|
|
uint_fast8_t payloadLength = 0; // return value
|
|
uint_fast8_t payloadStart = 0;
|
|
uint_fast8_t payloadEnd = 0;
|
|
|
|
uint_fast8_t i;
|
|
|
|
// find [ ] message from the back of the message
|
|
for (i = nBuf - 1; i > 0; i--)
|
|
{
|
|
if (pBuf[i] == ']')
|
|
{
|
|
payloadEnd = i;
|
|
}
|
|
else if (pBuf[i] == '[')
|
|
{
|
|
payloadStart = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F("payload start: "));
|
|
Serial.print(payloadStart);
|
|
Serial.print(F(" end: "));
|
|
Serial.print(payloadEnd);
|
|
Serial.print(F(" : "));
|
|
for (i = payloadStart + 1; i < payloadEnd; i++)
|
|
{
|
|
Serial.print(pBuf[i]);
|
|
Serial.print(' ');
|
|
}
|
|
Serial.println();
|
|
}
|
|
|
|
if ((payloadStart != 0) && (payloadEnd != 0) && (payloadEnd - payloadStart > 4) && (pBuf[payloadStart + 1] == '0') && (pBuf[payloadStart + 2] == 'A'))
|
|
{ // if valid message then parse
|
|
i = payloadStart + 4; // start+3 should be ' '
|
|
while (i < payloadEnd)
|
|
{
|
|
if (pBuf[i] != ' ')
|
|
{
|
|
if (isAsciiHex(pBuf[i]) && isAsciiHex(pBuf[i + 1]))
|
|
{
|
|
pBuf[payloadLength++] = asciiToHexU8(&(pBuf[i++]));
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
pBuf[payloadLength] = 0x0; // null-terminate the string
|
|
return payloadLength;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::_process(void)
|
|
{
|
|
uint32_t nowMilliseconds = millis();
|
|
|
|
if ((nowMilliseconds < _lastShieldMS) || ((nowMilliseconds - _lastShieldMS) > 5000))
|
|
{
|
|
_shieldGetNetworkInfo();
|
|
_lastShieldMS = nowMilliseconds;
|
|
}
|
|
else if ((_networkState == STATE_JOINED) &&
|
|
((nowMilliseconds < _lastPingMS) || ((nowMilliseconds - _lastPingMS) > 60000)))
|
|
{ // ping every minutes or on rollover
|
|
send("ping");
|
|
_lastPingMS = nowMilliseconds;
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::handleLine(void)
|
|
{
|
|
if (_nBufRX > 0)
|
|
{
|
|
if (isRxLine(_pBufRX))
|
|
{
|
|
debugPrintBuffer("->| ", _pBufRX, _nBufRX);
|
|
{
|
|
uint_fast8_t messageBufLength = translatePayload(_pBufRX, _nBufRX);
|
|
|
|
if (messageBufLength > 0)
|
|
{
|
|
debugPrintBuffer("->| payload :: ", (uint8_t *)_pBufRX, messageBufLength);
|
|
|
|
_calloutFunction(String((char *)_pBufRX)); // call out to main application
|
|
}
|
|
else
|
|
{
|
|
debugPrintBuffer("->| no payload from :: ", _pBufRX, _nBufRX);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ //XXX Totally slapped together since this is temp-- will go away with command set change
|
|
uint_fast8_t i = 0;
|
|
bool found = false;
|
|
if (_nBufRX >= 32) //netinfo:0022A3000000B675,E30E,02
|
|
{
|
|
while (i < _nBufRX)
|
|
{
|
|
if (
|
|
(_pBufRX[i] == 'n') &&
|
|
(_pBufRX[i + 1] == 'e') &&
|
|
(_pBufRX[i + 2] == 't') &&
|
|
(_pBufRX[i + 3] == 'i') &&
|
|
(_pBufRX[i + 4] == 'n') &&
|
|
(_pBufRX[i + 5] == 'f') &&
|
|
(_pBufRX[i + 6] == 'o') &&
|
|
(_pBufRX[i + 7] == ':') &&
|
|
(_pBufRX[i + 24] == ',') &&
|
|
(_pBufRX[i + 29] == ',')
|
|
)
|
|
{
|
|
// parse off EUI
|
|
if (
|
|
isAsciiHex(_pBufRX[i + 8]) &&
|
|
isAsciiHex(_pBufRX[i + 9]) &&
|
|
isAsciiHex(_pBufRX[i + 10]) &&
|
|
isAsciiHex(_pBufRX[i + 11]) &&
|
|
isAsciiHex(_pBufRX[i + 12]) &&
|
|
isAsciiHex(_pBufRX[i + 13]) &&
|
|
isAsciiHex(_pBufRX[i + 14]) &&
|
|
isAsciiHex(_pBufRX[i + 15]) &&
|
|
isAsciiHex(_pBufRX[i + 16]) &&
|
|
isAsciiHex(_pBufRX[i + 17]) &&
|
|
isAsciiHex(_pBufRX[i + 18]) &&
|
|
isAsciiHex(_pBufRX[i + 19]) &&
|
|
isAsciiHex(_pBufRX[i + 20]) &&
|
|
isAsciiHex(_pBufRX[i + 21]) &&
|
|
isAsciiHex(_pBufRX[i + 22]) &&
|
|
isAsciiHex(_pBufRX[i + 23]) &&
|
|
|
|
isAsciiHex(_pBufRX[i + 25]) &&
|
|
isAsciiHex(_pBufRX[i + 26]) &&
|
|
isAsciiHex(_pBufRX[i + 27]) &&
|
|
isAsciiHex(_pBufRX[i + 28]) &&
|
|
|
|
isAsciiHex(_pBufRX[i + 30]) &&
|
|
isAsciiHex(_pBufRX[i + 31])
|
|
)
|
|
{
|
|
uint8_t tempNetStat = asciiToHexU8(&(_pBufRX[i + 30]));
|
|
if (tempNetStat <= STATE_LEAVING) // make sure it maps to the enum
|
|
{
|
|
_networkState = (SmartThingsNetworkState_t)tempNetStat;
|
|
|
|
_nodeID = asciiToHexU8(&(_pBufRX[i + 25]));
|
|
_nodeID <<= 8;
|
|
_nodeID |= asciiToHexU8(&(_pBufRX[i + 27]));
|
|
|
|
_eui64[7] = asciiToHexU8(&(_pBufRX[i + 8]));
|
|
_eui64[6] = asciiToHexU8(&(_pBufRX[i + 10]));
|
|
_eui64[5] = asciiToHexU8(&(_pBufRX[i + 12]));
|
|
_eui64[4] = asciiToHexU8(&(_pBufRX[i + 14]));
|
|
_eui64[3] = asciiToHexU8(&(_pBufRX[i + 16]));
|
|
_eui64[2] = asciiToHexU8(&(_pBufRX[i + 18]));
|
|
_eui64[1] = asciiToHexU8(&(_pBufRX[i + 20]));
|
|
_eui64[0] = asciiToHexU8(&(_pBufRX[i + 22]));
|
|
|
|
debugPrintBuffer(" |~> ", &(_pBufRX[i]), 32);
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
if (found == false)
|
|
debugPrintBuffer("->| IGNORING :: ", _pBufRX, _nBufRX);
|
|
}
|
|
_nBufRX = 0;
|
|
}
|
|
}
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::_shieldGetNetworkInfo(void)
|
|
{
|
|
_mySerial->print(F("custom netinfo\n"));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F(" |<~ custom netinfo\n"));
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
// Thing API | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|
|
// V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V V
|
|
//*****************************************************************************
|
|
|
|
// SoftwareSerial Constructor
|
|
#ifndef DISABLE_SOFTWARESERIAL
|
|
SmartThingsThingShield::SmartThingsThingShield(uint8_t pinRX, uint8_t pinTX, SmartThingsCallout_t *callout, String shieldType, bool enableDebug, int transmitInterval) :
|
|
SmartThings(callout, shieldType, enableDebug, transmitInterval),
|
|
//_SerialPort(SW_SERIAL),
|
|
_lastPingMS(0xFFFFFF00),
|
|
_lastShieldMS(0xFFFFFF00),
|
|
_networkState(STATE_UNKNOWN),
|
|
_nBufRX(0)
|
|
{
|
|
_mySerial = new SoftwareSerial(pinRX, pinTX);
|
|
_mySerial->begin(2400);
|
|
}
|
|
#else
|
|
//Hardware Serial Constructor
|
|
SmartThingsThingShield::SmartThingsThingShield(HardwareSerial* hwSerialPort, SmartThingsCallout_t *callout, String shieldType, bool enableDebug, int transmitInterval) :
|
|
SmartThings(callout, shieldType, enableDebug, transmitInterval),
|
|
_mySerial(hwSerialPort),
|
|
_lastPingMS(0xFFFFFF00),
|
|
_lastShieldMS(0xFFFFFF00),
|
|
_networkState(STATE_UNKNOWN),
|
|
_nBufRX(0)
|
|
{
|
|
// _isDebugEnabled = hwSerialPort != Serial ? enableDebug : false; //Do not allow debug print statements if using Hardware Serial (pins 0,1) for ST Communications
|
|
_mySerial->begin(2400);
|
|
}
|
|
#endif
|
|
//*****************************************************************************
|
|
//SmartThings::~SmartThings()
|
|
SmartThingsThingShield::~SmartThingsThingShield()
|
|
{
|
|
}
|
|
|
|
//*******************************************************************************
|
|
/// Initialize SmartThings Thingshield Library
|
|
//*******************************************************************************
|
|
void SmartThingsThingShield::init(void)
|
|
{
|
|
unsigned long tmpTime = millis();
|
|
do
|
|
{
|
|
//updateNetworkState();
|
|
run();
|
|
delay(10);
|
|
updateNetworkState();
|
|
} while ((_networkState != STATE_JOINED) && ((millis() - tmpTime) < 5000));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
if (_networkState != STATE_JOINED)
|
|
{
|
|
Serial.println(F("SmartThingsThingShield: Intialization timed out waiting for shield to connect."));
|
|
}
|
|
else
|
|
{
|
|
Serial.println(F("SmartThingsThingShield: Intialization Successful. Shield connected."));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::run(void)
|
|
{
|
|
uint8_t readByte;
|
|
|
|
while ((_nBufRX < SMARTTHINGS_RX_BUFFER_SIZE) && _mySerial->available())
|
|
{
|
|
|
|
readByte = _mySerial->read();
|
|
|
|
if ((readByte == 0x0D) || (readByte == 0x0A))
|
|
{ // handle data from SmartThing Hub line-by-line, execute user's callback function for each line
|
|
handleLine();
|
|
}
|
|
else
|
|
{
|
|
// keep track of everything that comes in until we reach a newline
|
|
// TODO(cwvh): validate bufferlength 1988-10-19
|
|
//if (_nBufRX > 200)
|
|
// panic("too many characters!");
|
|
_pBufRX[_nBufRX++] = readByte;
|
|
}
|
|
|
|
}
|
|
_process();
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::send(String message)
|
|
{
|
|
// e.g. thing.print("raw 0x0 {00 00 0A 0A 62 75 74 74 6f 6e 20 64 6f 77 6e }");
|
|
_mySerial->print(F("raw 0x0 { 00 00 0A 0A "));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F("<-| raw 0x0 { 00 00 0A 0A "));
|
|
}
|
|
|
|
for (int i = 0; i < message.length(); i++)
|
|
{
|
|
_mySerial->print(message[i], HEX);
|
|
_mySerial->print(' ');
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(message[i], HEX);
|
|
Serial.print(' ');
|
|
}
|
|
}
|
|
|
|
_mySerial->print(F("}\nsend 0x0 1 1\n"));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F("}\nsend 0x0 1 1\n"));
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::shieldSetLED(uint8_t red, uint8_t green, uint8_t blue)
|
|
{
|
|
if (red > 9) red = 9;
|
|
if (green > 9) green = 9;
|
|
if (blue > 9) blue = 9;
|
|
|
|
_mySerial->print(F("custom rgb "));
|
|
_mySerial->write((red + '0'));
|
|
_mySerial->print(' ');
|
|
_mySerial->write((green + '0'));
|
|
_mySerial->print(' ');
|
|
_mySerial->write((blue + '0'));
|
|
_mySerial->print(F(" \n"));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F(" |<~ custom rgb "));
|
|
Serial.write(red + '0');
|
|
Serial.print(' ');
|
|
Serial.write(green + '0');
|
|
Serial.print(' ');
|
|
Serial.write(blue + '0');
|
|
Serial.print(F(" \n"));
|
|
}
|
|
}
|
|
|
|
|
|
//*****************************************************************************
|
|
SmartThingsNetworkState_t SmartThingsThingShield::shieldGetLastNetworkState(void)
|
|
{
|
|
return _networkState;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
SmartThingsNetworkState_t SmartThingsThingShield::shieldGetNetworkState(void)
|
|
{
|
|
_shieldGetNetworkInfo();
|
|
return _networkState;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
uint16_t SmartThingsThingShield::shieldGetNodeID(void)
|
|
{
|
|
_shieldGetNetworkInfo();
|
|
return _nodeID;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::shieldGetEUI64(uint8_t eui[8])
|
|
{
|
|
_shieldGetNetworkInfo();
|
|
{
|
|
uint_fast8_t i = 7;
|
|
do
|
|
{
|
|
eui[i] = _eui64[i];
|
|
} while (i--);
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::shieldFindNetwork(void)
|
|
{
|
|
_mySerial->print(F("custom find\n"));
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F(" |<~ custom find\n"));
|
|
|
|
}
|
|
}
|
|
|
|
//*****************************************************************************
|
|
void SmartThingsThingShield::shieldLeaveNetwork(void)
|
|
{
|
|
_mySerial->print(F("custom leave\n"));
|
|
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F(" |<~ custom leave\n"));
|
|
}
|
|
}
|
|
|
|
//*******************************************************************************
|
|
// Update the network State/LED
|
|
//*******************************************************************************
|
|
void SmartThingsThingShield::updateNetworkState() //get the current zigbee network status of the ST Shield
|
|
{
|
|
static SmartThingsNetworkState_t tempState = shieldGetLastNetworkState();
|
|
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.println(F("Called updateNetworkState()"));
|
|
}
|
|
|
|
if (tempState != _networkState)
|
|
{
|
|
if (_isDebugEnabled)
|
|
{
|
|
Serial.print(F("Called updateNetworkState() tmpState ="));
|
|
Serial.println(tempState);
|
|
}
|
|
|
|
switch (_networkState)
|
|
{
|
|
case STATE_NO_NETWORK:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: NO_NETWORK"));
|
|
shieldSetLED(2, 0, 0); // red
|
|
break;
|
|
case STATE_JOINING:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: JOINING"));
|
|
shieldSetLED(2, 0, 0); // red
|
|
break;
|
|
case STATE_JOINED:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: JOINED"));
|
|
shieldSetLED(0, 0, 0); // off
|
|
break;
|
|
case STATE_JOINED_NOPARENT:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: JOINED_NOPARENT"));
|
|
shieldSetLED(2, 0, 2); // purple
|
|
break;
|
|
case STATE_LEAVING:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: LEAVING"));
|
|
shieldSetLED(2, 0, 0); // red
|
|
break;
|
|
default:
|
|
case STATE_UNKNOWN:
|
|
if (_isDebugEnabled) Serial.println(F("Everything: UNKNOWN"));
|
|
shieldSetLED(0, 2, 0); // green
|
|
break;
|
|
}
|
|
_networkState = tempState;
|
|
}
|
|
}
|
|
|
|
} |