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