char net_msgp_tpms(char stat)
{
  char k, *s;
  long p;

#if 0
  if ((car_tpms_t[0] == 0) && (car_tpms_t[1] == 0) &&
          (car_tpms_t[2] == 0) && (car_tpms_t[3] == 0))
    return stat; // No TPMS, no report
  // ...new stat fn: No TMPS = one report with stale=-1
#endif

  s = stp_rom(net_scratchpad, "MP-0 W");
  for (k = 0; k < 4; k++)
  {
    if (car_tpms_t[k] > 0)
    {
      p = (long) ((float) car_tpms_p[k] / 0.2755);
      s = stp_l2f(s, NULL, p, 1);
      s = stp_i(s, ",", car_tpms_t[k] - 40);
      s = stp_rom(s, ",");
    }
    else
    {
      s = stp_rom(s, "0,0,");
    }
  }
  s = stp_i(s, NULL, car_stale_tpms);

  return net_msg_encode_statputs(stat, &crc_tpms);
}
char net_msgp_gps(char stat)
{
  char *s;

  s = stp_latlon(net_scratchpad, "MP-0 L", car_latitude);
  s = stp_latlon(s, ",", car_longitude);
  s = stp_i(s, ",", car_direction);
  s = stp_i(s, ",", car_altitude);
  s = stp_i(s, ",", car_gpslock);
  s = stp_i(s, ",", car_stale_gps);

  return net_msg_encode_statputs(stat, &crc_gps);
}
Esempio n. 3
0
BOOL net_sms_handle_diag(char *caller, char *command, char *arguments)
{
  char *s;

  if (sys_features[FEATURE_CARBITS] & FEATURE_CB_SOUT_SMS) return FALSE;

  net_send_sms_start(caller);

  s = stp_rom(net_scratchpad, "DIAG:");
  s = stp_i(s, "\n RED Led:", led_code[OVMS_LED_RED]);
  s = stp_i(s, "\n GRN Led:", led_code[OVMS_LED_GRN]);
  s = stp_x(s, "\n NET State:0x", net_state);

  if (car_12vline > 0)
  {
    s = stp_l2f(s, "\n 12V Line:", car_12vline, 1);
    s = stp_l2f(s, " ref=", car_12vline_ref, 1);
  }

#ifndef OVMS_NO_CRASHDEBUG
  /* DEBUG / QA stats: output crash counter and decode last reason:
   */
  s = stp_i(s, "\n Crashes:", debug_crashcnt);
  if (debug_crashreason)
  {
    s = stp_rom(s, "\n ..last:");
    if (debug_crashreason & 0x01)
      s = stp_rom(s, " BOR"); // Brown Out Reset
    if (debug_crashreason & 0x02)
      s = stp_rom(s, " POR"); // Power On Reset
    if (debug_crashreason & 0x04)
      s = stp_rom(s, " PD"); // Power-Down Detection
    if (debug_crashreason & 0x08)
      s = stp_rom(s, " TO"); // Watchdog Timeout
    if (debug_crashreason & 0x10)
      s = stp_rom(s, " RI"); // Reset Instruction
    if (debug_crashreason & 0x20)
      s = stp_rom(s, " STKFUL"); // Stack overflow
    if (debug_crashreason & 0x40)
      s = stp_rom(s, " STKUNF"); // Stack underflow
    s = stp_i(s, " - ", debug_checkpoint);
  }
#endif // OVMS_NO_CRASHDEBUG

  net_puts_ram(net_scratchpad);

  return TRUE;
}
BOOL net_sms_handle_paramsq(char *caller, char *command, char *arguments)
{
    unsigned char k, splen, msglen;
    char *p, *s;

    if (sys_features[FEATURE_CARBITS]&FEATURE_CB_SOUT_SMS) return FALSE;

    net_send_sms_start(caller);
    net_puts_rom("Params:");
    msglen=7;
    for (k=0; k<PARAM_MAX; k++)
    {
        p = par_get(k);
        if (*p != 0)
        {
            s = stp_i(net_scratchpad, "\n", k);
            s = stp_s(s, ":", p);
            splen = s - net_scratchpad;
            if((msglen+splen) > 160)
            {
                // SMS becomes too long, finish & start next:
                net_send_sms_finish();
                delay100(20);
                net_send_sms_start(caller);
                net_puts_rom("Params:");
                msglen=7+splen;
            }
            net_puts_ram(net_scratchpad);
        }
    }
    return TRUE;
}
BOOL net_sms_handle_temps(char *caller, char *command, char *arguments)
{
    char *s;

    s = stp_i(net_scratchpad, "Temperatures:\r\n  Ambient: ", car_ambient_temp);
    s = stp_i(s, "C\r\n  PEM: ", car_tpem);
    s = stp_i(s, "C\r\n  Motor: ", car_tmotor);
    s = stp_i(s, "C\r\n  Battery: ", car_tbattery);
    s = stp_rom(s, "C");
    if ((car_stale_ambient==0)||(car_stale_temps==0))
        s = stp_rom(s, "\r\n  (stale)");

    net_send_sms_start(caller);
    net_puts_ram(net_scratchpad);
    return TRUE;
}
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();
  }
BOOL net_sms_handle_version(char *caller, char *command, char *arguments)
{
    unsigned char hwv = 1;
    char *s;
#ifdef OVMS_HW_V2
    hwv = 2;
#endif

    s = stp_i(net_scratchpad, "OVMS Firmware version: ", 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);

    net_send_sms_start(caller);
    net_puts_ram(net_scratchpad);
    return TRUE;
}
BOOL net_sms_handle_featuresq(char *caller, char *command, char *arguments)
{
    unsigned char k;
    char *s;

    if (sys_features[FEATURE_CARBITS]&FEATURE_CB_SOUT_SMS) return FALSE;

    net_send_sms_start(caller);
    net_puts_rom("Features:");
    for (k=0; k<FEATURES_MAX; k++)
    {
        if (sys_features[k] != 0)
        {
            s = stp_i(net_scratchpad, "\r\n ", k);
            s = stp_i(s, ":", sys_features[k]);
            net_puts_ram(net_scratchpad);
        }
    }
    return TRUE;
}
void net_sms_socalert(char* number)
{
    char *s;

    delay100(10);
    net_send_sms_start(number);

    s = stp_i(net_scratchpad, "ALERT!!! CRITICAL SOC LEVEL APPROACHED (", car_SOC); // 95%
    s = stp_rom(s, "% SOC)");
    net_puts_ram(net_scratchpad);

    net_send_sms_finish();
    delay100(5);
}
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_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();
}
char net_msgp_group(char stat, char groupnumber, char *groupname)
{
  char *s;

  s = stp_s(net_scratchpad, "MP-0 g", groupname);
  s = stp_i(s, ",", car_SOC);
  s = stp_i(s, ",", car_speed);
  s = stp_i(s, ",", car_direction);
  s = stp_i(s, ",", car_altitude);
  s = stp_i(s, ",", car_gpslock);
  s = stp_i(s, ",", car_stale_gps);
  s = stp_latlon(s, ",", car_latitude);
  s = stp_latlon(s, ",", car_longitude);

  if (groupnumber == 1)
    return net_msg_encode_statputs(stat, &crc_group1);
  else
    return net_msg_encode_statputs(stat, &crc_group2);
}
char net_msgp_firmware(char stat)
{
  // Send firmware version and GSM signal level
  char *s;
  unsigned char hwv = 1;
#ifdef OVMS_HW_V2
  hwv = 2;
#endif

  s = stp_i(net_scratchpad, "MP-0 F", 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));
  s = stp_i(s, "/V", hwv);
  s = stp_s(s, ",", car_vin);
  s = stp_i(s, ",", net_sq);
  s = stp_i(s, ",", sys_features[FEATURE_CANWRITE]);
  s = stp_s(s, ",", car_type);
  s = stp_s(s, ",", car_gsmcops);

  return net_msg_encode_statputs(stat, &crc_firmware);
}
char net_msgp_environment(char stat)
{
  char *s;
  unsigned long park;

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

  s = stp_i(net_scratchpad, "MP-0 D", car_doors1);
  s = stp_i(s, ",", car_doors2);
  s = stp_i(s, ",", car_lockstate);
  s = stp_i(s, ",", car_tpem);
  s = stp_i(s, ",", car_tmotor);
  s = stp_i(s, ",", car_tbattery);
  s = stp_i(s, ",", car_trip);
  s = stp_ul(s, ",", car_odometer);
  s = stp_i(s, ",", car_speed);
  s = stp_ul(s, ",", park);
  s = stp_i(s, ",", car_ambient_temp);
  s = stp_i(s, ",", car_doors3);
  s = stp_i(s, ",", car_stale_temps);
  s = stp_i(s, ",", car_stale_ambient);
  s = stp_l2f(s, ",", car_12vline, 1);
  s = stp_i(s, ",", car_doors4);
  s = stp_l2f(s, ",", car_12vline_ref, 1);
  s = stp_i(s, ",", car_doors5);

  return net_msg_encode_statputs(stat, &crc_environment);
}
char net_msgp_stat(char stat)
{
  char *p, *s;

  p = par_get(PARAM_MILESKM);

  s = stp_i(net_scratchpad, "MP-0 S", car_SOC);
  s = stp_s(s, ",", p);
  s = stp_i(s, ",", car_linevoltage);
  s = stp_i(s, ",", car_chargecurrent);

  switch (car_chargestate)
  {
  case 0x01:
    s = stp_rom(s, ",charging");
    break;
  case 0x02:
    s = stp_rom(s, ",topoff");
    break;
  case 0x04:
    s = stp_rom(s, ",done");
    break;
  case 0x0d:
    s = stp_rom(s, ",prepare");
    break;
  case 0x0f:
    s = stp_rom(s, ",heating");
    break;
  default:
    s = stp_rom(s, ",stopped");
  }

  switch (car_chargemode)
  {
  case 0x00:
    s = stp_rom(s, ",standard");
    break;
  case 0x01:
    s = stp_rom(s, ",storage");
    break;
  case 0x03:
    s = stp_rom(s, ",range");
    break;
  case 0x04:
    s = stp_rom(s, ",performance");
    break;
  default:
    s = stp_rom(s, ",");
  }

  if (*p == 'M') // Kmh or Miles
  {
    s = stp_i(s, ",", car_idealrange);
    s = stp_i(s, ",", car_estrange);
  }
  else
  {
    s = stp_i(s, ",", MI2KM(car_idealrange));
    s = stp_i(s, ",", MI2KM(car_estrange));
  }

  s = stp_i(s, ",", car_chargelimit);
  s = stp_i(s, ",", car_chargeduration);
  s = stp_i(s, ",", car_charge_b4);
  s = stp_i(s, ",", car_chargekwh);
  s = stp_i(s, ",", car_chargesubstate);
  s = stp_i(s, ",", car_chargestate);
  s = stp_i(s, ",", car_chargemode);
  s = stp_i(s, ",", car_timermode);
  s = stp_i(s, ",", car_timerstart);
  s = stp_i(s, ",", car_stale_timer);

  return net_msg_encode_statputs(stat, &crc_stat);
}
char net_msgp_stat(char stat)
{
  char *p, *s;

  p = par_get(PARAM_MILESKM);

  s = stp_i(net_scratchpad, "MP-0 S", car_SOC);
  s = stp_s(s, ",", p);
  s = stp_i(s, ",", car_linevoltage);
  s = stp_i(s, ",", car_chargecurrent);

  switch (car_chargestate)
  {
  case 0x01:
    s = stp_rom(s, ",charging");
    break;
  case 0x02:
    s = stp_rom(s, ",topoff");
    break;
  case 0x04:
    s = stp_rom(s, ",done");
    break;
  case 0x0d:
    s = stp_rom(s, ",prepare");
    break;
  case 0x0f:
    s = stp_rom(s, ",heating");
    break;
  default:
    s = stp_rom(s, ",stopped");
  }

  switch (car_chargemode)
  {
  case 0x00:
    s = stp_rom(s, ",standard");
    break;
  case 0x01:
    s = stp_rom(s, ",storage");
    break;
  case 0x03:
    s = stp_rom(s, ",range");
    break;
  case 0x04:
    s = stp_rom(s, ",performance");
    break;
  default:
    s = stp_rom(s, ",");
  }

  if (*p == 'M') // Kmh or Miles
  {
    s = stp_i(s, ",", car_idealrange);
    s = stp_i(s, ",", car_estrange);
  }
  else
  {
    s = stp_i(s, ",", KmFromMi(car_idealrange));
    s = stp_i(s, ",", KmFromMi(car_estrange));
  }

  s = stp_i(s, ",", car_chargelimit);
  s = stp_i(s, ",", car_chargeduration);
  s = stp_i(s, ",", car_charge_b4);
  s = stp_i(s, ",", car_chargekwh);
  s = stp_i(s, ",", car_chargesubstate);
  s = stp_i(s, ",", car_chargestate);
  s = stp_i(s, ",", car_chargemode);
  s = stp_i(s, ",", car_timermode);
  s = stp_i(s, ",", car_timerstart);
  s = stp_i(s, ",", car_stale_timer);
  s = stp_l2f(s, ",", (unsigned long)car_cac100, 2);
  s = stp_i(s, ",", car_chargefull_minsremaining);
  s = stp_i(s, ",", car_chargelimit_minsremaining);
  s = stp_i(s, ",", car_chargelimit_rangelimit);
  s = stp_i(s, ",", car_chargelimit_soclimit);
  s = stp_i(s, ",", car_coolingdown);
  s = stp_i(s, ",", car_cooldown_tbattery);
  s = stp_i(s, ",", car_cooldown_timelimit);
  s = stp_i(s, ",", car_chargeestimate);

  return net_msg_encode_statputs(stat, &crc_stat);
}
char *net_prep_stat(char *s)
{
  if (car_doors1bits.ChargePort)
  {
    // Charge port door is open, we are charging
    switch (car_chargemode)
    {
    case 0x00:
      s = stp_rom(s, "Standard - "); // Charge Mode Standard
      break;
    case 0x01:
      s = stp_rom(s, "Storage - "); // Storage
      break;
    case 0x03:
      s = stp_rom(s, "Range - "); // Range
      break;
    case 0x04:
      s = stp_rom(s, "Performance - "); // Performance
    }
    switch (car_chargestate)
    {
    case 0x01:
      s = stp_rom(s, "Charging"); // Charge State Charging
      break;
    case 0x02:
      s = stp_rom(s, "Charging, Topping off"); // Topping off
      break;
    case 0x04:
      s = stp_rom(s, "Charging Done"); // Done
      break;
    case 0x0d:
      s = stp_rom(s, "Preparing"); // Preparing
      break;
    case 0x0f:
      s = stp_rom(s, "Charging, Heating"); // Heating
      break;
    default:
      s = stp_rom(s, "Charging Stopped"); // Stopped
    }
  }
  else
  {
    s = stp_rom(s, "Not charging");
  }

  if (can_mileskm == 'M')
  {
    s = stp_i(s, "\r Range: ", car_estrange);
    s = stp_i(s, " - ", car_idealrange);
    s = stp_rom(s, " mi");
  }
  else
  {
    s = stp_i(s, "\r Range: ", MI2KM(car_estrange));
    s = stp_i(s, " - ", MI2KM(car_idealrange));
    s = stp_rom(s, " km");
  }

  s = stp_i(s, "\r SOC: ", car_SOC);
  s = stp_rom(s, "%");

  if (can_mileskm == 'M')
  {
    s = stp_ul(s, "\r ODO: ", car_odometer / 10);
    s = stp_rom(s, " mi");
  }
  else
  {
    s = stp_ul(s, "\r ODO: ", MI2KM(car_odometer / 10));
    s = stp_rom(s, " km");
  }

  return s;
}
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
}
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;
  }
char *net_prep_ctp(char *s, char *argument)
{
  int imStart = car_idealrange;
  int imTarget = 0;
  int pctTarget = 0;
  int cac100 = car_cac100;
  int volts = car_linevoltage;
  int amps = car_chargelimit;
  int degAmbient = car_ambient_temp;
  int chargemode = car_chargemode;
  int watts = 0;
  int imExpect;
  int minRemain;
  
  if (vehicle_fn_minutestocharge == NULL)
  {
    return stp_rom(s, "CTP not available");
  }
  
  // CTP 90s 150e 70% 16800w 160a 24d [S|R|P]
  while (argument != NULL)
  {
    int cch = strlen(argument);
    strupr(argument); // Convert argument to upper case
    if (cch > 0)
      {
      int val;
      char chType = argument[cch-1];
      argument[cch-1] = 0;
      if (cch > 1)
        {
        val = atoi(argument);
        switch (chType)
          {
          case 'S':
            imStart = val;
            break;
          case 'E':
            imTarget = val;
           break;
          case '%':
            pctTarget = val;
            break;
          case 'W':
            watts = val;
            break;
          case 'V':
            volts = val;
            break;
          case 'A':
            amps = val;
            break;
          case 'C':
            cac100 = val*100;
            break;
          case 'D':
            degAmbient = val;
            break;
          }
        }
      else
        {
        switch (chType)
          {
          case 'S':
            chargemode = 0;
            break;
          case 'R':
            chargemode = 3;
            break;
          case 'P':
            chargemode = 4;
            break;
          }
        }
      argument[cch-1] = chType;
      }
    argument = net_sms_nextarg(argument);
  }
  
  if (volts > 0 && amps > 0)
    watts = volts * amps;

  if (watts < 1000)
    {
    s = stp_rom(s, "no power level specified");
    return s;
    }
  
  minRemain = vehicle_fn_minutestocharge(chargemode, watts, imStart, imTarget, pctTarget, cac100, degAmbient, &imExpect);

  s = stp_i(s, NULL, imStart);
  s = stp_i(s, " to ", imExpect);
  s = stp_rom(s, " ideal mi\r");
  if (minRemain >= 0)
    {
    s = stp_i(s, NULL, minRemain/60);
    s = stp_ulp(s, ":", minRemain % 60, 2, '0');
    }
  else if (minRemain == -3)
    {
    s = stp_rom(s, "target reached");
    }
  else
    {
    s = stp_i(s, "error: ", minRemain);
    }

  return s;
}
char *net_prep_stat(char *s)
{
  // convert distance values as needed
  unsigned int estrange = car_estrange;
  unsigned int idealrange = car_idealrange;
  unsigned long odometer = car_odometer;
  const rom char *unit = " mi";
  if (can_mileskm == 'K')
  {
    estrange = KmFromMi(estrange);
    idealrange = KmFromMi(idealrange);
    odometer = KmFromMi(odometer);
    unit = " km";
  }

  if (car_time != 0)
    {
    char *p = par_get(PARAM_TIMEZONE);
    s = stp_time(s, NULL, car_time + timestring_to_mins(p)*60L);
    s = stp_rom(s, "\r ");
    }

  if (car_coolingdown>=0)
    {
    s = stp_i(s, "Cooldown: ", car_tbattery);
    s = stp_i(s, "C/",car_cooldown_tbattery);
    s = stp_i(s, "C (",car_coolingdown);
    s = stp_i(s, "cycles, ",car_cooldown_timelimit);
    s = stp_rom(s, "mins remain)");
    }

  if (car_doors1bits.ChargePort)
  {
    char fShowVA = TRUE;
    // Charge port door is open, we are charging
    switch (car_chargemode)
    {
    case 0x00:
      s = stp_rom(s, "Standard - "); // Charge Mode Standard
      break;
    case 0x01:
      s = stp_rom(s, "Storage - "); // Storage
      break;
    case 0x03:
      s = stp_rom(s, "Range - "); // Range
      break;
    case 0x04:
      s = stp_rom(s, "Performance - "); // Performance
    }
    switch (car_chargestate)
    {
    case 0x01:
      s = stp_rom(s, "Charging"); // Charge State Charging
      break;
    case 0x02:
      s = stp_rom(s, "Charging, Topping off"); // Topping off
      break;
    case 0x04:
      s = stp_rom(s, "Charging Done"); // Done
      fShowVA = FALSE;
      break;
    case 0x0d:
      s = stp_rom(s, "Preparing"); // Preparing
      break;
    case 0x0f:
      s = stp_rom(s, "Charging, Heating"); // Heating
      break;
    default:
      s = stp_rom(s, "Charging Stopped"); // Stopped
      fShowVA = FALSE;
      break;
    }
//  this causes ACC to think the charge port door has been closed and opened,
//  which then causes it to do something that makes the coolant pump come on
//  car_doors1bits.ChargePort = 0; // MJ Close ChargePort, will open next CAN Reading
    if (fShowVA)
    {
      s = stp_i(s, "\r ", car_linevoltage);
      s = stp_i(s, "V/", car_chargecurrent);
      s = stp_rom(s, "A");
      if (car_chargefull_minsremaining >= 0)
        {
        s = stp_i(s,"\r Full: ",car_chargefull_minsremaining);
        s = stp_rom(s," mins");
        }
      if (car_chargelimit_soclimit > 0)
        {
        s = stp_i(s, "\r ", car_chargelimit_soclimit);
        s = stp_i(s,"%: ",car_chargelimit_minsremaining);
        s = stp_rom(s," mins");
        }
      if (car_chargelimit_rangelimit > 0)
        {
        s = stp_i(s, "\r ", (can_mileskm == 'K')?KmFromMi(car_chargelimit_rangelimit):car_chargelimit_rangelimit);
        s = stp_rom(s, unit);
        s = stp_i(s,": ",car_chargelimit_minsremaining);
        s = stp_rom(s," mins");
        }
    }
  }
  else
  {
    s = stp_rom(s, "Not charging");
  }

  s = stp_i(s, "\r SOC: ", car_SOC);
  s = stp_rom(s, "%");

  if (idealrange != 0)
    {
    s = stp_i(s, "\r Ideal Range: ", idealrange);
    s = stp_rom(s, unit);
    }
  if (estrange != 0)
    {
    s = stp_i(s, "\r Est. Range: ", estrange);
    s = stp_rom(s, unit);
    }
  if (odometer != 0)
    {
    s = stp_l2f_h(s, "\r ODO: ", odometer, 1);
    s = stp_rom(s, unit);
    }
  if (car_cac100 != 0)
    {
    s = stp_l2f_h(s, "\r CAC: ", (unsigned long)car_cac100, 2);
    }

  return s;
}
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;
  }