BOOL net_sms_handle_gprsq(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, "GPRS:");
    s = stp_s(s, "\r\n APN:", par_get(PARAM_GPRSAPN));
    s = stp_s(s, "\r\n User:"******"\r\n Password:"******"\r\n GSM:", car_gsmcops);

    if (!inputs_gsmgprs())
        s = stp_rom(s, "\r\n GPRS: DISABLED");
    else if (net_msg_serverok)
        s = stp_rom(s, "\r\n GPRS: OK\r\n Server: Connected OK");
    else if (net_state == NET_STATE_READY)
        s = stp_rom(s, "\r\n GSM: OK\r\n Server: Not connected");
    else
    {
        s = stp_x(s, "\r\n GSM/GPRS: Not connected (0x", net_state);
        s = stp_rom(s, ")");
    }

    net_puts_ram(net_scratchpad);

    return TRUE;
}
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_capabilities(char stat)
{
  char *s;

  s = stp_rom(net_scratchpad, "MP-0 V");
  if ((can_capabilities != NULL) && (can_capabilities[0] != 0))
  {
    s = stp_rom(s, can_capabilities);
    s = stp_rom(s, ",");
  }
  s = stp_rom(s, "C1-6,C40-41,C49");

  return net_msg_encode_statputs(stat, &crc_capabilities);
}
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_send_sms_start(char* number)
  {
  if (net_state == NET_STATE_DIAGMODE)
    {
    // DIAG mode: screen output
    net_puts_rom("# ");
    }
  else if (net_msg_bufpos)
    {
    // NET SMS wrapper mode: nothing to do here
    // net_put* will write to net_msg_bufpos
    }
  else
    {
    // MODEM mode:
    net_puts_rom("AT+CMGS=\"");
    net_puts_ram(number);
    net_puts_rom("\"\r\n");
    delay100(2);
    }

  if ((car_time > 315360000)&&
      ((sys_features[FEATURE_CARBITS]&FEATURE_CB_SSMSTIME)==0))
    {
    // Car time is valid, and sms time is not disabled
    char *p = par_get(PARAM_TIMEZONE);
    char *s = stp_time(net_scratchpad, NULL, car_time + timestring_to_mins(p)*60L);
    s = stp_rom(s, "\r ");
    net_puts_ram(net_scratchpad);
    }
  }
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_alert(void)
{
  char *s;

  delay100(2);

  s = stp_rom(net_scratchpad, "MP-0 PA");
  net_prep_stat(s);
}
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();
  }
Esempio n. 9
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;
}
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
}
BOOL net_sms_handle_vehicleq(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, "Vehicle:");
    s = stp_s(s, "\r\n VehicleType: ", par_get(PARAM_VEHICLETYPE));

    net_puts_ram(net_scratchpad);

    return TRUE;
}
Esempio n. 13
0
BOOL net_sms_handle_vehicleq(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, "Vehicle:");
  s = stp_vehicletype(s);

  net_puts_ram(net_scratchpad);

  return TRUE;
  }
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();
  }
BOOL net_sms_handle_serverq(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, "Server:");
    s = stp_s(s, "\r\n IP:", par_get(PARAM_SERVERIP));
    s = stp_s(s, "\r\n Password:"******"\r\n Paranoid:", par_get(PARAM_PARANOID));

    net_puts_ram(net_scratchpad);

    return TRUE;
}
void net_sms_12v_alert(char* number)
{
    char *s;

    delay100(10);
    net_send_sms_start(number);

    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_puts_ram(net_scratchpad);

    net_send_sms_finish();
    delay100(5);
}
BOOL net_sms_handle_moduleq(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, "Module:");
    s = stp_s(s, "\r\n VehicleID:", par_get(PARAM_VEHICLEID));
    s = stp_s(s, "\r\n Units:", par_get(PARAM_MILESKM));
    s = stp_s(s, "\r\n Notifications:", par_get(PARAM_NOTIFIES));
    s = stp_s(s, "\r\n VehicleType:", par_get(PARAM_VEHICLETYPE));

    net_puts_ram(net_scratchpad);

    return TRUE;
}
Esempio n. 18
0
void net_send_sms_start(char* number)
  {
  if (net_msg_bufpos)
    {
    // NET SMS wrapper mode: nothing to do here
    // net_put* will write to net_msg_bufpos
    }
#ifdef OVMS_DIAGMODULE
  else if (net_state == NET_STATE_DIAGMODE)
    {
    // DIAG mode: screen output
    net_msg_sendpending = 1;
    net_puts_rom("# ");
    }
#endif // OVMS_DIAGMODULE
  else
    {
    // MODEM mode:
    net_puts_rom("AT+CMGS=\"");
    net_puts_ram(number);
    net_puts_rom("\"\r\n");
    delay100(2);
    }

  // ATT: the following code tries to prepend the current time to ALL
  //    outbound SMS. It relies on a) all SMS leaving enough space
  //    to add "HH:MM:SS\r " = 10 chars and b) ALL SMS senders to
  //    call net_send_sms_start() BEFORE preparing the message in
  //    net_scratchpad -- otherwise the prepd message is lost.
#ifndef OVMS_NO_SMSTIME
  if ((car_time > 315360000)&&
      ((sys_features[FEATURE_CARBITS]&FEATURE_CB_SSMSTIME)==0))
    {
    // Car time is valid, and sms time is not disabled
    char *p = par_get(PARAM_TIMEZONE);
    char *s = stp_time(net_scratchpad, NULL, car_time + timestring_to_mins(p)*60L);
    s = stp_rom(s, "\r ");
    net_puts_ram(net_scratchpad);
    }
#endif //OVMS_NO_SMSTIME
  
  }
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;
}
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_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;
}
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
}
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;
}
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;
}