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;
}
// Register to the NET OVMS server
void net_msg_register(void)
  {
  char k;
  char *p;
  unsigned int sr;

  // Make a (semi-)random client token
  sr = TMR0L*256;
  sr += TMR0H;
  for (k=0;k<8;k++)
    {
    sr += can_databuffer[k];
    }
  srand(sr);
  for (k=0;k<TOKEN_SIZE;k++)
    {
    token[k] = cb64[rand()%64];
    }
  token[TOKEN_SIZE] = 0;

  p = par_get(PARAM_SERVERPASS);
  hmac_md5(token, TOKEN_SIZE, p, strlen(p), digest);

  net_puts_rom("MP-C 0 ");
  net_puts_ram(token);
  net_puts_rom(" ");
  base64encodesend(digest, MD5_SIZE);
  net_puts_rom(" ");
  p = par_get(PARAM_VEHICLEID);
  net_puts_ram(p);
  net_puts_rom("\r\n");
  }
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);
    }
  }
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_gsmlockq(char *caller, char *command, char *arguments)
{
    char *p;

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

    net_send_sms_start(caller);
    net_puts_rom("GSMLOCK: ");

    p = par_get(PARAM_GSMLOCK);
    if (*p == 0)
    {
        net_puts_rom("(none)\r\n");
    }
    else
    {
        net_puts_ram(p);
        net_puts_rom("\r\n");
    }

    stp_s(net_scratchpad, "Current: ", car_gsmcops);
    net_puts_ram(net_scratchpad);

    return TRUE;
}
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;
}
////////////////////////////////////////////////////////////////////////
// can_initialise()
// This function is an entry point from the main() program loop, and
// gives the CAN framework an opportunity to initialise itself.
//
void can_initialise(void)
  {
  char *p;

  car_type[0] = 'V'; // Car is type VA - Volt/Ampera
  car_type[1] = 'A';
  car_type[2] = 0;

  CANCON = 0b10010000; // Initialize CAN
  while (!CANSTATbits.OPMODE2); // Wait for Configuration mode

  // We are now in Configuration Mode
  RXB0CON = 0b00000000; // RX buffer0 uses Mask RXM0 and filters RXF0, RXF1

  RXM0SIDL = 0b10100000;
  RXM0SIDH = 0b11111111;	// Set Mask0 to 0x7fd

  RXF0SIDL = 0b00000000;	// Setup Filter0 and Mask so that only CAN ID 0x100 and 0x102 will be accepted
  RXF0SIDH = 0b00100000;	// Set Filter0 to 0x100

  RXB1CON = 0b00000000;	// RX buffer1 uses Mask RXM1 and filters RXF2, RXF3, RXF4, RXF5

  RXM1SIDL = 0b11100000;
  RXM1SIDH = 0b11111111;	// Set Mask1 to 0x7ff

  RXF2SIDL = 0b10000000;	// Setup Filter2 so that CAN ID 0x344 will be accepted
  RXF2SIDH = 0b01101000;

  RXF3SIDL = 0b01000000;	// Setup Filter3 so that CAN ID 0x402 will be accepted
  RXF3SIDH = 0b10000000;

  RXF4SIDL = 0b00000000;        // Setup Filter4 so that CAN ID 0x400 will be accepted
  RXF4SIDH = 0b10000000;

  BRGCON1 = 0; // SET BAUDRATE to 1 Mbps
  BRGCON2 = 0xD2;
  BRGCON3 = 0x02;

  CIOCON = 0b00100000; // CANTX pin will drive VDD when recessive
  if (sys_features[FEATURE_CANWRITE]>0)
    {
    CANCON = 0b00000000;  // Normal mode
    }
  else
    {
    CANCON = 0b01100000; // Listen only mode, Receive bufer 0
    }

  RCONbits.IPEN = 1; // Enable Interrupt Priority
  PIE3bits.RXB1IE = 1; // CAN Receive Buffer 1 Interrupt Enable bit
  PIE3bits.RXB0IE = 1; // CAN Receive Buffer 0 Interrupt Enable bit
  IPR3 = 0b00000011; // high priority interrupts for Buffers 0 and 1

  p = par_get(PARAM_MILESKM);
  can_mileskm = *p;
  can_lastspeedmsg[0] = 0;
  can_lastspeedrpt = 0;
  }
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;
}
BOOL net_sms_handle_passq(char *caller, char *command, char *arguments)
{
    char *p = par_get(PARAM_MODULEPASS);

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

    net_send_sms_start(caller);
    stp_s(net_scratchpad, "Module password: ", p);
    net_puts_ram(net_scratchpad);
    return TRUE;
}
BOOL net_sms_handle_registerq(char *caller, char *command, char *arguments)
{
    char *p = par_get(PARAM_REGPHONE);

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

    net_send_sms_start(caller);
    stp_s(net_scratchpad, "Registered number: ", p);
    net_puts_ram(net_scratchpad);
    return TRUE;
}
void net_sms_stat(char* number)
  {
  char *p;

#ifdef OVMS_SUPPRESS_OUTGOING_SMS
  return;
#endif

  delay100(2);
  net_send_sms_start(number);

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

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

  net_puts_rom(" \rSOC: ");
  sprintf(net_scratchpad, (rom far char*)"%u%%", car_SOC); // 95%
  net_puts_ram(net_scratchpad);
  net_puts_rom("\x1a");
  }
// We start with two helper functions
unsigned char net_sms_checkcaller(char *caller)
{
    char *p = par_get(PARAM_REGPHONE);
    if (*p && strncmp(p,caller,strlen(p)) == 0)
        return 1;
    else
    {
        if ((sys_features[FEATURE_CARBITS]&FEATURE_CB_SAD_SMS) == 0)
            net_send_sms_rom(caller,NET_MSG_DENIED);
        return 0;
    }
}
unsigned char net_sms_checkpassarg(char *caller, char *arguments)
{
    char *p = par_get(PARAM_MODULEPASS);

    if ((arguments != NULL)&&(*p && strncmp(p,arguments,strlen(p))==0))
        return 1;
    else
    {
        if ((sys_features[FEATURE_CARBITS]&FEATURE_CB_SAD_SMS) == 0)
            net_send_sms_rom(caller,NET_MSG_DENIED);
        return 0;
    }
}
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();
  }
////////////////////////////////////////////////////////////////////////
// net_notify_status()
// Emits a status notification error to the user (by SMS)
// upon request (e.g. by an incoming call or sms STAT command).
//
void net_notify_status(void)
  {
  // Emit an unsolicited notification showing current status
  char *p,*q;
  p = par_get(PARAM_NOTIFIES);
  if (strstrrampgm(p,(char const rom far*)"SMS") != NULL)
    {
    net_sms_notify = 1;
    }
  if (strstrrampgm(p,(char const rom far*)"IP") != NULL)
    {
    net_msg_notify = 1;
    }
  }
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_sms_params(char* number)
  {
  unsigned char k;
  char *p;

  net_send_sms_start(number);
  net_puts_rom("Params:");
  for (k=0;k<PARAM_MAX;k++)
    {
    sprintf(net_scratchpad, (rom far char*)" %u:", k);
    net_puts_ram(net_scratchpad);
    net_puts_ram(par_get(k));
    }
  net_puts_rom("\x1a");
  }
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;
}
BOOL net_sms_handle_ap(char *caller, char *command, char *arguments)
{
    unsigned char d = 0;
    char *p = par_get(PARAM_MODULEPASS);

    while ((d < PARAM_MAX)&&(arguments != NULL))
    {
        if ((arguments[0]=='-')&&(arguments[1]==0))
            par_set(d++, arguments+1);
        else
            par_set(d++, arguments);
        arguments = net_sms_nextarg(arguments);
    }

    if (net_state != NET_STATE_DIAGMODE)
        net_state_enter(NET_STATE_FIRSTRUN);

    return FALSE;
}
void net_sms_params(char* number)
  {
  unsigned char k;
  char *p;

#ifdef OVMS_SUPPRESS_OUTGOING_SMS
  return;
#endif

  net_send_sms_start(number);
  net_puts_rom("Params:");
  for (k=0;k<PARAM_MAX;k++)
    {
    sprintf(net_scratchpad, (rom far char*)" %u:", k);
    net_puts_ram(net_scratchpad);
    net_puts_ram(par_get(k));
    }
  net_puts_rom("\x1a");
  }
Example #21
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;
}
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);
}
void main(void)
{
  unsigned char x, y;

  // DEBUG / QA stats: get last reset reason:
  x = (~RCON) & 0x1f;
  if (STKPTRbits.STKFUL) x += 32;
  if (STKPTRbits.STKUNF) x += 64;

  // ...clear RCON:
  RCONbits.NOT_BOR = 1; // b0 = 1  = Brown Out Reset
  RCONbits.NOT_POR = 1; // b1 = 2  = Power On Reset
  //RCONbits.NOT_PD = 1;    // b2 = 4  = Power Down detection
  //RCONbits.NOT_TO = 1;    // b3 = 8  = watchdog TimeOut occured
  RCONbits.NOT_RI = 1; // b4 = 16 = Reset Instruction

  if (x == 3) // 3 = normal Power On
  {
    debug_crashreason = 0;
    debug_crashcnt = 0;
#ifdef OVMS_LOGGINGMODULE
    logging_initialise();
#endif
  }
  else
  {
    debug_crashreason = x | 0x80; // 0x80 = keep checkpoint until sent to server
    debug_crashcnt++;
  }

  CHECKPOINT(0x20)

  for (x = 0; x < FEATURES_MAP_PARAM; x++)
    sys_features[x] = 0; // Turn off the features

  // The top N features are persistent
  for (x = FEATURES_MAP_PARAM; x < FEATURES_MAX; x++)
  {
    sys_features[x] = atoi(par_get(PARAM_FEATURE_S + (x - FEATURES_MAP_PARAM)));
  }

  // Make sure cooldown is off
  car_coolingdown = -1;

  // Port configuration
  inputs_initialise();
  TRISB = 0xFE;

  // Timer 0 enabled, Fosc/4, 16 bit mode, prescaler 1:256
  // This gives us one tick every 51.2uS before prescale (13.1ms after)
  T0CON = 0b10000111; // @ 5Mhz => 51.2uS

  // Initialisation...
  led_initialise();
  par_initialise();
  vehicle_initialise();
  net_initialise();

  CHECKPOINT(0x21)

  // Startup sequence...
  // Holding the RED led on, pulse out the firmware version on the GREEN led
  delay100(10); // Delay 1 second
  led_set(OVMS_LED_RED, OVMS_LED_ON);
  led_set(OVMS_LED_GRN, OVMS_LED_OFF);
  led_start();
  delay100(10); // Delay 1.0 seconds
  led_set(OVMS_LED_GRN, ovms_firmware[0]);
  led_start();
  delay100(35); // Delay 3.5 seconds
  ClrWdt(); // Clear Watchdog Timer
  led_set(OVMS_LED_GRN, ovms_firmware[1]);
  led_start();
  delay100(35); // Delay 3.5 seconds
  ClrWdt(); // Clear Watchdog Timer
  led_set(OVMS_LED_GRN, ovms_firmware[2]);
  led_start();
  delay100(35); // Delay 3.5 seconds
  ClrWdt(); // Clear Watchdog Timer
  led_set(OVMS_LED_GRN, OVMS_LED_OFF);
  led_set(OVMS_LED_RED, OVMS_LED_OFF);
  led_start();
  delay100(10); // Delay 1 second
  ClrWdt(); // Clear Watchdog Timer

  // Setup ready for the main loop
  led_set(OVMS_LED_GRN, OVMS_LED_OFF);
  led_start();

#ifdef OVMS_HW_V2
  car_12vline = inputs_voltage()*10;
  car_12vline_ref = 0;
#endif

#ifdef OVMS_ACCMODULE
  acc_initialise();
#endif

  // Proceed to main loop
  y = 0; // Last TMR0H
  while (1) // Main Loop
  {
    CHECKPOINT(0x22)
    if ((vUARTIntStatus.UARTIntRxError) ||
            (vUARTIntStatus.UARTIntRxOverFlow))
      net_reset_async();

    while (!vUARTIntStatus.UARTIntRxBufferEmpty)
    {
      CHECKPOINT(0x23)
      net_poll();
    }

    CHECKPOINT(0x24)
    vehicle_idlepoll();

    ClrWdt(); // Clear Watchdog Timer

    x = TMR0L;
    if (TMR0H >= 0x4c) // Timout ~1sec (actually 996ms)
    {
      TMR0H = 0;
      TMR0L = 0; // Reset timer
      CHECKPOINT(0x25)
      net_ticker();
      CHECKPOINT(0x26)
      vehicle_ticker();
#ifdef OVMS_LOGGINGMODULE
      CHECKPOINT(0x27)
      logging_ticker();
#endif
#ifdef OVMS_ACCMODULE
      CHECKPOINT(0x28)
      acc_ticker();
#endif
    }
    else if (TMR0H != y)
    {
      if ((TMR0H % 0x04) == 0)
      {
        CHECKPOINT(0x29)
        net_ticker10th();
        CHECKPOINT(0x2A)
        vehicle_ticker10th();
        CHECKPOINT(0x2B)
      }
      y = TMR0H;
    }
  }
////////////////////////////////////////////////////////////////////////
// vehicle_initialise()
// This function is an entry point from the main() program loop, and
// gives the vehicle framework an opportunity to initialise itself.
//
void vehicle_initialise(void)
  {
  char *p;

  vehicle_fn_init = NULL;
  vehicle_fn_poll0 = NULL;
  vehicle_fn_poll1 = NULL;
  vehicle_fn_ticker1 = NULL;
  vehicle_fn_ticker10 = NULL;
  vehicle_fn_ticker60 = NULL;
  vehicle_fn_ticker300 = NULL;
  vehicle_fn_ticker600 = NULL;
  vehicle_fn_ticker = NULL;
  vehicle_fn_ticker10th = NULL;
  vehicle_fn_idlepoll = NULL;
  vehicle_fn_commandhandler = NULL;
  vehicle_fn_smshandler = NULL;
  vehicle_fn_smsextensions = NULL;

  // Clear the internal GPS flag, unless specifically requested by the module
  net_fnbits &= ~(NET_FN_INTERNALGPS);

  p = par_get(PARAM_VEHICLETYPE);
  if (p == NULL)
    {
    car_type[0] = 0; // Car is undefined
    car_type[1] = 0;
    car_type[2] = 0;
    car_type[3] = 0;
    car_type[4] = 0;
    }
#ifdef OVMS_CAR_TESLAROADSTER
  else if (memcmppgm2ram(p, (char const rom far*)"TR", 2) == 0)
    {
    void vehicle_teslaroadster_initialise(void);
    vehicle_teslaroadster_initialise();
    }
#endif
#ifdef OVMS_CAR_VOLTAMPERA
  else if (memcmppgm2ram(p, (char const rom far*)"VA", 2) == 0)
    {
    void vehicle_voltampera_initialise(void);
    vehicle_voltampera_initialise();
    }
#endif
#ifdef OVMS_CAR_RENAULTTWIZY
  else if (memcmppgm2ram(p, (char const rom far*)"RT", 2) == 0)
    {
    void vehicle_twizy_initialise(void);
    vehicle_twizy_initialise();
    }
#endif
#ifdef OVMS_CAR_OBDII
  else if (memcmppgm2ram(p, (char const rom far*)"O2", 2) == 0)
    {
    void vehicle_obdii_initialise(void);
    vehicle_obdii_initialise();
    }
#endif
#ifdef OVMS_CAR_THINKCITY
  else if (memcmppgm2ram(p, (char const rom far*)"TC", 2) == 0)
    {
    void vehicle_thinkcity_initialise(void);
    vehicle_thinkcity_initialise();
    }
#endif
#ifdef OVMS_CAR_NISSANLEAF
  else if (memcmppgm2ram(p, (char const rom far*)"NL", 2) == 0)
    {
    void vehicle_nissanleaf_initialise(void);
    vehicle_nissanleaf_initialise();
    }
#endif
#ifdef OVMS_CAR_NONE
  else
    {
    void vehicle_none_initialise(void);
    vehicle_none_initialise();
    }
#endif

  RCONbits.IPEN = 1; // Enable Interrupt Priority
  PIE3bits.RXB1IE = 1; // CAN Receive Buffer 1 Interrupt Enable bit
  PIE3bits.RXB0IE = 1; // CAN Receive Buffer 0 Interrupt Enable bit
  IPR3 = 0b00000011; // high priority interrupts for Buffers 0 and 1

  p = par_get(PARAM_MILESKM);
  can_mileskm = *p;
  }
void par_getbase64(unsigned char param, void* dest, size_t length)
  {
  char *p = par_get(param);
  memset(dest,0,length);
  base64decode(p, dest);
  }
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();
          }
        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;
  }
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, ",", 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);
}