st-anything/lib/SmartThings/SmartThingsThingShield.cpp

538 lines
16 KiB
C++
Raw Normal View History

2023-03-11 14:11:03 +00:00
//*******************************************************************************
/// @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;
}
}
}