// <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;
}
void net_msg_tpms(void)
  {
  char k;
  long p;
  int b,a;

  if ((car_tpms_t[0]==0)&&(car_tpms_t[1]==0)&&
      (car_tpms_t[2]==0)&&(car_tpms_t[3]==0))
    return; // No TPMS, no report

  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 W");
  for (k=0;k<4;k++)
    {
    if (car_tpms_t[k]>0)
      {
      p = (long)((float)car_tpms_p[k]/0.2755);
      b = (p / 10);
      a = (p % 10);
      sprintf(net_msg_scratchpad, (rom far char*)"%d.%d,%d,",
              b,a,(int)(car_tpms_t[k]-40));
      strcat(net_scratchpad,net_msg_scratchpad);
      }
    else
      {
      strcatpgm2ram(net_scratchpad, (rom far char*)"0,0,");
      }
    }
  net_scratchpad[strlen(net_scratchpad)-1] = 0; // Remove trailing ','
  net_msg_encode_puts();
  }
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_socalert(void)
  {
  char *s;

  s = stp_i(net_scratchpad, "MP-0 PAALERT!!! CRITICAL SOC LEVEL APPROACHED (", car_SOC); // 95%
  s = stp_rom(s, "% SOC)");
  net_msg_encode_puts();
  }
void net_msg_firmware(void)
  {
  // Send firmware version and GSM signal level
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 F");
  sprintf(net_msg_scratchpad, (rom far char*)"1.0.8,%s,%d",
    car_vin, net_sq);
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
void net_msg_firmware(void)
  {
  // TODO: GSM signal level not reported yet
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 F");
  sprintf(net_msg_scratchpad, (rom far char*)"1.0.0,%s,%d",
    car_vin,0);
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
void net_msg_12v_alert(void)
  {
  char *s;

  s = stp_l2f(net_scratchpad, "MP-0 PAALERT!!! 12V BATTERY CRITICAL (", car_12vline, 1);
  s = stp_l2f(s, "V, ref=", car_12vline_ref, 1);
  s = stp_rom(s, "V)");
  net_msg_encode_puts();
  }
// 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;
      }
    }
  }
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_environment(void)
  {
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 D");
  sprintf(net_msg_scratchpad, (rom far char*)"%d,%d,%d,%d,%d,%d,%d,%lu,%d",
          car_doors1, car_doors2, car_lockstate,
          car_tpem, car_tmotor, car_tbattery,
          car_trip, car_odometer, car_speed);
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
void net_msg_gps(void)
  {
  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 L");
  format_latlon(car_latitude,net_msg_scratchpad);
  strcat(net_scratchpad,net_msg_scratchpad);
  strcatpgm2ram(net_scratchpad,(char const rom far*)",");
  format_latlon(car_longitude,net_msg_scratchpad);
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
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_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_stat(void)
  {
  char *p;

  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 S");
  p = par_get(PARAM_MILESKM);
  sprintf(net_msg_scratchpad,(rom far char*)"%d,%s,%d,%d,",car_SOC,p,car_linevoltage,car_chargecurrent);
  strcat(net_scratchpad,net_msg_scratchpad);
  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*)"topoff,"); // Topping off
      break;
    case 0x04:
      strcatpgm2ram(net_scratchpad,(char const rom far*)"done,"); // Done
      break;
    default:
      strcatpgm2ram(net_scratchpad,(char const rom far*)"stopped,"); // Stopped
    }
  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
    default:
      strcatpgm2ram(net_scratchpad,(char const rom far*)",");
    }
  if (*p == 'M') // Kmh or Miles
    sprintf(net_msg_scratchpad, (rom far char*)"%u,", car_idealrange);
  else
    sprintf(net_msg_scratchpad, (rom far char*)"%u,", (unsigned int) ((float) car_idealrange * 1.609));
  strcat(net_scratchpad,net_msg_scratchpad);
  if (*p == 'M') // Kmh or Miles
    sprintf(net_msg_scratchpad, (rom far char*)"%u", car_estrange);
  else
    sprintf(net_msg_scratchpad, (rom far char*)"%u", (unsigned int) ((float) car_estrange * 1.609));
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
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_environment(void)
  {
  unsigned long park;

  if (car_parktime == 0)
    park = 0;
  else
    park = car_time - car_parktime;

  strcpypgm2ram(net_scratchpad,(char const rom far*)"MP-0 D");
  sprintf(net_msg_scratchpad, (rom far char*)"%d,%d,%d,%d,%d,%d,%d,%lu,%d,%lu",
          car_doors1, car_doors2, car_lockstate,
          car_tpem, car_tmotor, car_tbattery,
          car_trip, car_odometer, car_speed, park);
  strcat(net_scratchpad,net_msg_scratchpad);
  net_msg_encode_puts();
  }
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();
}
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;
  }
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
    }
  }