Logo Search packages:      
Sourcecode: faumachine version File versions  Download package

serial_modem_fsm.c

/* $Id: serial_modem_fsm.c,v 1.19 2009-01-28 12:59:22 potyra Exp $ 
 *
 *  Final state machine parsing AT commands of the serial modem.
 *
 * Copyright (C) 2003-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */

#ifdef DEFINITIONS

/* type definitions */

/* states of fsm */
enum fsm_states_e {
      STATE_DATA,
      STATE_ESC1,
      STATE_ESC2,
      STATE_COMMAND,
      STATE_COMMAND_RET,
      STATE_WAIT_BEFORE_CMD
};

enum at_parse_states {
      STATE_CMD,
      STATE_PARAM,
      STATE_SPECIAL
};

/* speaker behavior */
enum speaker_ctrl_values {
      SPKR_OFF,
      SPKR_ON_TILL_CARRIER,
      SPKR_ON,
      SPKR_DIAL_OFF
};

#endif /* DEFINITIONS */

#ifdef STATE

/** state of the FSM */
struct {
      bool echo;        /* echo commands */
      bool connected;   /* connected to external s.th. */
      bool results;           /* echo result codes */
      bool res_numeric;       /* res codes in numeric (true) or text form */
      enum speaker_ctrl_values speaker_control;
      unsigned char speaker_volume;
      enum fsm_states_e fsm_state;
      /** when was the last data received? */
      unsigned long long fsm_last_data;
} NAME;

#endif /* STATE */


#ifdef BEHAVIOR

#include <assert.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "glue-log.h"
#include "glue-main.h"

#include "serial_modem_dialout.h"


/* ********************* DEFINITIONS ************************** */

#define CMD_MAXLEN 1024
#define RESULT_LEN   20
/* maximum length of numbers to dial (must be less than CMD_MAXLEN) */
#define MAX_NUM_LEN  10

/* for at-i cmd */
#define IDENTIFY0 "1.0-faum"
#define IDENTIFY1 "0x7fe2a3"
#define IDENTIFY2 "OK"
#define IDENTIFY3 "FAUMachine-Modem v1.0"
#define IDENTIFY4 PACKAGE_VERSION
#define IDENTIFY5 PACKAGE_STRING
#define IDENTIFY6 PACKAGE_NAME
#define IDENTIFY7 VERSION
#define IDENTIFY8 "1.0"
#define IDENTIFY9 "Modem-Simulator"

/* some asciis */
#define ASC_DEL         127
#define ASC_BS          8
#define ASC_LF          10
#define ASC_CR          13
#define ASC_SPACE 32

/* Time of silence for Hayes Escape sequence (in seconds) */
#define HAYES_SILENCE_T 1

/* replies of modem */
static const char modem_results[67][RESULT_LEN] = {
      "OK",                   /* 0 */
      "CONNECT",        /* 1 */
      "RING",                 /* 2 */
      "NO CARRIER",           /* 3 */
      "ERROR",          /* 4 */
      "CONNECT 1200",         /* 5 */
      "NO DIALTONE",          /* 6 */
      "BUSY",                 /* 7 */
      "NO ANSWER",            /* 8 */
      "",               /* 9 */
      "CONNECT 2400",         /* 10 */
      "CONNECT 4800",         /* 11 */
      "CONNECT 9600",         /* 12 */
      "CONNECT 14400",  /* 13 */
      "CONNECT 19200",  /* 14 */
      "",               /* 15 */
      "",               /* 16 */
      "",               /* 17 */
      "CONNECT 57600",  /* 18 */
      "",               /* 19 */
      "",               /* 20 */
      "",               /* 21 */
      "CONNECT 1200/75",      /* 22 */
      "CONNECT 75/1200",      /* 23 */
      "CONNECT 7200",   /* 24 */
      "CONNECT 12000",  /* 25 */
      "",               /* 26 */
      "",               /* 27 */
      "CONNECT 38400",  /* 28 */
      "",               /* 29 */
      "",               /* 30 */
      "CONNECT 115200",       /* 31 */
      "",               /* 32 */
      "CONNECT 33333",  /* 33 */
      "CONNECT 37333",  /* 34 */
      "CONNECT 41333",  /* 35 */
      "CONNECT 42666",  /* 36 */
      "CONNECT 44000",  /* 37 */
      "CONNECT 45333",  /* 38 */
      "CONNECT 46666",  /* 39 */
      "",               /* 40 */
      "",               /* 41 */
      "CONNECT 48000",  /* 42 */
      "CONNECT 49333",  /* 43 */
      "",               /* 44 */
      "RINGBACK",       /* 45 */
      "",               /* 46 */
      "",               /* 47 */
      "",               /* 48 */
      "",               /* 49 */
      "",               /* 50 */
      "",               /* 51 */
      "",               /* 52 */
      "CONNECT 50666",  /* 53 */
      "CONNECT 52000",  /* 54 */
      "CONNECT 53333",  /* 55 */
      "CONNECT 54666",  /* 56 */
      "CONNECT 56000",  /* 57 */
      "CONNECT 57333",  /* 58 */
      "CONNECT 16800",  /* 59 */
      "",               /* 60 */
      "CONNECT 21600",  /* 61 */
      "CONNECT 24000",  /* 62 */
      "CONNECT 26400",  /* 63 */
      "CONNECT 28800",  /* 64 */
      "CONNECT 31200",  /* 65 */
      "CONNECT 33600",  /* 66 */
};


/* ******************** IMPLEMENTAION ************************* */

/** report a result code to pc.
 *  @param cpssp config instance.
 *  @param code result code.
 */
static void
modem_result(struct cpssp *cpssp, unsigned char code) 
{
      assert(code < 65);
      
      if (! cpssp->NAME.results) {
            return;
      }

      if (cpssp->NAME.res_numeric) {
            char res_nr[5];
            snprintf(res_nr, 5, "%d\r", code);
            modem_serial_push_data(res_nr[0]);
      } else {
            char res_code[RESULT_LEN + 5];
            int i;

            snprintf(res_code, RESULT_LEN + 5, "\r\n%s\r\n", 
                        modem_results[code]);
            
            for (i=0; i<strlen(res_code); i++) {
                  modem_serial_push_data(res_code[i]);
            }
      }
}

/** reset the internal state of the fsm */
static void
NAME_(reset)(struct cpssp *cpssp)
{
      cpssp->NAME.fsm_state = STATE_COMMAND;
      cpssp->NAME.echo = true;
      cpssp->NAME.connected = false;
      cpssp->NAME.results = true;
      cpssp->NAME.res_numeric=false;
      cpssp->NAME.speaker_volume = 0;
      cpssp->NAME.speaker_control = SPKR_OFF;
}

/** handle and at command to reset the modem. 
 * @param cpssp config instance
 * @param arg argument to atz command (only 0 supported atm.)
 * @return: numerical result code */
static unsigned char
NAME_(at_reset)(struct cpssp *cpssp, unsigned char arg) 
{
      if (arg == 0) {
            serial_modem_reset(cpssp);
            return RESULT_OK;
      }

      return RESULT_ERROR;
}



static void
modem_connect(struct cpssp *cpssp, unsigned char arg) 
{
      switch (arg) {
      case 0:
            cpssp->NAME.connected = false;
            modem_dialout_hangup();
            serial_modem_reset(cpssp);
            return;
      case 1:
            modem_dialout_lift();
            return;
      default: 
            serial_modem_reset(cpssp);
      }
}

static unsigned char
modem_identify(struct cpssp *cpssp, unsigned char arg)
{
      char ident[42];
      char *id_ptr;
      int i;
      
      id_ptr = ident;
      id_ptr += sprintf(id_ptr, "%s", "\r\n");

      switch(arg) {
      case 0: id_ptr += sprintf(id_ptr, IDENTIFY0);
            break;
      case 1: id_ptr += sprintf(id_ptr, IDENTIFY1);
            break;
      case 2: id_ptr += sprintf(id_ptr, IDENTIFY2);
            break;
      case 3: id_ptr += sprintf(id_ptr, IDENTIFY3);
            break;
      case 4: id_ptr += sprintf(id_ptr, IDENTIFY4);
            break;
      case 5: id_ptr += sprintf(id_ptr, IDENTIFY5);
            break;
      case 6: id_ptr += sprintf(id_ptr, IDENTIFY6);
            break;
      case 7: id_ptr += sprintf(id_ptr, IDENTIFY7);
            break;
      case 8: id_ptr += sprintf(id_ptr, IDENTIFY8);
            break;
      case 9: id_ptr += sprintf(id_ptr, IDENTIFY9);
            break;
      default:
            return RESULT_ERROR;
      }
      
      sprintf(id_ptr, "\r\n");
      
      for (i=0; i<strlen(ident); i++) {
            modem_serial_push_data(ident[i]);
      }
      return RESULT_OK;
}

static unsigned char
modem_speaker_vol(struct cpssp *cpssp, unsigned char arg) 
{
      if (arg < 4) {
            cpssp->NAME.speaker_volume = arg;
            return RESULT_OK;
      }
                  
      return RESULT_ERROR;
}

static void
modem_fsm_dial(struct cpssp *cpssp, char * arg, int len)
{
      unsigned long number;
      char *endptr;
      enum dial_modes mode = DIAL_MODE_TONE; /* default: tone */
      
      if (! len) {
            serial_modem_reset(cpssp);
            return;
      }
      
      /* strip leading ws */
      while ((*arg == ' ') && len) {
            arg++;
            len--;
      }

      if (! len) {
            modem_result(cpssp, RESULT_ERROR);
            return;
      }

      /* pulse or tone prefix? */
      if (arg[0] == 'p') {
            mode = DIAL_MODE_PULSE;
            arg++;
            len--;
      } else if (arg[0] == 't') {
            mode = DIAL_MODE_TONE;
            arg++;
            len--;
      }

      if (MAX_NUM_LEN < len) {
            modem_result(cpssp, RESULT_ERROR);
            return;
      }

      /* this is safe, as long MAX_NUM_LEN < CMD_MAXLEN 
       * (we still handle a buffer of [CMD_MAXLEN]) */
      arg[len] = '\0';

      number = strtol(arg, &endptr, 10);
      if (endptr && *endptr) {
            modem_result(cpssp, RESULT_ERROR);
            return;
      }
      
      modem_dialout_dial(number, mode);
}

static unsigned char
modem_speaker_ctrl(struct cpssp *cpssp, unsigned char arg) 
{
      switch (arg) {
      case 0: cpssp->NAME.speaker_control = SPKR_OFF;
            break;
      case 1: cpssp->NAME.speaker_control = SPKR_ON_TILL_CARRIER;
            break;
      case 2: cpssp->NAME.speaker_control = SPKR_ON;
            break;
      case 3: cpssp->NAME.speaker_control = SPKR_DIAL_OFF;
            break;
      default:
            return RESULT_ERROR;
      }
      return RESULT_OK;
}


/* enter data mode */
static unsigned char
modem_data_mode(struct cpssp *cpssp, unsigned char arg) 
{
      switch(arg) {
      case 0:
      case 1:
            cpssp->NAME.fsm_state = STATE_DATA;
            modem_dialout_set_serbusstate(true);
            break;
      default:
            return RESULT_ERROR;
      }
      return RESULT_OK;
}

/* handle one AT command. (without AT-prefix!) */
static void
handle_at_cmd(struct cpssp *cpssp, char * cmd_buffer, int cmd_counter)
{
      unsigned char result = RESULT_ERROR;
      assert(cmd_counter);

      /* using 0 as default argument, when none given */
      if (cmd_counter < 2) {
            cmd_counter = 2;
            /* cmd_buffer must be at least size 2 */
            cmd_buffer[1] = '0';
      }
      
      switch (cmd_buffer[0]) {
      case 'e':
            if (cmd_buffer[1] == '1') {
                  cpssp->NAME.echo = true;
                  result = RESULT_OK;
            } else if (cmd_buffer[1] == '0') {
                  cpssp->NAME.echo = false;
                  result = RESULT_OK;
            }
            break;
      case 'i':
            result = modem_identify(cpssp, cmd_buffer[1] - 48);
            break;
      case 'h':
            /* FIXME result? */
            modem_connect(cpssp, cmd_buffer[1] - 48);
            return;
      case 'q':
            if (cmd_buffer[1] == '1') {
                  cpssp->NAME.results = false;
                  result = RESULT_OK;
            } else if (cmd_buffer[1] == '0') {
                  cpssp->NAME.results = true;
                  result = RESULT_OK;
            }
            break;
      case 'v':
            if (cmd_buffer[1] == '1') {
                  cpssp->NAME.res_numeric = false;
                  result = RESULT_OK;
                  return;
            } else if (cmd_buffer[1] == '0') {
                  cpssp->NAME.res_numeric = true;
                  result = RESULT_OK;
                  return;
            }
            break;
      case 'z':
            result = NAME_(at_reset)(cpssp, cmd_buffer[1] - 48);
            break;
      case 'l':
            result = modem_speaker_vol(cpssp, cmd_buffer[1] - 48);
            break;
      case 'm':
            result = modem_speaker_ctrl(cpssp, cmd_buffer[1] - 48);
            break;
      case 'd':
            modem_fsm_dial(cpssp, &(cmd_buffer[1]), cmd_counter - 1);
            return;
      case 'o':
            result = modem_data_mode(cpssp, cmd_buffer[1] - 48);
            break;
      default:
            faum_log(FAUM_LOG_DEBUG, "serial-modem", "fsm", 
                  "got non-AT command %s\n", cmd_buffer);
      }

      modem_result(cpssp, result);
}

/* since there may be more than one AT-Commands in one line:
 * split the line up into single command-tokens and execute them.
 */
static void
extract_at_cmds(struct cpssp *cpssp, char *cmd_buffer, int cmd_counter)
{
      int i=0;
      static char mybuf[CMD_MAXLEN] = "";
      int cnt=0;
      unsigned char c;
      enum at_parse_states state=STATE_CMD;

      for (i=0; i < cmd_counter; i++) {
            c = cmd_buffer[i];

            switch (state) {
            case STATE_CMD:
                  if ((97 <= c) && (c <= 172) & (c != 'd')) {
                        mybuf[cnt++] = c;
                        assert(cnt < CMD_MAXLEN - 1);
                        state = STATE_PARAM;
                  } else if (c == 'd') {
                        mybuf[cnt++] = c;
                        assert(cnt < CMD_MAXLEN - 1);
                        state = STATE_SPECIAL;
                  } else {
                        faum_log(FAUM_LOG_DEBUG, "serial-modem", 
                                    "fsm", 
                                    "unhandled: %c -- %d\n", c,
                                    c);
                        modem_result(cpssp, RESULT_ERROR);
                        return;
                  }
                  break;
            case STATE_PARAM: 
                  if ((48 <= c) && (c <= 57)) {
                        /* 0-9 */
                        mybuf[cnt++] = c;
                        assert(cnt < CMD_MAXLEN - 1);
                        state = STATE_PARAM;
                  } else if ((97 <= c) && (c <= 172) && (c != 'd')) {
                        /* a-z... last command had no param */
                        if (cnt) {
                              handle_at_cmd(cpssp, mybuf, cnt);
                              state=STATE_PARAM;
                              cnt=0;
                              mybuf[cnt++] = c;
                        }
                  } else {
                        switch(c) {
                        case ASC_SPACE:
                        case ASC_LF:
                        case ASC_CR:
                              if (cnt) {
                                    handle_at_cmd(cpssp, 
                                          mybuf, 
                                          cnt);
                                    cnt=0;
                                    state = STATE_CMD;
                              }
                              break;
                        case 'd':
                              mybuf[cnt++] = c;
                              assert(cnt < CMD_MAXLEN - 1);
                              state=STATE_SPECIAL;
                        default:
                              modem_result(cpssp, RESULT_ERROR);
                              return;
                        }
                  }
                  break;
            case STATE_SPECIAL:
                  switch(c) {
                  case ASC_SPACE:
                  case ASC_LF:
                  case ASC_CR:
                        if (cnt) {
                              handle_at_cmd(cpssp, mybuf, cnt);
                              cnt=0;
                              state = STATE_CMD;
                        }
                        break;
                  default:
                        mybuf[cnt++] = c;
                        assert(cnt < CMD_MAXLEN - 1);
                        state = STATE_SPECIAL;
                  }
                  break;
            }
      }
      if (cnt) {
            handle_at_cmd(cpssp, mybuf, cnt);
      }
}

/* parse one AT-Command-Line */
static void 
prepare_at_line(struct cpssp *cpssp, char * cmd_buffer, int cmd_counter)
{
      int i;

      /* strip leading whitespace */
      while(cmd_counter) {
            if ((*cmd_buffer == ASC_SPACE) || (*cmd_buffer == ASC_CR)
             || (*cmd_buffer == ASC_LF)) {
                  cmd_counter--;
                  cmd_buffer++;
                  continue;
            }
            break;
      }
      
      /* use lowercase characters */
      for (i=cmd_counter; i--; ) {
            cmd_buffer[i] = tolower(cmd_buffer[i]);
      }
      
      /* nothing to parse? */
      if (! cmd_counter) {
            return;
      }
      
      /* strip leading AT */
      if ((2 < cmd_counter) && (cmd_buffer[0] == 'a') 
       && (cmd_buffer[1] == 't')) {
            /* beginning is AT */
            cmd_buffer += 2;
            cmd_counter -= 2;
            /* there may be more than one AT-Commands in this line... */
            extract_at_cmds(cpssp, cmd_buffer, cmd_counter);
            return;
      } 
      
      if ((cmd_counter == 2) 
       && (cmd_buffer[0] == 'a') 
       && (cmd_buffer[1] == 't')) {
            /* only AT... should give ok and do nothing. */
            modem_result(cpssp, RESULT_OK);
      } else {
            /* non AT-Commands */
            /* FIXME: show result here? */
            modem_result(cpssp, RESULT_ERROR);

            cmd_buffer[cmd_counter] = '\0';
            faum_log(FAUM_LOG_DEBUG, "serial-modem", "fsm", 
                        "got non AT-Command: %s\n.", cmd_buffer);
            return;
      }
}

static void
set_esc_timer(struct cpssp *cpssp) 
{
      cpssp->NAME.fsm_last_data = time_virt();
}

static bool
check_esc_timer(struct cpssp *cpssp)
{
      unsigned long long now;
      unsigned long long diff;
      
      now = time_virt();
      
      diff = (now - cpssp->NAME.fsm_last_data) / TIME_HZ;

      return HAYES_SILENCE_T <= diff;
}

static void
modem_fsm_set_connected(struct cpssp *cpssp)
{
      cpssp->NAME.connected = true;
      cpssp->NAME.fsm_state = STATE_DATA;
      modem_dialout_set_serbusstate(true);
}


/** create the fsm component
 *  @param cpssp config structure 
 */
static void
modem_fsm_create(struct cpssp *cpssp) 
{
      set_esc_timer(cpssp);
}

/** distribute character to modem_fsm from serial interface */
static void
modem_fsm_push_char(struct cpssp *cpssp, const char c)
{
      static int cmd_counter=0;
      static char cmd_buffer[CMD_MAXLEN];

      switch(cpssp->NAME.fsm_state) {
      case STATE_DATA:
            if ((c == '+') && check_esc_timer(cpssp)) {
                  modem_dialout_set_serbusstate(false);
                  cpssp->NAME.fsm_state = STATE_ESC1;
            } else {
                  modem_dialout_set_serbusstate(true);
                  cpssp->NAME.fsm_state = STATE_DATA;
            }
            break;
      case STATE_ESC1:
            if (c == '+') {
                  cpssp->NAME.fsm_state = STATE_ESC2;
            } else {
                  modem_dialout_set_serbusstate(true);
                  modem_dialout_push_char('+');
                  cpssp->NAME.fsm_state = STATE_DATA;
            }
            break;
      case STATE_ESC2:
            if (c == '+') {
                  cpssp->NAME.fsm_state = STATE_WAIT_BEFORE_CMD;
            } else {
                  cpssp->NAME.fsm_state = STATE_DATA;
                  modem_dialout_set_serbusstate(true);
                  modem_dialout_push_char('+');
                  modem_dialout_push_char('+');
            }
            break;
      case STATE_WAIT_BEFORE_CMD:
            if (! check_esc_timer(cpssp)) {
                  cpssp->NAME.fsm_state = STATE_DATA;
                  modem_dialout_set_serbusstate(true);
                  modem_dialout_push_char('+');
                  modem_dialout_push_char('+');
                  modem_dialout_push_char('+');
                  break;
            } else {
                  cpssp->NAME.fsm_state = STATE_COMMAND;
            }
      case STATE_COMMAND:
            if (cpssp->NAME.echo) {
                  modem_serial_push_data(c);
            }

            if ((c == ASC_DEL) || (c == ASC_BS)) {
                  if (0 < cmd_counter) {
                        cmd_counter--;
                  } else {
                  }
                  break;
            }

            if (c == ASC_CR) {
                  cpssp->NAME.fsm_state = STATE_COMMAND;
                  prepare_at_line(cpssp, cmd_buffer, cmd_counter);
                  cmd_counter = 0;
                  return;
                  break;
            }
            
            if ((c != ASC_LF) && (cmd_counter < CMD_MAXLEN - 2)) {
                  cmd_buffer[cmd_counter++] = c;
            } else {
                  cpssp->NAME.fsm_state = STATE_COMMAND_RET;
            }
            break;
      case STATE_COMMAND_RET:
            if (cpssp->NAME.echo) {
                  modem_serial_push_data(c);
            }
            if (c == ASC_CR) {
                  cpssp->NAME.fsm_state = STATE_COMMAND;
                  prepare_at_line(cpssp, cmd_buffer, cmd_counter);
                  cmd_counter = 0;
                  return;
            } else {
                  faum_log(FAUM_LOG_DEBUG, "serial-modem", "fsm",
                              "expecting cr after command lf\n");
            }
      }

      set_esc_timer(cpssp);

      return;
}

/** notify online/offline events from modem_dialout
 * e.g. line broke down
 * @param cpssp config instance
 * @param online state of line */
static void
modem_fsm_set_linestate(struct cpssp *cpssp, bool online)
{
      if (cpssp->NAME.connected == online) {
            faum_log(FAUM_LOG_WARNING, "serial-modem", "fsm", 
                        "set_linestate has no effect\n.");
      } 

      cpssp->NAME.connected = online;
      modem_dialout_set_serbusstate(false);
      cpssp->NAME.fsm_state = STATE_COMMAND;
}

#undef CMD_MAXLEN
#undef RESULT_LEN
#undef MAX_NUM_LEN
#undef IDENTIFY0
#undef IDENTIFY1
#undef IDENTIFY2
#undef IDENTIFY3
#undef IDENTIFY4
#undef IDENTIFY5
#undef IDENTIFY6
#undef IDENTIFY7
#undef IDENTIFY8
#undef IDENTIFY9
#undef ASC_DEL
#undef ASC_BS
#undef ASC_LF
#undef ASC_CR
#undef ASC_SPACE
#undef HAYES_SILENCE_T

#endif /* BEHAVIOR */

Generated by  Doxygen 1.6.0   Back to index