Richard Nash / xpl

Dependents:   XPL-App4_cleanup XPL-App5

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers xPL.cpp Source File

xPL.cpp

00001 /*
00002  * xPL.Arduino v0.1, xPL Implementation for Arduino
00003  *
00004  * This code is parsing a xPL message stored in 'received' buffer
00005  * - isolate and store in 'line' buffer each part of the message -> detection of EOL character (DEC 10)
00006  * - analyse 'line', function of its number and store information in xpl_header memory
00007  * - check for each step if the message respect xPL protocol
00008  * - parse each command line
00009  *
00010  * Copyright (C) 2012 johan@pirlouit.ch, olivier.lebrun@gmail.com
00011  * Original version by Gromain59@gmail.com
00012  *
00013  * This program is free software; you can redistribute it and/or
00014  * modify it under the terms of the GNU General Public License
00015  * as published by the Free Software Foundation; either version 2
00016  * of the License, or (at your option) any later version.
00017  *
00018  * This program is distributed in the hope that it will be useful,
00019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00021  * GNU General Public License for more details.
00022  *
00023  * You should have received a copy of the GNU General Public License
00024  * along with this program; if not, write to the Free Software
00025  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00026 */
00027  
00028 #include "xPL.h"
00029 
00030 #define XPL_LINE_MESSAGE_BUFFER_MAX         128 // max length of a line         // maximum command in a xpl message
00031 #define XPL_END_OF_LINE                     10
00032 
00033 // define the line number identifier
00034 #define XPL_MESSAGE_TYPE_IDENTIFIER         1
00035 #define XPL_OPEN_HEADER                     2
00036 #define XPL_HOP_COUNT                       3
00037 #define XPL_SOURCE                          4
00038 #define XPL_TARGET                          5
00039 #define XPL_CLOSE_HEADER                    6
00040 #define XPL_SCHEMA_IDENTIFIER               7
00041 #define XPL_OPEN_SCHEMA                     8
00042 
00043 // Heartbeat request class definition
00044 //prog_char XPL_HBEAT_REQUEST_CLASS_ID[] PROGMEM = "hbeat";
00045 //prog_char XPL_HBEAT_REQUEST_TYPE_ID[] PROGMEM = "request";
00046 //prog_char XPL_HBEAT_ANSWER_CLASS_ID[] PROGMEM = "hbeat";
00047 //prog_char XPL_HBEAT_ANSWER_TYPE_ID[] PROGMEM = "basic";  //app, basic
00048 #define XPL_HBEAT_REQUEST_CLASS_ID  "hbeat"
00049 #define XPL_HBEAT_REQUEST_TYPE_ID  "request"
00050 #define XPL_HBEAT_ANSWER_CLASS_ID  "hbeat"
00051 #define XPL_HBEAT_ANSWER_TYPE_ID  "app"
00052 
00053 /* xPL Class */
00054 xPL::xPL()
00055 {
00056   udp_port = XPL_UDP_PORT;
00057   
00058   SendExternal = NULL;
00059 
00060 #ifdef ENABLE_PARSING
00061   AfterParseAction = NULL;
00062 
00063   last_heartbeat = 0;
00064   hbeat_interval = XPL_DEFAULT_HEARTBEAT_INTERVAL;
00065   xpl_accepted = XPL_ACCEPT_ALL;
00066 #endif
00067 }
00068 
00069 xPL::~xPL()
00070 {
00071 }
00072 
00073 /// Set the source of outgoing xPL messages
00074 void xPL::SetSource(const char * _vendorId, const char * _deviceId, const char * _instanceId)
00075 {
00076     memcpy(source.vendor_id, _vendorId, XPL_VENDOR_ID_MAX);
00077     memcpy(source.device_id, _deviceId, XPL_DEVICE_ID_MAX);
00078     memcpy(source.instance_id, _instanceId, XPL_INSTANCE_ID_MAX);
00079 }
00080 
00081 /**
00082  * \brief       Send an xPL message
00083  * \details   There is no validation of the message, it is sent as is.
00084  * \param    buffer         buffer containing the xPL message.
00085  */
00086 void xPL::SendMessage(char *_buffer)
00087 {
00088     (*SendExternal)(_buffer);
00089 }
00090 
00091 /**
00092  * \brief       Send an xPL message
00093  * \details   There is no validation of the message, it is sent as is.
00094  * \param    message                    An xPL message.
00095  * \param    _useDefaultSource  if true, insert the default source (defined in SetSource) on the message.
00096  */
00097 void xPL::SendMessage(xPL_Message *_message, bool _useDefaultSource)
00098 {
00099     if(_useDefaultSource)
00100     {
00101         _message->SetSource(source.vendor_id, source.device_id, source.instance_id);
00102     }
00103 
00104     SendMessage(_message->toString());
00105 }
00106 
00107 #ifdef ENABLE_PARSING
00108 
00109 /**
00110  * \brief       xPL Stuff
00111  * \details   Send heartbeat messages at "hbeat_interval" interval
00112  */
00113 void xPL::Process()
00114 {
00115     static bool bFirstRun = true;
00116 
00117     // Check heartbeat + send
00118     //if ((millis()-last_heartbeat >= (unsigned long)hbeat_interval * 1000)
00119     //      || (bFirstRun && millis() > 3000))
00120     if ((clock()-last_heartbeat >= (unsigned long)hbeat_interval * 1000)
00121           || (bFirstRun && clock() > 3000))
00122     {
00123         SendHBeat();
00124         bFirstRun = false;
00125     }
00126 }
00127 
00128 /**
00129  * \brief       Parse an ingoing xPL message
00130  * \details   Parse a message, check for hearbeat request and call user defined callback for post processing.
00131  * \param    buffer         buffer of the ingoing UDP Packet
00132  */
00133 void xPL::ParseInputMessage(char* _buffer)
00134 {
00135     xPL_Message* xPLMessage = new xPL_Message();
00136     Parse(xPLMessage, _buffer);
00137     
00138     // check if the message is an hbeat.request to send a heartbeat
00139     if (CheckHBeatRequest(xPLMessage))
00140     {
00141         SendHBeat();
00142     }
00143     
00144     // call the user defined callback to execute an action
00145     if(AfterParseAction != NULL)
00146     {
00147       (*AfterParseAction)(xPLMessage);
00148     }
00149     
00150     delete xPLMessage;
00151 }
00152 
00153 /**
00154  * \brief       Check the xPL message target
00155  * \details   Check if the xPL message is for us
00156  * \param    _message         an xPL message
00157  */
00158 bool xPL::TargetIsMe(xPL_Message * _message)
00159 {
00160   if (memcmp(_message->target.vendor_id, source.vendor_id, strlen(source.vendor_id)) != 0)
00161     return false;
00162 
00163   if (memcmp(_message->target.device_id, source.device_id, strlen(source.device_id)) != 0)
00164     return false;
00165 
00166   if (memcmp(_message->target.instance_id, source.instance_id, strlen(source.instance_id)) != 0)
00167     return false;
00168 
00169   return true;
00170 }
00171 
00172 /**
00173  * \brief       Send a heartbeat message
00174   */
00175 void xPL::SendHBeat()
00176 {
00177     last_heartbeat = clock(); //millis();
00178     char buffer[XPL_MESSAGE_BUFFER_MAX];
00179     
00180 //  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);
00181     //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);
00182     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);
00183     
00184     //(*SendExternal)(buffer);
00185     SendMessage(buffer);
00186     
00187     printf("XPL: HB Sent\r\n");
00188 }
00189 
00190 /**
00191  * \brief       Check if the message is a heartbeat request
00192   * \param    _message         an xPL message
00193  */
00194 inline bool xPL::CheckHBeatRequest(xPL_Message* _message)
00195 {
00196   if (!TargetIsMe(_message))
00197     return false;
00198 
00199   return _message->IsSchema(XPL_HBEAT_REQUEST_CLASS_ID, XPL_HBEAT_REQUEST_TYPE_ID);
00200 }
00201 
00202 /**
00203  * \brief       Parse a buffer and generate a xPL_Message
00204  * \details   Line based xPL parser
00205  * \param    _xPLMessage    the result xPL message
00206  * \param    _message         the buffer
00207  */
00208 void xPL::Parse(xPL_Message* _xPLMessage, char* _buffer)
00209 {
00210     int len = strlen(_buffer);
00211 
00212     short j=0;
00213     short line=0;
00214     int result=0;
00215     char lineBuffer[XPL_LINE_MESSAGE_BUFFER_MAX+1];
00216 
00217     // read each character of the message
00218     for(short i = 0; i < len; i++)
00219     {
00220         // load byte by byte in 'line' buffer, until '\n' is detected
00221         if(_buffer[i] == XPL_END_OF_LINE) // is it a linefeed (ASCII: 10 decimal)
00222         {
00223             ++line;
00224             lineBuffer[j]='\0'; // add the end of string id
00225             
00226             if(line <= XPL_OPEN_SCHEMA)
00227             {
00228                 // first part: header and schema determination
00229                 // we analyse the line, function of the line number in the xpl message
00230                 result = AnalyseHeaderLine(_xPLMessage, lineBuffer ,line);
00231             }
00232             
00233             if(line > XPL_OPEN_SCHEMA)
00234             {
00235                 // second part: command line
00236                 // we analyse the specific command line, function of the line number in the xpl message
00237                 result = AnalyseCommandLine(_xPLMessage, lineBuffer, line-9, j);
00238 
00239                 if(result == _xPLMessage->command_count+1)
00240                     break;
00241             }
00242             
00243             if (result < 0) break;
00244 
00245             j = 0; // reset the buffer pointer
00246             clearStr(lineBuffer); // clear the buffer
00247         }
00248         else
00249         {
00250             // next character
00251             lineBuffer[j++] = _buffer[i];
00252         }
00253     }
00254 }
00255 
00256 /**
00257  * \brief       Parse the header part of the xPL message line by line
00258  * \param    _xPLMessage    the result xPL message
00259  * \param    _buffer               the line to parse
00260  * \param    _line                 the line number
00261  */
00262 short xPL::AnalyseHeaderLine(xPL_Message* _xPLMessage, char* _buffer, short _line)
00263 {
00264     switch (_line)
00265     {
00266         case XPL_MESSAGE_TYPE_IDENTIFIER: //message type identifier
00267 
00268             if (memcmp(_buffer,"xpl-",4)==0) //xpl
00269             {
00270                 if (memcmp(_buffer+4,"cmnd",4)==0) //command type
00271                 {
00272                     _xPLMessage->type=XPL_CMND;  //xpl-cmnd
00273                 }
00274                 else if (memcmp(_buffer+4,"stat",4)==0) //statut type
00275                 {
00276                     _xPLMessage->type=XPL_STAT;  //xpl-stat
00277                 }
00278                 else if (memcmp(_buffer+4,"trig",4)==0) // trigger type
00279                 {
00280                     _xPLMessage->type=XPL_TRIG;  //xpl-trig
00281                 }
00282             }
00283             else
00284             {
00285                 return 0;  //unknown message
00286             }
00287 
00288             return 1;
00289 
00290             //break;
00291 
00292         case XPL_OPEN_HEADER: //header begin
00293 
00294             if (memcmp(_buffer,"{",1)==0)
00295             {
00296                 return 2;
00297             }
00298             //else
00299             //{
00300                 return -2;
00301             //}
00302 
00303             //break;
00304 
00305         case XPL_HOP_COUNT: //hop
00306             if (sscanf(_buffer, XPL_HOP_COUNT_PARSER, &_xPLMessage->hop))
00307             {
00308                 return 3;
00309             }
00310             //else
00311             //{
00312                 return -3;
00313             //}
00314 
00315             //break;
00316 
00317         case XPL_SOURCE: //source
00318             if (sscanf(_buffer, XPL_SOURCE_PARSER, &_xPLMessage->source.vendor_id, &_xPLMessage->source.device_id, &_xPLMessage->source.instance_id) == 3)
00319             {
00320               return 4;
00321             }
00322             //else
00323             //{
00324               return -4;
00325             //}
00326 
00327             //break;
00328 
00329         case XPL_TARGET: //target
00330 
00331             if (sscanf(_buffer, XPL_TARGET_PARSER, &_xPLMessage->target.vendor_id, &_xPLMessage->target.device_id, &_xPLMessage->target.instance_id) == 3)
00332             {
00333               return 5;
00334             }
00335             //else
00336             //{
00337               if(memcmp(_xPLMessage->target.vendor_id,"*", 1) == 0)  // check if broadcast message
00338               {
00339                   return 5;
00340               }
00341               //else
00342               //{
00343                   return -5;
00344               //}
00345             //}
00346             //break;
00347 
00348         case XPL_CLOSE_HEADER: //header end
00349             if (memcmp(_buffer,"}",1)==0)
00350             {
00351                 return 6;
00352             }
00353             //else
00354             //{
00355                 return -6;
00356             //}
00357 
00358             //break;
00359 
00360         case XPL_SCHEMA_IDENTIFIER: //schema
00361             sscanf(_buffer, XPL_SCHEMA_PARSER, &_xPLMessage->schema.class_id, &_xPLMessage->schema.type_id);
00362             return 7;
00363         
00364         case XPL_OPEN_SCHEMA: //header begin
00365             if (memcmp(_buffer,"{",1)==0)
00366             {
00367                 return 8;
00368             }
00369             //else
00370             //{
00371                 return -8;
00372             //}
00373             //break;
00374     }
00375 
00376     return -100;
00377 }
00378 
00379 /**
00380  * \brief       Parse the body part of the xPL message line by line
00381  * \param    _xPLMessage                       the result xPL message
00382  * \param    _buffer                               the line to parse
00383  * \param    _command_line                 the line number
00384  */
00385 short xPL::AnalyseCommandLine(xPL_Message * _xPLMessage, char *_buffer, short _command_line, short line_length)
00386 {
00387     if (memcmp(_buffer,"}",1) == 0) // End of schema
00388     {
00389         return _xPLMessage->command_count+1;
00390     }
00391     else    // parse the next command
00392     {
00393         struct_command newcmd;
00394         
00395         sscanf(_buffer, XPL_COMMAND_PARSER, &newcmd.name, &newcmd.value);
00396 
00397         _xPLMessage->AddCommand(newcmd.name, newcmd.value);
00398 
00399         return _command_line;
00400     }
00401 }
00402 #endif