// Receive a NET msg from the OVMS server
void net_msg_in(char* msg)
  {
  int k;

  if (net_msg_serverok == 0)
    {
    if (memcmppgm2ram(msg, (char const rom far*)"MP-S 0 ", 7) == 0)
      {
      net_msg_server_welcome(msg+7);
      }
    return; // otherwise ignore it
    }

  // Ok, we've got an encrypted message waiting for work.
  // The following is a nasty hack because base64decode doesn't like incoming
  // messages of length divisible by 4, and is really expecting a CRLF
  // terminated string, so we give it one...
  strcatpgm2ram(msg,(char const rom far*)"\r\n");
  k = base64decode(msg,net_scratchpad);
  RC4_crypt(&rx_crypto1, &rx_crypto2, net_scratchpad, k);
  if (memcmppgm2ram(net_scratchpad, (char const rom far*)"MP-0 ", 5) == 0)
    {
    msg = net_scratchpad+5;
    switch (*msg)
      {
      case 'A': // PING
        strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 a");
        if (net_msg_sendpending==0)
          {
          net_msg_start();
          net_msg_encode_puts();
          net_msg_send();
          }
        break;
      case 'Z': // PEER connection
        if (msg[1] != '0')
          {
          net_apps_connected = 1;
          if (net_msg_sendpending==0)
            {
            net_msg_start();
            net_msg_stat();
            net_msg_gps();
            net_msg_tpms();
            net_msg_firmware();
            net_msg_environment();
            net_msg_send();
            }
          }
        else
          {
          net_apps_connected = 0;
          }
        break;
      }
    }
  }
// <stat> guarded encode the message in net_scratchpad and start the send process
char net_msg_encode_statputs(char stat, WORD *oldcrc)
  {
  WORD newcrc = crc16(net_scratchpad, strlen(net_scratchpad));

  switch (stat)
    {
    case 0:
      // Always output
      net_msg_encode_puts();
      *oldcrc = newcrc;
      break;
    case 1:
      // Guarded output, but net_msg_start() has already been sent
      if (*oldcrc != newcrc)
        {
        net_msg_encode_puts();
        *oldcrc = newcrc;
        }
      break;
    case 2:
      // Guarded output, but net_msg_start() has not yet been sent
      if (*oldcrc != newcrc)
        {
        net_msg_start();
        net_msg_encode_puts();
        *oldcrc = newcrc;
        stat = 1;
        }
      break;
    }
  return stat;
}
////////////////////////////////////////////////////////////////////////
// net_state_ticker60()
// State Model: Per-minute ticker
// This function is called approximately once per minute (since state
// was first entered), and gives the state a timeslice for activity.
//
void net_state_ticker60(void)
  {
  switch (net_state)
    {
    case NET_STATE_READY:
      if (net_msg_sendpending>0)
        {
        net_granular_tick -= 5; // Try again in 5 seconds...
        return;
        }
      if ((net_link==1)&&(net_apps_connected>0))
        {
        net_msg_start();
        net_msg_stat();
        net_msg_gps();
        net_msg_tpms();
        net_msg_environment();
        net_msg_send();
        }
      net_state_vchar = net_state_vchar ^ 1;
      delay100(2);
      net_puts_rom(NET_CREG_CIPSTATUS);
      break;
    }
  }
void net_msg_cmd_do(void)
  {
  CHECKPOINT(0x44)
  delay100(2);

  // commands 40-49 are special AT commands, thus, disable net_msg here
  if ((net_msg_cmd_code < 40) || (net_msg_cmd_code > 49))
    net_msg_start();

    // Execute cmd: ask car module to execute first:
   if ((vehicle_fn_commandhandler == NULL)||
       (! vehicle_fn_commandhandler(TRUE, net_msg_cmd_code,net_msg_cmd_msg)))
     {
     // Car module does not feel responsible, fall back to standard:
     if( !net_msg_cmd_exec() )
       {
       // No standard as well => return "unimplemented"
       STP_UNIMPLEMENTED(net_scratchpad, net_msg_cmd_code);
       net_msg_encode_puts();
       }
     }

   // terminate IPSEND by Ctrl-Z (should this be disabled for commands 40-49 as well?)
   net_msg_send();

#ifdef OVMS_ACCMODULE
   acc_handle_msg(TRUE, net_msg_cmd_code, net_msg_cmd_msg);
#endif

   // clear command
   net_msg_cmd_code = 0;
   net_msg_cmd_msg[0] = 0;
  }
void net_msg_valettrunk(void)
  {
  char *p;

  delay100(2);
  net_msg_start();
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 PATrunk has been opened (valet mode).");
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_alarm(void)
  {
  char *p;

  delay100(2);
  net_msg_start();
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 PAVehicle alarm is sounding!");
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_socalert(void)
  {
  char *s;

  delay100(2);
  net_msg_start();
  s = stp_i(net_scratchpad, "MP-0 PAALERT!!! CRITICAL SOC LEVEL APPROACHED (", car_SOC); // 95%
  s = stp_rom(s, "% SOC)");
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_erroralert(unsigned int errorcode, unsigned long errordata)
  {
  char *s;

  delay100(2);
  net_msg_start();
  s = stp_s(net_scratchpad, "MP-0 PE", car_type);
  s = stp_ul(s, ",", (unsigned long)errorcode);
  s = stp_ul(s, ",", (unsigned long)errordata);
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_alert(void)
  {
  char *p;

  delay100(2);
  net_msg_start();
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 PA");

  switch (car_chargemode)
    {
    case 0x00:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Standard - "); // Charge Mode Standard
      break;
    case 0x01:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Storage - "); // Storage
      break;
    case 0x03:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Range - "); // Range
      break;
    case 0x04:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Performance - "); // Performance
    }
  switch (car_chargestate)
    {
    case 0x01:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Charging"); // Charge State Charging
      break;
    case 0x02:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Charging, Topping off"); // Topping off
      break;
    case 0x04:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Charging Done"); // Done
      break;
    default:
      strcatpgm2ram(net_scratchpad,(char const rom far *)"Charging Stopped"); // Stopped
    }

  strcatpgm2ram(net_scratchpad,(char const rom far *)"\rIdeal Range: "); // Ideal Range
  p = par_get(PARAM_MILESKM);
  if (*p == 'M') // Kmh or Miles
    sprintf(net_msg_scratchpad, (rom far char*)"%u mi", car_idealrange); // Miles
  else
    sprintf(net_msg_scratchpad, (rom far char*)"%u Km", (unsigned int) ((float) car_idealrange * 1.609)); // Kmh
  strcat((char*)net_scratchpad,net_msg_scratchpad);

  strcatpgm2ram(net_scratchpad,(char const rom far *)" SOC: ");
  sprintf(net_msg_scratchpad, (rom far char*)"%u%%", car_SOC); // 95%
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_reply_ussd(char *buf, unsigned char buflen)
{
  // called from net_state_activity()
  // buf contains a "+CUSD:" USSD command result
  // parse and return as command reply:
  char *s, *t = NULL;

  // Server not ready? abort
  // TODO: store, resend when server is connected
  if ((!net_msg_serverok) || (!buf) || (!buflen))
    return;

  // isolate USSD reply text
  if (t = memchr((void *) buf, '"', buflen))
  {
    ++t;
    buflen -= (t - buf);
    buf = t; // start of USSD string
    while ((*t) && (*t != '"') && ((t - buf) < buflen))
    {
      if (*t == ',') // "escape" comma for MP-0
        *t = '.';
      t++;
    }
    *t = 0; // end of USSD string
  }

  // format reply:
  s = stp_i(net_scratchpad, "MP-0 c", CMD_SendUSSD);
  if (t)
    s = stp_s(s, ",0,", buf);
  else
    s = stp_rom(s, ",1,Invalid USSD result");

  // send reply:

  if (net_msg_sendpending > 0)
  {
    delay100(20); // HACK... should buffer & retry later... but RAM is precious
    s = NULL; // flag
  }

  net_msg_start();
  net_msg_encode_puts();
  net_msg_send();

  if (!s)
    delay100(20); // HACK: give modem additional time if there was sendpending>0
}
void net_msg_12v_alert(void)
  {
  char *s;

  delay100(2);
  net_msg_start();
  if (can_minSOCnotified & CAN_MINSOC_ALERT_12V)
    s = stp_l2f(net_scratchpad, "MP-0 PAALERT!!! 12V BATTERY CRITICAL (", car_12vline, 1);
  else
    s = stp_l2f(net_scratchpad, "MP-0 PA12V BATTERY OK (", car_12vline, 1);
  s = stp_l2f(s, "V, ref=", car_12vline_ref, 1);
  s = stp_rom(s, "V)");
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_forward_sms(char *caller, char *SMS)
  {
  //Server not ready, stop sending
  //TODO: store this message inside buffer, resend it when server is connected
  if ((net_msg_serverok == 0)||(net_msg_sendpending)>0)
    return;

  delay100(2);
  net_msg_start();
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 PA");
  strcatpgm2ram(net_scratchpad,(char const rom far*)"SMS FROM: ");
  strcat(net_scratchpad, caller);
  strcatpgm2ram(net_scratchpad,(char const rom far*)" - MSG: ");
  SMS[70]=0; // Hacky limit on the max size of an SMS forwarded
  strcat(net_scratchpad, SMS);
  net_msg_encode_puts();
  net_msg_send();
  }
void net_msg_reply_ussd(char *buf)
{
  // called from net_state_activity()
  // buf contains a "+CUSD:" USSD command result
  // parse and return as command reply:
  char *s, *t = NULL;

  // Server not ready? abort
  // TODO: store, resend when server is connected
  if ((!net_msg_serverok) || (!buf))
    return;

  // isolate USSD reply text
  if (t = strchr(buf, '"'))
  {
    buf = ++t; // start of USSD string
    while ((*t) && (*t != '"'))
    {
      if (*t == ',') // replace comma
        *t = '.';
      t++;
    }
    *t = 0; // end of USSD string
  }

  // format reply:
  s = stp_i(net_scratchpad, "MP-0 c", CMD_SendUSSD);
  if (t)
    s = stp_s(s, ",0,", buf);
  else
    s = stp_rom(s, ",1,Invalid USSD result");

  // send reply:
  if (net_msg_sendpending > 0)
    delay100(20); // HACK... should abort, buffer & retry later...
  net_msg_start();
  net_msg_encode_puts();
  net_msg_send();
}
////////////////////////////////////////////////////////////////////////
// net_state_ticker600()
// State Model: Per-10-minute ticker
// This function is called approximately once per ten minutes (since
// state was first entered), and gives the state a timeslice for activity.
//
void net_state_ticker600(void)
  {
  switch (net_state)
    {
    case NET_STATE_READY:
      if ((net_link==1)&&(net_apps_connected==0))
        {
        if (net_msg_sendpending>0)
          {
          net_granular_tick -= 5; // Try again in 5 seconds...
          }
        else
          {
          net_msg_start();
          net_msg_stat();
          net_msg_gps();
          net_msg_tpms();
          net_msg_environment();
          net_msg_send();
          }
        }
      break;
    }
  }
BOOL net_msg_cmd_exec(void)
  {
  int k;
  char *p, *s;

  delay100(2);

  CHECKPOINT(0x43)

  switch (net_msg_cmd_code)
    {
    case 1: // Request feature list (params unused)
      for (k=0;k<FEATURES_MAX;k++)
        {
          s = stp_i(net_scratchpad, "MP-0 c1,0,", k);
          s = stp_i(s, ",", FEATURES_MAX);
          s = stp_i(s, ",", sys_features[k]);
          net_msg_encode_puts();
        }
      break;

    case 2: // Set feature (params: feature number, value)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the command, and <p> to the param value
        k = atoi(net_msg_cmd_msg);
        if ((k>=0)&&(k<FEATURES_MAX))
          {
          sys_features[k] = atoi(p);
          if (k>=FEATURES_MAP_PARAM) // Top N features are persistent
            par_set(PARAM_FEATURE_S+(k-FEATURES_MAP_PARAM), p);
          if (k == FEATURE_CANWRITE) vehicle_initialise();
          STP_OK(net_scratchpad, net_msg_cmd_code);
          }
        else
          {
          STP_INVALIDRANGE(net_scratchpad, net_msg_cmd_code);
          }
        }
      else
        {
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      break;

    case 3: // Request parameter list (params unused)
      for (k=0;k<PARAM_MAX;k++)
        {
          p = par_get(k);
          if (k==PARAM_SERVERPASS) *p=0; // Don't show netpass1
          s = stp_i(net_scratchpad, "MP-0 c3,0,", k);
          s = stp_i(s, ",", PARAM_MAX);
          s = stp_s(s, ",", p);
          net_msg_encode_puts();
        }
      break;

    case 4: // Set parameter (params: param number, value)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the command, and <p> to the param value
        k = atoi(net_msg_cmd_msg);
        if ((k>=0)&&(k<PARAM_FEATURE_S))
          {
          par_set(k, p);
          STP_OK(net_scratchpad, net_msg_cmd_code);
          if ((k==PARAM_MILESKM) || (k==PARAM_VEHICLETYPE)) vehicle_initialise();
#ifdef OVMS_ACCMODULE
          // Reset the ACC state it an ACC parameter is changed
          if ((k>=PARAM_ACC_S)&&(k<(PARAM_ACC_S+PARAM_ACC_COUNT)))
            acc_state_enter(ACC_STATE_FIRSTRUN);
#endif
          }
        else
          {
          STP_INVALIDRANGE(net_scratchpad, net_msg_cmd_code);
          }
        }
      else
        {
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      break;

    case 5: // Reboot (params unused)
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      net_state_enter(NET_STATE_HARDSTOP);
      break;

    case 6: // CHARGE ALERT (params unused)
      net_msg_alert();
      net_msg_encode_puts();
      break;

    case 7: // SMS command wrapper

      // process command:
      net_msg_bufpos = net_msg_scratchpad;
      k = net_sms_in(par_get(PARAM_REGPHONE), net_msg_cmd_msg);
      net_msg_bufpos = NULL;
      // output is now in net_msg_scratchpad

      // create return string:
      s = stp_i(net_scratchpad, NET_MSG_CMDRESP, net_msg_cmd_code);
      s = stp_i(s, ",", 1-k); // 0=ok 1=error
      if (k)
        {
        *s++ = ',';
        for (p = net_msg_scratchpad; *p; p++)
          {
            if (*p == '\n')
              *s++ = '\r'; // translate LF to CR
            else if (*p == ',')
              *s++ = ';'; // translate , to ;
            else
              *s++ = *p;
          }
        *s = 0;
        }

      // send return string:
      net_msg_encode_puts();
      break;

    case 40: // Send SMS (params: phone number, SMS message)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the phone number, and <p> to the SMS message
        net_send_sms_start(net_msg_cmd_msg);
        net_puts_ram(p);
        net_puts_rom("\x1a");
        delay100(5);
        net_msg_start();
        STP_OK(net_scratchpad, net_msg_cmd_code);
        }
      else
        {
        net_msg_start();
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      delay100(2);
      break;

    case 41: // Send MMI/USSD Codes (param: USSD_CODE)
      net_puts_rom("AT+CUSD=1,\"");
      net_puts_ram(net_msg_cmd_msg);
      net_puts_rom("\",15\r");
      // cmd reply #1 to acknowledge command:
      delay100(5);
      net_msg_start();
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      delay100(2);
      // cmd reply #2 sent on USSD response, see net_msg_reply_ussd()
      break;
      
    case 49: // Send raw AT command (param: raw AT command)
      net_puts_ram(net_msg_cmd_msg);
      net_puts_rom("\r");
      delay100(5);
      net_msg_start();
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      delay100(2);
      break;

  default:
      return FALSE;
    }

  return TRUE;
  }
////////////////////////////////////////////////////////////////////////
// net_state_ticker1()
// State Model: Per-second ticker
// This function is called approximately once per second, and gives
// the state a timeslice for activity.
//
void net_state_ticker1(void)
  {
  char *p;

  switch (net_state)
    {
    case NET_STATE_START:
      net_state_vchar = net_state_vchar ^ 1; // Toggle LED on/off
      led_act(net_state_vchar);
      break;
    case NET_STATE_DOINIT:
      if ((net_timeout_ticks==10)&&(net_timeout_ticks==20))
        net_puts_rom(NET_INIT); // Try again...
      break;   
    case NET_STATE_COPS:
      net_state_vchar = net_state_vchar ^ 1; // Toggle LED on/off
      led_net(net_state_vchar);
      led_act(net_state_vchar^1);
      break;
    case NET_STATE_SOFTRESET:
      net_state_enter(NET_STATE_START);
      break;
    case NET_STATE_READY:
      if (net_watchdog > 0)
        {
        if (--net_watchdog == 0)
          {
          net_state_enter(NET_STATE_COPS); // Reset network connection
          return;
          }
        }
      if (net_msg_sendpending>0)
        {
        net_msg_sendpending++;
        if (net_msg_sendpending>60)
          {
          // Pending for more than 60 seconds..
          net_state_enter(NET_STATE_DONETINIT); // Reset GPRS link
          return;
          }
        }
      if ((net_reg == 0x01)||(net_reg == 0x05))
        {
        if ((net_msg_notify==1)&&(net_msg_serverok==1))
          {
          net_msg_notify = 0;
          delay100(10);
          net_msg_alert();
          return;
          }
        if (net_sms_notify==1)
          {
          net_sms_notify = 0;
          delay100(10);
          p = par_get(PARAM_REGPHONE);
          net_sms_stat(p);
          return;
          }
        if ((net_msg_notifyenvironment==1)&&
            (net_msg_serverok==1)&&
            (net_apps_connected>0)&&
            (net_msg_sendpending==0))
          {
          net_msg_notifyenvironment = 0;
          delay100(10);
          net_msg_start();
          net_msg_environment();
          net_msg_send();
          }
        }
      break;
    }
  }
BOOL net_msg_cmd_exec(void)
  {
  int k;
  char *p, *s;

  delay100(2);

  CHECKPOINT(0x43)

  switch (net_msg_cmd_code)
    {
    case 1: // Request feature list (params unused)
      for (k=0;k<FEATURES_MAX;k++)
        {
          s = stp_i(net_scratchpad, "MP-0 c1,0,", k);
          s = stp_i(s, ",", FEATURES_MAX);
          s = stp_i(s, ",", sys_features[k]);
          net_msg_encode_puts();
        }
      break;

    case 2: // Set feature (params: feature number, value)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the command, and <p> to the param value
        k = atoi(net_msg_cmd_msg);
        if ((k>=0)&&(k<FEATURES_MAX))
          {
          sys_features[k] = atoi(p);
          if (k>=FEATURES_MAP_PARAM) // Top N features are persistent
            par_set(PARAM_FEATURE_S+(k-FEATURES_MAP_PARAM), p);
          if (k == FEATURE_CANWRITE) vehicle_initialise();
          STP_OK(net_scratchpad, net_msg_cmd_code);
          }
        else
          {
          STP_INVALIDRANGE(net_scratchpad, net_msg_cmd_code);
          }
        }
      else
        {
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      break;

    case 3: // Request parameter list (params unused)
      for (k=0;k<PARAM_MAX;k++)
        {
          p = par_get(k);
          if (k==PARAM_SERVERPASS) *p=0; // Don't show netpass1
          s = stp_i(net_scratchpad, "MP-0 c3,0,", k);
          s = stp_i(s, ",", PARAM_MAX);
          s = stp_s(s, ",", p);
          net_msg_encode_puts();
        }
      break;

    case 4: // Set parameter (params: param number, value)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the command, and <p> to the param value
        k = atoi(net_msg_cmd_msg);
        if ((k>=0)&&(k<PARAM_FEATURE_S))
          {
          par_set(k, p);
          STP_OK(net_scratchpad, net_msg_cmd_code);
          if ((k==PARAM_MILESKM) || (k==PARAM_VEHICLETYPE)) vehicle_initialise();
          }
        else
          {
          STP_INVALIDRANGE(net_scratchpad, net_msg_cmd_code);
          }
        }
      else
        {
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      break;

    case 5: // Reboot (params unused)
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      net_state_enter(NET_STATE_HARDSTOP);
      break;

    case 6: // CHARGE ALERT (params unused)
      net_msg_alert();
      net_msg_encode_puts();
      break;

    case 40: // Send SMS (params: phone number, SMS message)
      for (p=net_msg_cmd_msg;(*p != 0)&&(*p != ',');p++) ;
      // check if a value exists and is separated by a comma
      if (*p == ',')
        {
        *p++ = 0;
        // At this point, <net_msg_cmd_msg> points to the phone number, and <p> to the SMS message
        net_send_sms_start(net_msg_cmd_msg);
        net_puts_ram(p);
        net_puts_rom("\x1a");
        delay100(5);
        net_msg_start();
        STP_OK(net_scratchpad, net_msg_cmd_code);
        }
      else
        {
        net_msg_start();
        STP_INVALIDSYNTAX(net_scratchpad, net_msg_cmd_code);
        }
      net_msg_encode_puts();
      delay100(2);
      break;

    case 41: // Send MMI/USSD Codes (param: USSD_CODE)
      net_puts_rom("AT+CUSD=1,\"");
      net_puts_ram(net_msg_cmd_msg);
      net_puts_rom("\",15\r");
      // cmd reply #1 to acknowledge command:
      delay100(5);
      net_msg_start();
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      delay100(2);
      // cmd reply #2 sent on USSD response, see net_msg_reply_ussd()
      break;
      
    case 49: // Send raw AT command (param: raw AT command)
      net_puts_ram(net_msg_cmd_msg);
      net_puts_rom("\r");
      delay100(5);
      net_msg_start();
      STP_OK(net_scratchpad, net_msg_cmd_code);
      net_msg_encode_puts();
      delay100(2);
      break;
    default:
      return FALSE;
    }

  return TRUE;
  }
// Receive a NET msg from the OVMS server
void net_msg_in(char* msg)
  {
  int k;
  char s;

  if (net_msg_serverok == 0)
    {
    if (memcmppgm2ram(msg, (char const rom far*)"MP-S 0 ", 7) == 0)
      {
      net_msg_server_welcome(msg+7);
      net_granular_tick = 3590; // Nasty hack to force a status transmission in 10 seconds
      }
    return; // otherwise ignore it
    }

  // Ok, we've got an encrypted message waiting for work.
  // The following is a nasty hack because base64decode doesn't like incoming
  // messages of length divisible by 4, and is really expecting a CRLF
  // terminated string, so we give it one...
  CHECKPOINT(0x40)
  if (((strlen(msg)*4)/3) >= (NET_BUF_MAX-3))
    {
    // Quick exit to reset link if incoming message is too big
    net_state_enter(NET_STATE_DONETINIT);
    return;
    }
  strcatpgm2ram(msg,(char const rom far*)"\r\n");
  k = base64decode(msg,net_scratchpad);
  CHECKPOINT(0x41)
  RC4_crypt(&rx_crypto1, &rx_crypto2, net_scratchpad, k);
  if (memcmppgm2ram(net_scratchpad, (char const rom far*)"MP-0 ", 5) != 0)
    {
    net_state_enter(NET_STATE_DONETINIT);
    return;
    }
  msg = net_scratchpad+5;

  if ((*msg == 'E')&&(msg[1]=='M'))
    {
    // A paranoid-mode message from the server (or, more specifically, app)
    // The following is a nasty hack because base64decode doesn't like incoming
    // messages of length divisible by 4, and is really expecting a CRLF
    // terminated string, so we give it one...
    msg += 2; // Now pointing to the code just before encrypted paranoid message
    strcatpgm2ram(msg,(char const rom far*)"\r\n");
    k = base64decode(msg+1,net_msg_scratchpad+1);
    RC4_setup(&pm_crypto1, &pm_crypto2, pdigest, MD5_SIZE);
    for (k=0;k<1024;k++)
      {
      net_scratchpad[0] = 0;
      RC4_crypt(&pm_crypto1, &pm_crypto2, net_scratchpad, 1);
      }
    RC4_crypt(&pm_crypto1, &pm_crypto2, net_msg_scratchpad+1, k);
    net_msg_scratchpad[0] = *msg; // The code
    // The message is now out of paranoid mode...
    msg = net_msg_scratchpad;
    }

  CHECKPOINT(0x42)
  switch (*msg)
    {
    case 'A': // PING
      strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 a");
      if (net_msg_sendpending==0)
        {
        net_msg_start();
        net_msg_encode_puts();
        net_msg_send();
        }
      break;
    case 'Z': // PEER connection
      if (msg[1] != '0')
        {
        net_apps_connected = 1;
        if (net_msg_sendpending==0)
          {
          net_msg_start();
          net_msgp_stat(0);
          net_msgp_gps(0);
          net_msgp_tpms(0);
          net_msgp_firmware(0);
          net_msgp_environment(0);
          net_msg_send();
          }
        }
      else
        {
        net_apps_connected = 0;
        }
      break;
    case 'h': // Historical data acknowledgement
#ifdef OVMS_LOGGINGMODULE
      logging_ack(atoi(msg+1));
#endif // #ifdef OVMS_LOGGINGMODULE
      break;
    case 'C': // COMMAND
      net_msg_cmd_in(msg+1);
      if (net_msg_sendpending==0) net_msg_cmd_do();
      break;
    }
  }
void net_msg_server_welcome(char *msg)
  {
  // The server has sent a welcome (token <space> base64digest)
  char *d,*p,*s;
  int k;
  unsigned char hwv = 1;

  #ifdef OVMS_HW_V2
  hwv = 2;
  #endif

  if( !msg ) return;
  for (d=msg;(*d != 0)&&(*d != ' ');d++) ;
  if (*d != ' ') return;
  *d++ = 0;

  // At this point, <msg> is token, and <x> is base64digest
  // (both null-terminated)

  // Check for token-replay attack
  if (strcmp(token,msg)==0)
    return; // Server is using our token!

  // Validate server token
  p = par_get(PARAM_SERVERPASS);
  hmac_md5(msg, strlen(msg), p, strlen(p), digest);
  base64encode(digest, MD5_SIZE, net_scratchpad);
  if (strcmp(d,net_scratchpad)!=0)
    return; // Invalid server digest

  // Ok, at this point, our token is ok
  strcpy(net_scratchpad,msg);
  strcat(net_scratchpad,token);
  hmac_md5(net_scratchpad,strlen(net_scratchpad),p,strlen(p),digest);

  // Setup, and prime the rx and tx cryptos
  RC4_setup(&rx_crypto1, &rx_crypto2, digest, MD5_SIZE);
  for (k=0;k<1024;k++)
    {
    net_scratchpad[0] = 0;
    RC4_crypt(&rx_crypto1, &rx_crypto2, net_scratchpad, 1);
    }
  RC4_setup(&tx_crypto1, &tx_crypto2, digest, MD5_SIZE);
  for (k=0;k<1024;k++)
    {
    net_scratchpad[0] = 0;
    RC4_crypt(&tx_crypto1, &tx_crypto2, net_scratchpad, 1);
    }

  net_msg_serverok = 1;

  p = par_get(PARAM_PARANOID);
  if (*p == 'P')
    {
    // Paranoid mode initialisation
    if (ptokenmade==0)
      {
      // We need to make the ptoken
      for (k=0;k<TOKEN_SIZE;k++)
        {
        ptoken[k] = cb64[rand()%64];
        }
      ptoken[TOKEN_SIZE] = 0;
      }

    // To be truly paranoid, we must send the paranoid token to the server ;-)
    ptokenmade=0; // Leave it off for the MP-0 ET message
    strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 ET");
    strcat(net_scratchpad,ptoken);
    net_msg_start();
    net_msg_encode_puts();
    net_msg_send();
    ptokenmade=1; // And enable paranoid mode from now on...

    // And calculate the pdigest for future use
    p = par_get(PARAM_MODULEPASS);
    hmac_md5(ptoken, strlen(ptoken), p, strlen(p), pdigest);
    }
  else
    {
    ptokenmade = 0; // This disables paranoid mode
  }


  /* DEBUG / QA stats: Send crash counter and last reason:
   *
   * MP-0 H*-OVM-DebugCrash,0,2592000
   *  ,<firmware_version>/<vehicle_type><vehicle_version>/V<hardware_version>
   *  ,<crashcnt>,<crashreason>,<checkpoint>
   */

  if (debug_crashreason & 0x80)
    {
    debug_crashreason &= ~0x80; // clear checkpoint hold bit

    s = stp_i(net_scratchpad, "MP-0 H*-OVM-DebugCrash,0,2592000,", ovms_firmware[0]);
    s = stp_i(s, ".", ovms_firmware[1]);
    s = stp_i(s, ".", ovms_firmware[2]);
    s = stp_s(s, "/", par_get(PARAM_VEHICLETYPE));
    if (vehicle_version)
       s = stp_rom(s, vehicle_version);
    s = stp_i(s, "/V", hwv);
    s = stp_i(s, ",", debug_crashcnt);
    s = stp_x(s, ",", debug_crashreason);
    s = stp_i(s, ",", debug_checkpoint);

    delay100(20);
    net_msg_start();
    net_msg_encode_puts();
    net_msg_send();
    }
#ifdef OVMS_LOGGINGMODULE
  logging_serverconnect();
#endif // #ifdef OVMS_LOGGINGMODULE
}
void net_msg_server_welcome(char *msg)
  {
  // The server has sent a welcome (token <space> base64digest)
  char *d,*p;
  int k;

  for (d=msg;(*d != 0)&&(*d != ' ');d++) ;
  if (*d != ' ') return;
  *d++ = 0;

  // At this point, <msg> is token, and <x> is base64digest
  // (both null-terminated)

  // Check for token-replay attack
  if (strcmp(token,msg)==0)
    return; // Server is using our token!

  // Validate server token
  p = par_get(PARAM_NETPASS1);
  hmac_md5(msg, strlen(msg), p, strlen(p), digest);
  base64encode(digest, MD5_SIZE, net_scratchpad);
  if (strcmp(d,net_scratchpad)!=0)
    return; // Invalid server digest

  // Ok, at this point, our token is ok
  strcpy(net_scratchpad,msg);
  strcat(net_scratchpad,token);
  hmac_md5(net_scratchpad,strlen(net_scratchpad),p,strlen(p),digest);

  // Setup, and prime the rx and tx cryptos
  RC4_setup(&rx_crypto1, &rx_crypto2, digest, MD5_SIZE);
  for (k=0;k<1024;k++)
    {
    net_scratchpad[0] = 0;
    RC4_crypt(&rx_crypto1, &rx_crypto2, net_scratchpad, 1);
    }
  RC4_setup(&tx_crypto1, &tx_crypto2, digest, MD5_SIZE);
  for (k=0;k<1024;k++)
    {
    net_scratchpad[0] = 0;
    RC4_crypt(&tx_crypto1, &tx_crypto2, net_scratchpad, 1);
    }

  net_msg_serverok = 1;

  p = par_get(PARAM_PARANOID);
  if (*p == 'P')
    {
    // Paranoid mode initialisation
    if (ptokenmade==0)
      {
      // We need to make the ptoken
      for (k=0;k<TOKEN_SIZE;k++)
        {
        ptoken[k] = cb64[rand()%64];
        }
      ptoken[TOKEN_SIZE] = 0;
      }

    // To be truly paranoid, we must send the paranoid token to the server ;-)
    ptokenmade=0; // Leave it off for the MP-0 ET message
    strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 ET");
    strcat(net_scratchpad,ptoken);
    net_msg_start();
    net_msg_encode_puts();
    net_msg_send();
    ptokenmade=1; // And enable paranoid mode from now on...

    // And calculate the pdigest for future use
    p = par_get(PARAM_REGPASS);
    hmac_md5(ptoken, strlen(ptoken), p, strlen(p), pdigest);
    }
  else
    {
    ptokenmade = 0; // This disables paranoid mode
    }
  }
////////////////////////////////////////////////////////////////////////
// net_state_activity()
// State Model: Some async data has been received
// This is called to indicate to the state that a complete piece of async
// data has been received in net_buf (with length net_buf_pos, and mode
// net_buf_mode), and the data should be completely handled before
// returning.
//
void net_state_activity()
  {
  if (net_buf_mode == NET_BUF_SMS)
    {
    // An SMS has arrived, and net_caller has been primed
    if ((net_reg != 0x01)&&(net_reg != 0x05))
      { // Treat this as a network registration
      net_watchdog=0; // Disable watchdog, as we have connectivity
      net_reg = 0x05;
      led_net(1);
      }
    net_sms_in(net_caller,net_buf,net_buf_pos);
    return;
    }
  else if (net_buf_mode != NET_BUF_CRLF)
    {
    // An IP data message has arrived
    net_msg_in(net_buf);
    return;
    }

  switch (net_state)
    {
    case NET_STATE_DOINIT:
      if ((net_buf_pos >= 2)&&(net_buf[0] == 'O')&&(net_buf[1] == 'K'))
        {
        net_state_enter(NET_STATE_COPS);
        }
      break;
    case NET_STATE_COPS:
      if ((net_buf_pos >= 2)&&(net_buf[0] == 'O')&&(net_buf[1] == 'K'))
        net_state_enter(NET_STATE_DONETINIT); // COPS reconnect was OK
      else if ((net_buf_pos >= 5)&&(net_buf[0] == 'E')&&(net_buf[1] == 'R'))
        net_state_enter(NET_STATE_SOFTRESET); // Reset the entire async
      break;
    case NET_STATE_DONETINIT:
      if ((net_buf_pos >= 2)&&
               (net_buf[0] == 'E')&&
               (net_buf[1] == 'R')&&
               (net_state_vchar == 5)) // ERROR response to AT+CIFSR
        {
        // This is a nasty case. The GPRS has locked up.
        // The only solution I can find is a hard reset of the modem
        led_act(0);
        net_state_enter(NET_STATE_HARDRESET);
        }
      else if ((net_buf_pos >= 2)&&
          (((net_buf[0] == 'O')&&(net_buf[1] == 'K')))|| // OK
          (((net_buf[0] == 'S')&&(net_buf[1] == 'H')))||  // SHUT OK
          (net_state_vchar == 5)) // Local IP address
        {
        net_buf_pos = 0;
        net_timeout_ticks = 60;
        net_link = 0;
        delay100(1);
        switch (++net_state_vchar)
          {
          case 1:
            net_puts_rom("AT+CGDCONT=1,\"IP\",\"");
            net_puts_ram(par_get(PARAM_GPRSAPN));
            net_puts_rom("\"\r");
            break;
          case 2:
            net_puts_rom("AT+CSTT=\"");
            net_puts_ram(par_get(PARAM_GPRSAPN));
            net_puts_rom("\",\"");
            net_puts_ram(par_get(PARAM_GPRSUSER));
            net_puts_rom("\",\"");
            net_puts_ram(par_get(PARAM_GPRSPASS));
            net_puts_rom("\"\r");
            break;
          case 3:
            net_puts_rom("AT+CIICR\r");
            break;
          case 4:
            net_puts_rom("AT+CIPHEAD=1\r");
            break;
          case 5:
            net_puts_rom("AT+CIFSR\r");
            break;
          case 6:
            net_puts_rom("AT+CLPORT=\"TCP\",\"6867\"\r");
            break;
          case 7:
            net_puts_rom("AT+CIPSTART=\"TCP\",\"");
            net_puts_ram(par_get(PARAM_SERVERIP));
            net_puts_rom("\",\"6867\"\r");
            break;
          case 8:
            net_state_enter(NET_STATE_READY);
            break;
          }
        }
      else if ((net_buf_pos>=7)&&
               (memcmppgm2ram(net_buf, (char const rom far*)"+CREG: 0", 8) == 0))
        { // Lost network connectivity during NETINIT
        net_state_enter(NET_STATE_SOFTRESET);
        }
      else if (memcmppgm2ram(net_buf, (char const rom far*)"+PDP: DEACT", 11) == 0)
        { // PDP couldn't be activated - try again...
        net_state_enter(NET_STATE_SOFTRESET);
        }
      break;
    case NET_STATE_READY:
      if (memcmppgm2ram(net_buf, (char const rom far*)"+CREG", 5) == 0)
        { // "+CREG" Network registration
        if (net_buf[8]==',')
          net_reg = net_buf[9]&0x07; // +CREG: 1,x
        else
          net_reg = net_buf[7]&0x07; // +CREG: x
        if ((net_reg == 0x01)||(net_reg == 0x05)) // Registered to network?
          {
          net_watchdog=0; // Disable watchdog, as we have connectivity
          led_net(1);
          }
        else if (net_watchdog == 0)
          {
          net_watchdog = 120; // We need connectivity within 120 seconds
          led_net(0);
          }
        }
      else if (memcmppgm2ram(net_buf, (char const rom far*)"+CLIP", 5) == 0)
        { // Incoming CALL
        if ((net_reg != 0x01)&&(net_reg != 0x05))
          { // Treat this as a network registration
          net_watchdog=0; // Disable watchdog, as we have connectivity
          net_reg = 0x05;
          led_net(1);
          }
        delay100(1);
        net_puts_rom(NET_HANGUP);
        net_notify_status();
        }
      else if (memcmppgm2ram(net_buf, (char const rom far*)"CONNECT OK", 10) == 0)
        {
        if (net_link == 0)
          {
          net_msg_start();
          net_msg_register();
          net_msg_send();
          }
        net_link = 1;
        }
      else if (memcmppgm2ram(net_buf, (char const rom far*)"STATE: ", 7) == 0)
        { // Incoming CIPSTATUS
        if (memcmppgm2ram(net_buf, (char const rom far*)"STATE: CONNECT OK", 17) == 0)
          {
          if (net_link == 0)
            {
            net_msg_start();
            net_msg_register();
            net_msg_send();
            }
          net_link = 1;
          }
        else
          {
          net_link = 0;
          if ((net_reg == 0x01)||(net_reg == 0x05))
            {
            // We have a GSM network, but CIPSTATUS is not up
            net_msg_disconnected();
            net_state_enter(NET_STATE_DONETINIT);
            }
          }
        }
      else if (memcmppgm2ram(net_buf, (char const rom far*)"SEND OK", 7) == 0)
        {
        net_msg_sendpending = 0;
        }
      else if ( (memcmppgm2ram(net_buf, (char const rom far*)"SEND FAIL", 9) == 0)||
                (memcmppgm2ram(net_buf, (char const rom far*)"CLOSED", 6) == 0)||
                (memcmppgm2ram(net_buf, (char const rom far*)"+CME ERROR", 10) == 0)||
                (memcmppgm2ram(net_buf, (char const rom far*)"+PDP: DEACT", 11) == 0) )
        { // Various GPRS error results
        // Re-initialize GPRS network and TCP socket
        net_msg_disconnected();
        net_state_enter(NET_STATE_DONETINIT);
        }
      break;
    }
  }