mmlib: add mmat, an AT/Hayest command set parser for custom modems
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 10 Apr 2019 15:47:16 +0000 (15:47 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 10 Apr 2019 15:47:16 +0000 (15:47 +0000)
mmsoftware/mmlib/mmat.c [new file with mode: 0644]
mmsoftware/mmlib/mmat.h [new file with mode: 0644]

diff --git a/mmsoftware/mmlib/mmat.c b/mmsoftware/mmlib/mmat.c
new file mode 100644 (file)
index 0000000..cc9fe0a
--- /dev/null
@@ -0,0 +1,300 @@
+
+#include <mmat.h>
+
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#ifdef TEST
+#include <stdio.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+
+static void    at_parse(at_ctx_t *);
+static int     at_parse_uint(at_ctx_t *, const char **, bool);
+
+
+static const char * const at_type_strings[AT_MAX] = {
+       "AT_STANDARD",
+       "AT_EXTENDED",
+       "AT_VENDOR1",
+       "AT_VENDOR2",
+       "AT_V250"
+};
+
+
+void
+at_ctx_init(at_ctx_t *ctx, char *buf, char *buf2, size_t bufmax,
+    int *regs, int regsmax, void (*eval)(at_ctx_t *),
+    void (*setreg)(at_ctx_t *))
+{
+
+       ctx->buf = buf;
+       ctx->buf2 = buf2;
+       ctx->lcmd = NULL;
+       ctx->bufmax = bufmax;
+       *ctx->buf = '\0';
+       ctx->bufn = 0;
+       ctx->reg = ctx->val = 0;
+       ctx->regs = regs;
+       ctx->regsmax = regsmax;
+       ctx->cmd = -1;
+       ctx->cmdtype = AT_STANDARD;
+       ctx->eval = eval;
+       ctx->setreg = setreg;
+}
+
+/*
+ * Should only be called in command mode and only with data read from
+ * the controlling terminal side (user or controlling software).
+ * Internally processes any presumably valid commands, calling user callbacks
+ * to process or notify.
+ */
+void
+at_char(at_ctx_t *ctx, char c)
+{
+
+       /* Full line received? */
+       if (c == '\r') {
+               bool lastcomm = false;
+
+               /* Repeat last command (A/)? */
+               if (ctx->buf2 != NULL && *ctx->buf2 != '\0' &&
+                   ctx->bufn == 2 && ctx->buf[0] == 'A' &&
+                   ctx->buf[1] == '/') {
+                       lastcomm = true;
+                       (void)memcpy(ctx->buf, ctx->buf2, ctx->bufmax);
+                       ctx->bufn = strlen(ctx->buf);
+               }
+
+               /* New AT command? */
+               if (ctx->bufn > 1 && ctx->buf[0] == 'A' && ctx->buf[1] == 'T') {
+                       ctx->buf[ctx->bufn] = '\0';
+                       /* Buffer for last command request */
+                       if (!lastcomm && ctx->buf2 != NULL)
+                               (void)memcpy(ctx->buf2, ctx->buf,
+                                   ctx->bufmax);
+                       at_parse(ctx);
+               }
+
+               /* Reset line, no point in clearing if using last command */
+               if (ctx->buf2 == NULL)
+                       (void)memset(ctx->buf, '\0', ctx->bufmax);
+               ctx->bufn = 0;
+               return;
+       }
+
+       /* Queue new char */
+       if (ctx->bufn < ctx->bufmax)
+               ctx->buf[ctx->bufn++] = c;
+}
+
+const char *
+at_type_str(int t)
+{
+
+       if (t < 0 || t >= AT_MAX)
+               return "AT_ERROR";
+       else
+               return at_type_strings[t];
+}
+
+/*
+ * D<...>              Dial long command
+ * [&|%|\]<l>[0|<n>]   Commands (including &V status)
+ * [+|#]<cmd>          Long V.250+GSM and voice commands
+ * S<n>                        Register select command
+ * =<v>                        Register assignment command
+ */
+static void
+at_parse(at_ctx_t *ctx)
+{
+       const char *cptr = &ctx->buf[2];
+       char lcmd[AT_LCMD_MAX + 1];
+       size_t lcmdn;
+       int type;
+       bool longcmd;
+
+next:
+       ctx->cmd = -1;
+       ctx->cmdtype = type = AT_STANDARD;
+       ctx->lcmd = NULL;
+       *lcmd = '\0';
+       longcmd = false;
+       lcmdn = 0;
+
+       for (; *cptr != '\0'; cptr++) {
+               /* Switch command type accordingly */
+               if (*cptr == '&') {
+                       type = AT_EXTENDED;
+                       continue;
+               }
+               if (*cptr == '%') {
+                       type = AT_VENDOR1;
+                       continue;
+               }
+               if (*cptr == '\\') {
+                       type = AT_VENDOR2;
+                       continue;
+               }
+               if (*cptr == '+' || *cptr == '#') {
+                       type = AT_V250;
+                       longcmd = true;
+                       continue;
+               }
+               if (type == AT_STANDARD) {
+                       /* Register select */
+                       if (*cptr == 'S' && isdigit((int)cptr[1])) {
+                               int reg;
+
+                               cptr++;
+                               if ((reg = at_parse_uint(ctx, &cptr, true)) != -1
+                                   && reg < ctx->regsmax)
+                                       ctx->reg = reg;
+                               continue;
+                       }
+                       /* Register assignment */
+                       if (*cptr == '=' && isdigit((int)cptr[1])) {
+                               int val;
+
+                               cptr++;
+                               if ((val = at_parse_uint(ctx, &cptr, true)) != -1) {
+                                       ctx->regs[ctx->reg] = ctx->val = val;
+                                       ctx->setreg(ctx);
+                               }
+                               continue;
+                       }
+                       /* Special dial command supports parameters */
+                       if (*cptr == 'D' && !longcmd) {
+                               lcmd[0] = 'D';
+                               lcmdn = 1;
+                               longcmd = true;
+                               continue;
+                       }
+               }
+               /* If a long command (ATD, AT+ or AT#) next command delimits */
+               if (longcmd) {
+                       if ((*cptr == '-' || *cptr == '+' || *cptr == '*' ||
+                            *cptr == '#' || *cptr == ',' || *cptr == '.' ||
+                           isdigit((int)*cptr) ||
+                           (isalpha((int)*cptr) && isupper((int)*cptr))) &&
+                           lcmdn < AT_LCMD_MAX) {
+                               lcmd[lcmdn++] = *cptr;
+                               continue;
+                       } else if (lcmdn > 0) {
+                               /* XXX Redundancy with below */
+                               lcmd[lcmdn] = '\0';
+                               ctx->cmd = -1;
+                               ctx->lcmd = lcmd;
+                               ctx->cmdtype = type;
+                               ctx->eval(ctx);
+                               goto next;
+                       }
+               }
+               /* Single-letter command, optional numeric value */
+               if (isalpha((int)*cptr) && isupper((int)*cptr)) {
+                       int val;
+
+                       ctx->cmd = *cptr;
+                       ctx->lcmd = NULL;
+                       ctx->cmdtype = type;
+                       cptr++;
+                       if ((val = at_parse_uint(ctx, &cptr, false)) != -1)
+                               ctx->val = val;
+                       else
+                               ctx->val = 0;
+                       ctx->eval(ctx);
+                       goto next;
+               }
+       }
+       /* End of line but long command remnant */
+       /* XXX Redundancy with above */
+       if (longcmd && lcmdn > 0) {
+               lcmd[lcmdn] = '\0';
+               ctx->cmd = -1;
+               ctx->lcmd = lcmd;
+               ctx->cmdtype = type;
+               ctx->eval(ctx);
+       }
+}
+
+static int
+at_parse_uint(at_ctx_t *ctx, const char **cptr, bool rewind)
+{
+       char *cptr2;
+       unsigned long i;
+       int r;
+
+       errno = 0;
+       i = strtoul(*cptr, &cptr2, 10);
+       if (cptr2 != *cptr && errno != ERANGE && errno != ULONG_MAX) {
+               r = (int)i;
+               if (r < 0)
+                       r = 0;
+       } else
+               r = -1;
+
+       if (rewind && cptr2 >= ctx->buf) 
+               cptr2--;
+       *cptr = cptr2;
+
+       return r;
+}
+
+
+#ifdef TEST
+
+#define AT_BUF_MAX     255
+#define AT_REG_MAX     32
+
+int            main(void);
+static void    ateval(at_ctx_t *);
+static void    atsetreg(at_ctx_t *);
+
+static at_ctx_t atctx;
+static char atbuf[AT_BUF_MAX + 1], atbuf2[AT_BUF_MAX + 1];
+static int atregs[AT_REG_MAX];
+
+static void
+ateval(at_ctx_t *ctx)
+{
+
+       if (ctx->cmd != -1) {
+               (void)printf("eval(type=%s, cmd=%c, val=%d)\n",
+                   at_type_str(ctx->cmdtype), ctx->cmd, ctx->val);
+       } else if (ctx->lcmd != NULL) {
+               (void)printf("eval(type=%s, lcmd=%s)\n",
+                   at_type_str(ctx->cmdtype), ctx->lcmd);
+       }
+}
+
+static void
+atsetreg(at_ctx_t *ctx)
+{
+
+       (void)printf("setreg(reg=%d, val=%d)\n", ctx->reg, ctx->val);
+}
+
+int
+main(void)
+{
+
+       at_ctx_init(&atctx, atbuf, atbuf2, AT_BUF_MAX, atregs, AT_REG_MAX,
+           ateval, atsetreg);
+       for (;;) {
+               int c;
+
+               if ((c = getchar()) == EOF || c == 3)
+                       break;
+               if (c == '\n')
+                       c = '\r';
+               at_char(&atctx, c);
+       }
+
+       exit(EXIT_SUCCESS);
+}
+
+#endif
diff --git a/mmsoftware/mmlib/mmat.h b/mmsoftware/mmlib/mmat.h
new file mode 100644 (file)
index 0000000..a770362
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef __MMAT_H__
+#define __MMAT_H__
+
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+
+#define AT_LCMD_MAX    16
+
+enum at_type {
+       AT_STANDARD = 0,
+       AT_EXTENDED,
+       AT_VENDOR1,
+       AT_VENDOR2,
+       AT_V250,
+       AT_MAX
+};
+
+typedef struct at_ctx at_ctx_t;
+struct at_ctx {
+       char    *buf, *buf2, *lcmd;
+       size_t  bufmax;
+       int     bufn, reg, val, *regs, regsmax, cmd, cmdtype;
+       void    (*eval)(at_ctx_t *);
+       void    (*setreg)(at_ctx_t *);
+};
+
+extern void            at_ctx_init(at_ctx_t *, char *, char *, size_t, int *, int,
+                           void (*)(at_ctx_t *), void (*)(at_ctx_t *));
+extern void            at_char(at_ctx_t *, char);
+extern const char      *at_type_str(int);
+
+
+#endif