Revision:
0:0fc77069cf70
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xPL.cpp	Mon Jul 30 14:10:27 2018 +0000
@@ -0,0 +1,399 @@
+/*
+ * 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);
+    
+    //(*SendExternal)(buffer);
+    SendMessage(buffer);
+}
+
+/**
+ * \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