Skip to content

Instantly share code, notes, and snippets.

@mrzechonek
Created February 7, 2020 21:57
Show Gist options
  • Save mrzechonek/d05bc795417e7635f6e0cf5b535af216 to your computer and use it in GitHub Desktop.
Save mrzechonek/d05bc795417e7635f6e0cf5b535af216 to your computer and use it in GitHub Desktop.
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "gsmat.h"
#include "log.h"
#include "utils.h"
/*
* This a parser for Hayes command set used to talk with GSM modem.
*
* Parser works in a push mode, so modem driver needs to continuously read
* characters from modem's UART and feed them into the parser.
*
* Stream received from the modem can take three basic forms: a command response,
* an unsolicited notification and a command result/error code.
*
* Responses and notifications look pretty much the same (from the grammar
* perspective), while a result is a single line with 'OK', 'ERROR' etc. They
* are all reported to upper layer using one of the callbacks provided
* in gsmat_init() function.
*
* Because of this grammar ambiguity, that parser alone cannot distinguish
* between command response and unsolicited notification, and the modem driver
* needs a state machine to determine whether we issued a command and are
* waiting for response, or didn't, and are waiting for a notification.
*
* What's worse, ZTE's MF206A modem sometimes sends notifications while the
* command is being issued (there is no internal "guard time" to postpone
* notifications when a command is being read), so it may happen that such a
* notification is mistaken for a command response. There is no way to work
* around it on a driver/parser level, and the application code needs to take
* this possibility into account by checking response content and retrying
* commands.
*
* Also, because we use modem's internal TCP/IP stack, there are a few *very*
* long commands and notifications (notably +ZIPSEND and +ZIPRECV). Because we
* don't have enough RAM to buffer them as regular responses, their content is
* passed to application layer on the fly, using recv_callback(). See _element
* action for details.
*/
#ifdef CONFIG_LOG_GSMAT
#undef LOCAL_LOG_LEVEL
#define LOCAL_LOG_LEVEL GLOBAL_LOG_LEVEL
#undef LOCAL_LOG_PREFIX
#define LOCAL_LOG_PREFIX "GSMAT:"
#endif
struct gsmat_cb
{
/*
* Ragel's state
*/
int cs;
/*
* buffer for "+COMMAND" string, used both in responses and unsolicited
* notifications
*/
char *command;
char command_buf[GSMAT_COMMAND_SIZE];
/* buffer for "OK", "ERROR" etc. */
char *result;
char result_buf[GSMAT_RESULT_SIZE];
/*
* buffer for response/notification arguments
* this is a list of null-delimited strings, ends with two consecutive
* nulls
*/
char *argument;
char argument_buf[GSMAT_ARGUMENT_SIZE];
/*
* internal buffer for a single argument
* this gets copied into argument_buf
*/
char *element;
char element_buf[GSMAT_ELEMENT_SIZE];
/*
* number of arguments accumulated so far
*/
int argument_count;
/*
* called for each response or notification
*/
void (*response_callback)(const char *, const char *);
/*
* called for each result
*/
void (*result_callback)(const char *);
/*
* called for each byte during +ZIPRECV notification
*/
void (*recv_callback)(uint8_t);
uint16_t recv_size;
uint16_t recv_count;
};
static struct gsmat_cb gsmat_cb;
%%{
machine gsmat;
access gsmat_cb.;
action signal_response
{
/*
* Most responses are of "+COMMAND: <arguments>" form, but there are a
* few reported without the "+COMMAND" prefix. In such cases we report
* only the arguments.
*/
if (gsmat_cb.command != gsmat_cb.command_buf || gsmat_cb.argument != gsmat_cb.argument_buf)
{
if ((strcmp_P(gsmat_cb.command_buf, PSTR("+ZIPRECV")) == 0) &&
(gsmat_cb.recv_count != gsmat_cb.recv_size * 2))
{
_ERR("ziprecv:%"PRIu16":%"PRIu16"", gsmat_cb.recv_count, gsmat_cb.recv_size * 2);
}
if (gsmat_cb.response_callback)
{
gsmat_cb.response_callback(gsmat_cb.command_buf, gsmat_cb.argument_buf);
}
}
gsmat_cb.command = gsmat_cb.command_buf;
memset(gsmat_cb.command_buf, 0, sizeof(gsmat_cb.command_buf));
gsmat_cb.argument = gsmat_cb.argument_buf;
memset(gsmat_cb.argument_buf, 0, sizeof(gsmat_cb.argument_buf));
gsmat_cb.element = gsmat_cb.element_buf;
memset(gsmat_cb.element_buf, 0, sizeof(gsmat_cb.element_buf));
gsmat_cb.argument_count = 0;
}
action signal_result
{
if (gsmat_cb.result != gsmat_cb.result_buf)
{
if (gsmat_cb.result_callback)
{
gsmat_cb.result_callback(gsmat_cb.result_buf);
}
}
gsmat_cb.result = gsmat_cb.result_buf;
memset(gsmat_cb.result_buf, 0, sizeof(gsmat_cb.result_buf));
gsmat_cb.argument = gsmat_cb.argument_buf;
memset(gsmat_cb.argument_buf, 0, sizeof(gsmat_cb.argument_buf));
gsmat_cb.element = gsmat_cb.element_buf;
memset(gsmat_cb.element_buf, 0, sizeof(gsmat_cb.element_buf));
gsmat_cb.argument_count = 0;
}
action _command
{
if (gsmat_cb.command < gsmat_cb.command_buf + sizeof(gsmat_cb.command_buf))
{
*gsmat_cb.command++ = fc;
*gsmat_cb.command = '\0';
}
}
action _element
{
static int8_t shift;
static uint8_t c;
/*
* +ZIPRECV's last argument can be very long, so we need to parse
* it on the fly
*/
if (strcmp_P(gsmat_cb.command_buf, PSTR("+ZIPRECV")) == 0)
{
switch (gsmat_cb.argument_count)
{
case 0:
/*
* first argument resets state
*/
c = 0;
shift = 1;
gsmat_cb.recv_size = 0;
gsmat_cb.recv_count = 0;
/* fall-through */
default:
/*
* first 3 arguments are processed as usual
*/
if (gsmat_cb.element < gsmat_cb.element_buf + sizeof(gsmat_cb.element_buf))
{
*gsmat_cb.element++ = fc;
*gsmat_cb.element = '\0';
}
break;
case 3:
/*
* 4th argument denotes data length, in bytes
*/
gsmat_cb.recv_size *= 10;
gsmat_cb.recv_size += fc - '0';
break;
case 4:
/*
* when reading 5th argument, convert each 2 consecutive
* hex digits into a number and pass it to the application
*/
c |= hex2num(fc) << (shift * 4);
gsmat_cb.recv_count++;
if (shift-- == 0)
{
if (gsmat_cb.recv_callback)
{
gsmat_cb.recv_callback(c);
}
c = 0;
shift = 1;
}
break;
}
}
else
{
if (gsmat_cb.element < gsmat_cb.element_buf + sizeof(gsmat_cb.element_buf))
{
*gsmat_cb.element++ = fc;
*gsmat_cb.element = '\0';
}
}
}
action _argument
{
const size_t element_size = gsmat_cb.element - gsmat_cb.element_buf;
/*
* append current element to argument list
*/
if (element_size && (gsmat_cb.argument + element_size < gsmat_cb.argument_buf + sizeof(gsmat_cb.argument_buf)))
{
memcpy(gsmat_cb.argument, gsmat_cb.element_buf, element_size);
gsmat_cb.argument += element_size;
/*
* make sure the argument list is double-terminated
*/
*gsmat_cb.argument++ = '\0';
*gsmat_cb.argument = '\0';
}
gsmat_cb.argument_count++;
gsmat_cb.element = gsmat_cb.element_buf;
memset(gsmat_cb.element_buf, 0, sizeof(gsmat_cb.element_buf));
}
action _result
{
if (gsmat_cb.result < gsmat_cb.result_buf + sizeof(gsmat_cb.result_buf))
{
*gsmat_cb.result++ = fc;
*gsmat_cb.result = '\0';
}
}
crlf = '\r' '\n';
# quoted string can contain characters with backslashes
quoted = '"' (/\\./ | [^"\\])* '"';
range = '(' digit+ '-' digit+ ')';
ip_address = ( digit+ '.' digit+ '.' digit+ '.' digit+ );
list_element = ( (upper | digit)+ | quoted | range | ip_address) $_element %_argument;
list_delimiter = ( ',' space? );
list = list_element ( list_delimiter list_element )*;
result =
( 'OK' | 'ERROR' ) $_result;
prefix = '+';
# response is either a "+COMMAND: <arguments>" or just "<arguments>", but
# second form cannot start with "+COMMAND" and cannot overlap with one of
# result strings
response = (
( prefix ( upper | digit )+ ) $_command ( ':' space list )? |
( ( ^prefix print+ ) - result ) $_element %_argument
);
command_response = (
( response crlf )? @signal_response |
( result crlf )? @signal_result
);
main := ( 'ATE0\r' )? ( crlf command_response )*;
}%%
%%write data;
void gsmat_init(void (*response_callback)(const char *, const char *),
void (*result_callback)(const char *),
void (*recv_callback)(uint8_t))
{
%%write init;
gsmat_cb.command = gsmat_cb.command_buf;
memset(gsmat_cb.command_buf, 0, sizeof(gsmat_cb.command_buf));
gsmat_cb.result = gsmat_cb.result_buf;
memset(gsmat_cb.result_buf, 0, sizeof(gsmat_cb.result_buf));
gsmat_cb.argument = gsmat_cb.argument_buf;
memset(gsmat_cb.argument_buf, 0, sizeof(gsmat_cb.argument_buf));
gsmat_cb.element = gsmat_cb.element_buf;
memset(gsmat_cb.element_buf, 0, sizeof(gsmat_cb.element_buf));
gsmat_cb.argument_count = 0;
if (response_callback)
{
gsmat_cb.response_callback = response_callback;
}
if (result_callback)
{
gsmat_cb.result_callback = result_callback;
}
if (recv_callback)
{
gsmat_cb.recv_callback = recv_callback;
}
}
bool gsmat_write(const char *p_buffer, size_t p_size)
{
const char *p = p_buffer;
const char *pe = p + p_size;
const char *eof = pe;
(void)eof;
while(p != pe)
{
%%write exec;
if(gsmat_cb.cs == %%{ write error; }%%)
{
_ERR("parse: \\x%02hhx (%c)", *p_buffer, *p_buffer);
return false;
}
}
return true;
}
/*
* vim:ft=ragel
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment