xpl lib
Dependents: XPL-App4_cleanup XPL-App5
xPL.cpp
- Committer:
- richnash
- Date:
- 2018-10-09
- Revision:
- 0:23c0d0e1c31d
File content as of revision 0:23c0d0e1c31d:
/* * xPL.Arduino v0.1, xPL Implementation for Arduino * * This code is parsing a xPL message stored in 'received' buffer * - isolate and store in 'line' buffer each part of the message -> detection of EOL character (DEC 10) * - analyse 'line', function of its number and store information in xpl_header memory * - check for each step if the message respect xPL protocol * - parse each command line * * Copyright (C) 2012 johan@pirlouit.ch, olivier.lebrun@gmail.com * Original version by Gromain59@gmail.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "xPL.h" #define XPL_LINE_MESSAGE_BUFFER_MAX 128 // max length of a line // maximum command in a xpl message #define XPL_END_OF_LINE 10 // define the line number identifier #define XPL_MESSAGE_TYPE_IDENTIFIER 1 #define XPL_OPEN_HEADER 2 #define XPL_HOP_COUNT 3 #define XPL_SOURCE 4 #define XPL_TARGET 5 #define XPL_CLOSE_HEADER 6 #define XPL_SCHEMA_IDENTIFIER 7 #define XPL_OPEN_SCHEMA 8 // Heartbeat request class definition //prog_char XPL_HBEAT_REQUEST_CLASS_ID[] PROGMEM = "hbeat"; //prog_char XPL_HBEAT_REQUEST_TYPE_ID[] PROGMEM = "request"; //prog_char XPL_HBEAT_ANSWER_CLASS_ID[] PROGMEM = "hbeat"; //prog_char XPL_HBEAT_ANSWER_TYPE_ID[] PROGMEM = "basic"; //app, basic #define XPL_HBEAT_REQUEST_CLASS_ID "hbeat" #define XPL_HBEAT_REQUEST_TYPE_ID "request" #define XPL_HBEAT_ANSWER_CLASS_ID "hbeat" #define XPL_HBEAT_ANSWER_TYPE_ID "app" /* xPL Class */ xPL::xPL() { udp_port = XPL_UDP_PORT; SendExternal = NULL; #ifdef ENABLE_PARSING AfterParseAction = NULL; last_heartbeat = 0; hbeat_interval = XPL_DEFAULT_HEARTBEAT_INTERVAL; xpl_accepted = XPL_ACCEPT_ALL; #endif } xPL::~xPL() { } /// Set the source of outgoing xPL messages void xPL::SetSource(const char * _vendorId, const char * _deviceId, const char * _instanceId) { memcpy(source.vendor_id, _vendorId, XPL_VENDOR_ID_MAX); memcpy(source.device_id, _deviceId, XPL_DEVICE_ID_MAX); memcpy(source.instance_id, _instanceId, XPL_INSTANCE_ID_MAX); } /** * \brief Send an xPL message * \details There is no validation of the message, it is sent as is. * \param buffer buffer containing the xPL message. */ void xPL::SendMessage(char *_buffer) { (*SendExternal)(_buffer); } /** * \brief Send an xPL message * \details There is no validation of the message, it is sent as is. * \param message An xPL message. * \param _useDefaultSource if true, insert the default source (defined in SetSource) on the message. */ void xPL::SendMessage(xPL_Message *_message, bool _useDefaultSource) { if(_useDefaultSource) { _message->SetSource(source.vendor_id, source.device_id, source.instance_id); } SendMessage(_message->toString()); } #ifdef ENABLE_PARSING /** * \brief xPL Stuff * \details Send heartbeat messages at "hbeat_interval" interval */ void xPL::Process() { static bool bFirstRun = true; // Check heartbeat + send //if ((millis()-last_heartbeat >= (unsigned long)hbeat_interval * 1000) // || (bFirstRun && millis() > 3000)) if ((clock()-last_heartbeat >= (unsigned long)hbeat_interval * 1000) || (bFirstRun && clock() > 3000)) { SendHBeat(); bFirstRun = false; } } /** * \brief Parse an ingoing xPL message * \details Parse a message, check for hearbeat request and call user defined callback for post processing. * \param buffer buffer of the ingoing UDP Packet */ void xPL::ParseInputMessage(char* _buffer) { xPL_Message* xPLMessage = new xPL_Message(); Parse(xPLMessage, _buffer); // check if the message is an hbeat.request to send a heartbeat if (CheckHBeatRequest(xPLMessage)) { SendHBeat(); } // call the user defined callback to execute an action if(AfterParseAction != NULL) { (*AfterParseAction)(xPLMessage); } delete xPLMessage; } /** * \brief Check the xPL message target * \details Check if the xPL message is for us * \param _message an xPL message */ bool xPL::TargetIsMe(xPL_Message * _message) { if (memcmp(_message->target.vendor_id, source.vendor_id, strlen(source.vendor_id)) != 0) return false; if (memcmp(_message->target.device_id, source.device_id, strlen(source.device_id)) != 0) return false; if (memcmp(_message->target.instance_id, source.instance_id, strlen(source.instance_id)) != 0) return false; return true; } /** * \brief Send a heartbeat message */ void xPL::SendHBeat() { last_heartbeat = clock(); //millis(); char buffer[XPL_MESSAGE_BUFFER_MAX]; // sprintf_P(buffer, PSTR("xpl-stat\n{\nhop=1\nsource=%s-%s.%s\ntarget=*\n}\n%s.%s\n{\ninterval=%d\n}\n"), source.vendor_id, source.device_id, source.instance_id, XPL_HBEAT_ANSWER_CLASS_ID, XPL_HBEAT_ANSWER_TYPE_ID, hbeat_interval); //sprintf(buffer, "xpl-stat\r\n{\r\nhop=1\r\nsource=%s-%s.%s\r\ntarget=*\r\n}\r\n%s.%s\r\n{\r\ninterval=%d\r\nport=3865\r\nremote-ip=8.8.8.8\r\nversion=1.0\r\n}\r\n", source.vendor_id, source.device_id, source.instance_id, XPL_HBEAT_ANSWER_CLASS_ID, XPL_HBEAT_ANSWER_TYPE_ID, hbeat_interval); sprintf(buffer, "xpl-stat\n{\nhop=1\nsource=%s-%s.%s\ntarget=*\n}\n%s.%s\n{\ninterval=%d\nport=3865\nremote-ip=8.8.8.8\nversion=1.0\n}\n", source.vendor_id, source.device_id, source.instance_id, XPL_HBEAT_ANSWER_CLASS_ID, XPL_HBEAT_ANSWER_TYPE_ID, hbeat_interval); //(*SendExternal)(buffer); SendMessage(buffer); printf("XPL: HB Sent\r\n"); } /** * \brief Check if the message is a heartbeat request * \param _message an xPL message */ inline bool xPL::CheckHBeatRequest(xPL_Message* _message) { if (!TargetIsMe(_message)) return false; return _message->IsSchema(XPL_HBEAT_REQUEST_CLASS_ID, XPL_HBEAT_REQUEST_TYPE_ID); } /** * \brief Parse a buffer and generate a xPL_Message * \details Line based xPL parser * \param _xPLMessage the result xPL message * \param _message the buffer */ void xPL::Parse(xPL_Message* _xPLMessage, char* _buffer) { int len = strlen(_buffer); short j=0; short line=0; int result=0; char lineBuffer[XPL_LINE_MESSAGE_BUFFER_MAX+1]; // read each character of the message for(short i = 0; i < len; i++) { // load byte by byte in 'line' buffer, until '\n' is detected if(_buffer[i] == XPL_END_OF_LINE) // is it a linefeed (ASCII: 10 decimal) { ++line; lineBuffer[j]='\0'; // add the end of string id if(line <= XPL_OPEN_SCHEMA) { // first part: header and schema determination // we analyse the line, function of the line number in the xpl message result = AnalyseHeaderLine(_xPLMessage, lineBuffer ,line); } if(line > XPL_OPEN_SCHEMA) { // second part: command line // we analyse the specific command line, function of the line number in the xpl message result = AnalyseCommandLine(_xPLMessage, lineBuffer, line-9, j); if(result == _xPLMessage->command_count+1) break; } if (result < 0) break; j = 0; // reset the buffer pointer clearStr(lineBuffer); // clear the buffer } else { // next character lineBuffer[j++] = _buffer[i]; } } } /** * \brief Parse the header part of the xPL message line by line * \param _xPLMessage the result xPL message * \param _buffer the line to parse * \param _line the line number */ short xPL::AnalyseHeaderLine(xPL_Message* _xPLMessage, char* _buffer, short _line) { switch (_line) { case XPL_MESSAGE_TYPE_IDENTIFIER: //message type identifier if (memcmp(_buffer,"xpl-",4)==0) //xpl { if (memcmp(_buffer+4,"cmnd",4)==0) //command type { _xPLMessage->type=XPL_CMND; //xpl-cmnd } else if (memcmp(_buffer+4,"stat",4)==0) //statut type { _xPLMessage->type=XPL_STAT; //xpl-stat } else if (memcmp(_buffer+4,"trig",4)==0) // trigger type { _xPLMessage->type=XPL_TRIG; //xpl-trig } } else { return 0; //unknown message } return 1; //break; case XPL_OPEN_HEADER: //header begin if (memcmp(_buffer,"{",1)==0) { return 2; } //else //{ return -2; //} //break; case XPL_HOP_COUNT: //hop if (sscanf(_buffer, XPL_HOP_COUNT_PARSER, &_xPLMessage->hop)) { return 3; } //else //{ return -3; //} //break; case XPL_SOURCE: //source if (sscanf(_buffer, XPL_SOURCE_PARSER, &_xPLMessage->source.vendor_id, &_xPLMessage->source.device_id, &_xPLMessage->source.instance_id) == 3) { return 4; } //else //{ return -4; //} //break; case XPL_TARGET: //target if (sscanf(_buffer, XPL_TARGET_PARSER, &_xPLMessage->target.vendor_id, &_xPLMessage->target.device_id, &_xPLMessage->target.instance_id) == 3) { return 5; } //else //{ if(memcmp(_xPLMessage->target.vendor_id,"*", 1) == 0) // check if broadcast message { return 5; } //else //{ return -5; //} //} //break; case XPL_CLOSE_HEADER: //header end if (memcmp(_buffer,"}",1)==0) { return 6; } //else //{ return -6; //} //break; case XPL_SCHEMA_IDENTIFIER: //schema sscanf(_buffer, XPL_SCHEMA_PARSER, &_xPLMessage->schema.class_id, &_xPLMessage->schema.type_id); return 7; case XPL_OPEN_SCHEMA: //header begin if (memcmp(_buffer,"{",1)==0) { return 8; } //else //{ return -8; //} //break; } return -100; } /** * \brief Parse the body part of the xPL message line by line * \param _xPLMessage the result xPL message * \param _buffer the line to parse * \param _command_line the line number */ short xPL::AnalyseCommandLine(xPL_Message * _xPLMessage, char *_buffer, short _command_line, short line_length) { if (memcmp(_buffer,"}",1) == 0) // End of schema { return _xPLMessage->command_count+1; } else // parse the next command { struct_command newcmd; sscanf(_buffer, XPL_COMMAND_PARSER, &newcmd.name, &newcmd.value); _xPLMessage->AddCommand(newcmd.name, newcmd.value); return _command_line; } } #endif