/*
;    Project:       Open Vehicle Monitor System
;    Date:          6 May 2012
;
;    Changes:
;    1.4  27.09.13 (Thomas)
;         - Fixed battery temperature reading.
;         - TODO: Fix charge status on charge interruption.
;    1.3  23.09.13 (Thomas)
;         - Fixed charging notification when battery is full, SOC=100%
;         - TODO: Fix battery temperature reading. 
;    1.2  22.09.13 (Thomas)
;         - Fixed Ideal range
;         - Added parking timer
;         - Added detection of car is asleep.
;    1.1  21.09.13 (Thomas)
;         - Fixed ODO
;         - Fixed filter and mask for poll0 (thanks to Matt Beard)
;         - Verified estimated range
;         - Verified SOC
;         - Verified Charge state
;         - Added battery temperature reading (thanks to Matt Beard)
;    1.0  Initial release
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/

#include <stdlib.h>
#include <delays.h>
#include <string.h>
#include "ovms.h"
#include "params.h"
#include "net_msg.h"

// Mitsubishi state variables

#pragma udata overlay vehicle_overlay_data
unsigned char mi_charge_timer;     // A per-second charge timer
unsigned long mi_charge_wm;        // A per-minute watt accumulator
unsigned char mi_candata_timer;    // A per-second timer for CAN bus data

signed char mi_batttemps[24];      // Temperature buffer, holds first two temps from the 0x6e1 messages (24 of the 64 values)

// Variables to calculate the estimated range during rapid/quick charge
unsigned char mi_QC;               // Simple flag that is non-zero when quick charging (i.e. states 2, 3 or 4)
unsigned char mi_QC_state;         // Quick charge state
                                   //   0=not QC, we have no reason te believe we are quick charging
                                   //   1=detecting QC, estrange is 255, if stable for 5 seconds we assume we are in QC mode
                                   //   2=QC wait to start, QC mode entered but current is 0, if here for > 60 seconds we abort the QC detection and go to ended
                                   //   3=QC active, QC mode entered and current flowing
                                   //   4=QC wait to end, QC mode entered and have seen current but is now 0, if 0 for 5 seconds enter ended
                                   //   5=QC ended, we appear to have completed a QC - will wait here and return to idle when estrange != 255
signed char mi_QC_countdown;       // Counter used for QC state detection, when > 0 it will be decremented each second
unsigned char mi_estrange;         // The estimated range from the CAN, which is used if valid
unsigned char mi_last_good_SOC;    // The last known good SOC, or 255 if none yet seen
unsigned char mi_last_good_range;  // The last known good range, or 255 if none yet seen
unsigned char mi_debug_disp;       // Flag for what debug to display during rapid, 0 = last_good_SOC, non-0 = last_good_range
unsigned int mi_BattVolt;          // Battery voltage in V * 10
int mi_BattCurr;                   // Battery current in A * 100, where -ve = discharge +ve = charge
#pragma udata

////////////////////////////////////////////////////////////////////////
// vehicle_mitsubishi_ticker1()
// This function is an entry point from the main() program loop, and
// gives the CAN framework a ticker call approximately once per second
//
BOOL vehicle_mitsubishi_ticker1(void)
  {
  
  ////////////////////////////////////////////////////////////////////////
  // Sleep detection
  ////////////////////////////////////////////////////////////////////////
  
  if (mi_candata_timer > 0)
    {
    if (--mi_candata_timer == 0) 
      { // Car has gone to sleep
      car_doors3 &= ~0x01;  // Car is asleep
      }
    else
      {
      car_doors3 |= 0x01;   // Car is awake
      }
    }
  
  car_time++;
  
  // Current app expects the cooling pump to be running if the temperatures are not stale!
  if(car_stale_temps == 0) car_doors3bits.CoolingPump=0;
  else car_doors3bits.CoolingPump=1;


  ////////////////////////////////////////////////////////////////////////
  // Quick/rapid charge detection
  ////////////////////////////////////////////////////////////////////////
  // Range of 255 is returned during rapid/quick charge
  // We can use this to indicate charge rate, but we will need to
  // calculate a new range based on the last seen SOC and estrange

  // A reported estimated range of 255 is a probable signal that we are rapid charging.
  // However, this has been seen to be reported during turn-on initialisation and
  // it may be read in error at times too. So, we only accept it if we have seen it
  // for 5 consecutive 1-second ticks. Then mi_rapid_countdown == 0 becomes an indicator
  // of the rapid charge state
  if(mi_estrange == 255)
    {
    switch(mi_QC_state)
      {
      default:      // Not yet in QC mode
      case 0:
        // Enter detection state for 5 seconds
        mi_QC_countdown = 5;
        mi_QC_state = 1;
        break;

      case 1:       // Detection state, wait for 5 seconds of stable estrange=255
        if(mi_QC_countdown > 0) mi_QC_countdown--;
        else
          {
          // estrange has been 255 for 5 seconds - we have detected an attempt to QC
          mi_QC = 1;

          if(mi_BattCurr > 0)
            {
            // Current is flowing into the battery - enter main QC state
            mi_QC_state = 3;
            }
          else
            {
            // Battery not yet charging - wait up to 60 seconds
            mi_QC_countdown = 60;
            mi_QC_state = 2;
            }
          }

      case 2:       // Waiting for charge current to start flowing
        if (mi_BattCurr > 0)
          {
          // Current flowing into the battery - enter main QC state
          mi_QC_state = 3;
          }
        else
          {
          if(mi_QC_countdown > 0) mi_QC_countdown--;
          else
            {
            // Charging did not start within 60 seconds - flag no longer doing QC and go to ended state and wait
            mi_QC = 0;
            mi_QC_state = 5;
            }
          }
        break;

      case 3:      // Main QC state - current has been seen to flow into the battery
        if(mi_BattCurr <= 0)
          {
          // Current has stopped flowing - enter end detection to see if it has stopped for 5 seconds
          mi_QC_countdown = 5;
          mi_QC_state = 4;
          }
        break;

      case 4:       // QC may be ending as charge current seen to be 0 - if it stays here for 5 seconds end QC
        if(mi_BattCurr > 0)
          {
          // Current resumed - return to main charge state
          mi_QC_state = 3;
          }
        else
          {
          if(mi_QC_countdown > 0) mi_QC_countdown--;
          else
            {
            // No current flowed for 5 seconds - flag no longer doing QC and go to ended state and wait
            mi_QC = 0;
            mi_QC_state = 5;
            }
          }
        break;

      case 5:       // End of QC - do nothing, only exit this state when estrange != 255
        break;
      }
    }
  else
    {
    // If we have just exited QC mode - clear AC charge values so end of charge is detected
    if(mi_QC != 0)
      {
      car_chargecurrent = 0;
      car_linevoltage = 0;
      }

    // Not in QC
    mi_QC = 0;
    mi_QC_state = 0;
    }

  if(mi_QC == 0)
    {
    if(mi_estrange != 255)
      {
      if (can_mileskm == 'K')
        {
        car_estrange = (unsigned int)(MiFromKm((unsigned long)mi_estrange));
        }
      else
        {
        car_estrange = (unsigned int)mi_estrange;
        }

      // Store figures for calculating the estrange during next quick/rapid charge
      if((car_SOC > 20) && (car_estrange > 5))
        {
          mi_last_good_SOC = car_SOC;
          mi_last_good_range = car_estrange;
        }
      }
    }
  else
    {
    if((mi_last_good_SOC == 255) || (car_SOC <= 10))
      {
      car_estrange = 0;  
      }
    else
      {
      // Simple range stimation during quick/rapid charge based on
      // last seen decent values of SOC and estrange and assuming that
      // estrange hits 0 at 10% SOC and is linear from there to 100%
        
      // If the last known SOC was too low for accurate guesstimating, 
      // use fundge-figures giving 72 mile range as it is probably best
      // to guess low-ish here (but not silly low)
      if(mi_last_good_SOC <= 15)
        {
          mi_last_good_SOC = 20;
          mi_last_good_range = 8;
        }
          
      // Calculate the guesstimate, taking care to multiply first to avoid integer truncation problems
      car_estrange = ((unsigned int)mi_last_good_range * (unsigned int)(car_SOC - 10)) / ((unsigned int)(mi_last_good_SOC - 10));
      }
    }
  
  ////////////////////////////////////////////////////////////////////////
  // Charge state determination
  ////////////////////////////////////////////////////////////////////////
  // 0x04 Chargeport open, bit 2 set
  // 0x08 Pilot signal on, bit 3 set
  // 0x10 Vehicle charging, bit 4 set
  // 0x0C Chargeport open and pilot signal, bits 2,3 set
  // 0x1C Bits 2,3,4 set

  if((mi_QC != 0) || ((car_chargecurrent!=0)&&(car_linevoltage>100)))
    { // CAN says the car is charging
    car_doors5bits.Charging12V = 1;  //MJ
    // Question: Does the 12V always charge with AC charging? How about rapid charging?
    if ((car_doors1 & 0x08)==0)
      { // Charge has started
      car_doors1 |= 0x1c;      // Set charge, door and pilot bits
      //car_doors1 |= 0x0c;      // Set charge, and pilot bits
      car_chargemode = 0;      // Standard charge mode
      car_chargestate = 1;     // Charging state
      car_chargesubstate = 3;  // Charging by request
      if(mi_QC != 0)
        {
        car_chargelimit = 125; // Signal quick/rapid charge
        }
      else
        {
        car_chargelimit = 16;  // Hard-code 16A charge limit
        }
      car_chargeduration = 0;  // Reset charge duration
      car_chargekwh = 0;       // Reset charge kWh
      mi_charge_timer = 0;     // Reset the per-second charge timer
      mi_charge_wm = 0;        // Reset the per-minute watt accumulator
      net_req_notification(NET_NOTIFY_STAT);
      }
    else
      { // Charge is ongoing
      car_doors1bits.ChargePort = 1;  //MJ
      mi_charge_timer++;
      if (mi_charge_timer>=60)
        { // One minute has passed
        mi_charge_timer=0;
        car_chargeduration++;
        // Note: mi_BattCurr is A*100 and BattVolt = V*10 so we need /1000
        mi_charge_wm += (mi_BattCurr*mi_BattVolt) / 1000;
        if (mi_charge_wm >= 60000L)
          { // Let's move 1kWh to the virtual car
          car_chargekwh += 1;
          mi_charge_wm -= 60000L;
          }
        }
      }
    }

  // Not charging (but still connected?)
  else if ( (mi_QC_state == 5) || ((mi_QC == 0)&&(car_chargecurrent==0)&&(car_linevoltage>=100)))
    { // CAN says the car is not charging
    if ((car_doors1 & 0x08)&&((car_SOC>=95) || (mi_QC_state == 5)))
      { // Charge has completed (i.e. is >= 95% or the QC charger has stopped the charge)
      car_doors1 &= ~0x18;    // Clear charge and pilot bits
      //car_doors1 &= ~0x0c;    // Clear charge and pilot bits
      car_doors1bits.ChargePort = 1;  //MJ
      car_chargemode = 0;     // Standard charge mode
      car_chargestate = 4;    // Charge DONE
      car_chargesubstate = 3; // Leave it as is
      net_req_notification(NET_NOTIFY_CHARGE);  // Charge done Message MJ
      mi_charge_timer = 0;       // Reset the per-second charge timer
      mi_charge_wm = 0;          // Reset the per-minute watt accumulator
      net_req_notification(NET_NOTIFY_STAT);
      }
    car_doors5bits.Charging12V = 0;  // MJ
    }

  else if ((mi_QC == 0)&&(car_chargecurrent==0)&&(car_linevoltage<100))
    { // CAN says the car is not charging
    if (car_doors1 & 0x08)
      { // Charge has completed / stopped
      car_doors1 &= ~0x18;    // Clear charge and pilot bits
      //car_doors1 &= ~0x0c;    // Clear charge and pilot bits
      car_doors1bits.ChargePort = 1;  //MJ
      car_chargemode = 0;     // Standard charge mode
      if (car_SOC < 95)
        { // Assume charge was interrupted
        car_chargestate = 21;    // Charge STOPPED
        car_chargesubstate = 14; // Charge INTERRUPTED
        net_req_notification(NET_NOTIFY_CHARGE);
        }
      else
        { // Assume charge completed normally
        car_chargestate = 4;    // Charge DONE
        car_chargesubstate = 3; // Leave it as is
        net_req_notification(NET_NOTIFY_CHARGE);  // Charge done Message MJ
        }
      mi_charge_timer = 0;       // Reset the per-second charge timer
      mi_charge_wm = 0;          // Reset the per-minute watt accumulator
      net_req_notification(NET_NOTIFY_STAT);
      }
    car_doors5bits.Charging12V = 0;  // MJ
    car_doors1bits.ChargePort = 0;   // Charging cable unplugged, charging door closed.
    }

  return FALSE;
  }

  
////////////////////////////////////////////////////////////////////////
// vehicle_mitsubishi_ticker10()
// State Model: 10 second ticker
// This function is called approximately once every 10 seconds (since state
// was first entered), and gives the state a timeslice for activity.
//

BOOL vehicle_mitsubishi_ticker10(void)
  {
   ////////////////////////////////////////////////////////////////////////
  // Battery temperature
  ////////////////////////////////////////////////////////////////////////
  
  int i;
  signed int tbattery = 0;
  for(i=0; i<24; i++)
    {
    tbattery += mi_batttemps[i];
    }
  car_tbattery = tbattery / 24;

  // Rapid charge estrange calculation debug - ambient temp alternates between showing mi_last_good_SOC and mi_last_good_range
  // NOTE: EXTRA DEBUG, WHEN FEATURE11 SET, WILL DISPLAY ALWAYS
/*  if((sys_features[PARAM_FEATURE11] > 0) || (mi_QC != 0))
      {
      if(mi_debug_disp == 0)
          {
          car_ambient_temp = mi_last_good_SOC;
          mi_debug_disp = 1;
          }
      else
          {
          car_ambient_temp = mi_last_good_range;
          mi_debug_disp = 0;
          }

          car_stale_ambient = 60;
      }
  else 
      {
      // Clear the debug display when not rapid charging
      car_ambient_temp = -127;
      car_stale_ambient = 0;
      }
*/
  return FALSE;
  }

////////////////////////////////////////////////////////////////////////
// can_poll()
// This function is an entry point from the main() program loop, and
// gives the CAN framework an opportunity to poll for data.
//
BOOL vehicle_mitsubishi_poll0(void)
  {
  unsigned int id = ((unsigned int)RXB0SIDL >>5)
                  + ((unsigned int)RXB0SIDH <<3);

  can_datalength = RXB0DLC & 0x0F; // number of received bytes
  can_databuffer[0] = RXB0D0;
  can_databuffer[1] = RXB0D1;
  can_databuffer[2] = RXB0D2;
  can_databuffer[3] = RXB0D3;
  can_databuffer[4] = RXB0D4;
  can_databuffer[5] = RXB0D5;
  can_databuffer[6] = RXB0D6;
  can_databuffer[7] = RXB0D7;

  RXB0CONbits.RXFUL = 0; // All bytes read, Clear flag

  mi_candata_timer = 60;  // Reset the timer
  
  switch (id)
    {

    case 0x346: //Range
      {
//          // FUDGE CODE: Setting feature 12 will simulate a rapid charge
//          if(sys_features[PARAM_FEATURE12] > 0) mi_estrange = 255;
//          else
      mi_estrange = can_databuffer[7];
      break;
      }
    
    case 0x373:
      // BatCurr & BatVolt - using data from http://myimiev.com/forum/viewtopic.php?p=4445&sid=d94a0757f19d9e97c25c794dc4f91ca9#p4445
      
      // NOTE: I had issues when combining this on one line - even with parentheses
      mi_BattCurr = can_databuffer[2];
      mi_BattCurr -= 128;
      mi_BattCurr = (mi_BattCurr << 8) + can_databuffer[3];

      mi_BattVolt = (((unsigned int)can_databuffer[4]) << 8) + (unsigned int)can_databuffer[5];

      // When rapid charging, use the charger input V&A
      if(mi_QC != 0)
        {
        // Convert battery current to a magnitude in A and hard limit at 255
        int BattCurr = mi_BattCurr / 100;
        if(BattCurr < 0) BattCurr = 0 - BattCurr;
        if(BattCurr > 255) BattCurr = 255;
        car_chargecurrent = BattCurr;

        // Convert the battery voltage to V
        car_linevoltage = mi_BattVolt / 10;
      }
      break;

    case 0x374: //SOC
      {
      car_SOC = (unsigned char)(((unsigned int)can_databuffer[1] - 10) / 2); 
      car_idealrange = ((((unsigned int)car_SOC) * 93) / 100); //Ideal range: 93 miles (150 Km).
      break;
      }

    case 0x389: //charge voltage & current
      // When not rapid charging, read the charger input V&A
      if(mi_QC == 0)
        {
        car_linevoltage = (unsigned char)can_databuffer[1];
        car_chargecurrent = (unsigned char)((unsigned int)can_databuffer[6] / 10);
        }
      break;
    }

  return TRUE;
  }

BOOL vehicle_mitsubishi_poll1(void)
  {
  unsigned int id = ((unsigned int)RXB1SIDL >>5)
                  + ((unsigned int)RXB1SIDH <<3);
  
  can_datalength = RXB1DLC & 0x0F; // number of received bytes
  can_databuffer[0] = RXB1D0;
  can_databuffer[1] = RXB1D1;
  can_databuffer[2] = RXB1D2;
  can_databuffer[3] = RXB1D3;
  can_databuffer[4] = RXB1D4;
  can_databuffer[5] = RXB1D5;
  can_databuffer[6] = RXB1D6;
  can_databuffer[7] = RXB1D7;

  RXB1CONbits.RXFUL = 0;        // All bytes read, Clear flag

  mi_candata_timer = 60;  // Reset the timer
  
  switch (id)
    {
    
    case 0x285:
      {
      if (can_databuffer[6] == 0x0C) // Car in park
        {
        car_doors1 |= 0x40;     //  PARK
        car_doors1 &= ~0x80;    // CAR OFF
        if (car_parktime == 0)
          {
          car_parktime = car_time-1;    // Record it as 1 second ago, so non zero report
          net_req_notification(NET_NOTIFY_ENV);
          }
        }
      
      else if (can_databuffer[6] == 0x0E) // Car not in park
        {
        car_doors1 &= ~0x40;     //  NOT PARK
        car_doors1 |= 0x80;      // CAR ON
        if (car_parktime != 0)
          {
          car_parktime = 0; // No longer parking
          net_req_notification(NET_NOTIFY_ENV);
          }
        }
      break;
      }

    case 0x286: // Charger temp + 40C?
      {
      car_tpem = (signed char)can_databuffer[3] - 40;
      car_stale_temps = 60; // Reset stale indicator
      break;
      }

    case 0x298: // Motor temp + 40C?
      {
      car_tmotor = (unsigned char)can_databuffer[3] - 40;
      car_stale_temps = 60; // Reset stale indicator
      break;
      }

    case 0x412: //Speed & Odo
      {
      if (can_databuffer[1] > 200) //Speed
        {
        car_speed = (unsigned char)((unsigned int)can_databuffer[1] - 255);
        }
      else
        {
        car_speed = (unsigned char) can_databuffer[1];
        }

      if (can_mileskm == 'M') // Odo
        {
        car_odometer = MiFromKm((((unsigned long) can_databuffer[2] << 16) + ((unsigned long) can_databuffer[3] << 8) + can_databuffer[4]) * 10);
        }
      else
        {
        car_odometer = ((((unsigned long) can_databuffer[2] << 16) + ((unsigned long) can_databuffer[3] << 8) + can_databuffer[4]) * 10);
        }
      break;
      }
    
    case 0x6e1: 
      {
       // Calculate average battery pack temperature based on 24 of the 64 temperature values
       // Message 0x6e1 carries two temperatures for each of 12 banks, bank number (1..12) in byte 0,
       // temperatures in bytes 2 and 3, offset by 50C
       int idx = can_databuffer[0];
       if((idx >= 1) && (idx <= 12))
         {
         idx = ((idx << 1)-2);
         mi_batttemps[idx] = (signed char)can_databuffer[2] - 50;
         mi_batttemps[idx + 1] = (signed char)can_databuffer[3] - 50;
         car_stale_temps = 60; // Reset stale indicator
         }
      break;
      }
    }
  
  return TRUE;
  }


////////////////////////////////////////////////////////////////////////
// vehicle_mitsubishi_initialise()
// This function is an entry point from the main() program loop, and
// gives the CAN framework an opportunity to initialise itself.
//
BOOL vehicle_mitsubishi_initialise(void)
  {
  char *p;
  int i;
  
  car_type[0] = 'M'; // Car is type MI - Mitsubishi iMiev
  car_type[1] = 'I';
  car_type[2] = 0;
  car_type[3] = 0;
  car_type[4] = 0;

  // Vehicle specific data initialisation
  car_stale_timer = -1; // Timed charging is not supported for OVMS MI
  car_time = 0;
  mi_candata_timer = 0;
  mi_QC = 0;
  mi_QC_state = 0;
  mi_QC_countdown = 0;
  mi_estrange = 0;
  mi_last_good_SOC = 0;
  mi_last_good_range = 0;
  mi_debug_disp = 0;
  mi_BattVolt = 0;
  mi_BattCurr = 0;
  
   // Clear the battery temperatures
  for(i=0; i<24; i++) mi_batttemps[i] = 0;

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

  // We are now in Configuration Mode

  // Filters: low byte bits 7..5 are the low 3 bits of the filter, and bits 4..0 are all zeros
  //          high byte bits 7..0 are the high 8 bits of the filter
  //          So total is 11 bits

  // Buffer 0 (filters 0, 1) for extended PID responses
  RXB0CON = 0b00000000;

  /* PIDs of interest:
   *   0x346: 011 0100 0110
   *   0x373: 011 0111 0011
   *   0x374: 011 0111 0100
   *   0x389: 011 1000 1001
   *
   * Grouping multiple PIDs:
   *   0x346: 011 0100 0110
   *   0x373: 011 0111 0011
   *   0x374: 011 0111 0100
   *          ~~~~~~~~~~~~~
   *          XXX XX-- X---
   *
   * Where X = same and - = different
   *
   * So, mask of 11111001000 will group these in the same buffer
   */

  // Mask0 = 0b11111001000 (0x7C8), filterbit 0-2 and 4-5 deactivated
  RXM0SIDL = 0b00000000;
  RXM0SIDH = 0b11111001;

  // Filter0 0b01101000000 (0x340..0x347 to 0x370..0x377 - includes 0x346, 0x373 and 0x374)
  RXF0SIDL = 0b00000000;
  RXF0SIDH = 0b01101000;

  // Filter1 0b01110001001 (0x389)
  RXF1SIDL = 0b00100000;
  RXF1SIDH = 0b01110001;


  // Buffer 1 (filters 2, 3, 4 and 5) for direct can bus messages
  RXB1CON  = 0b00000000;	// RX buffer1 uses Mask RXM1 and filters RXF2, RXF3, RXF4, RXF5

  // Mask1 = 0b11111111100 (0x7FC)
  // Note: We deactivate bits 0 and 1 to group 0x285 and 0x286 into the same buffer
  RXM1SIDL = 0b10000000;
  RXM1SIDH = 0b11111111;

  // Filter2 0b010100001xx (0x285 & 0x286)
  RXF2SIDL = 0b10000000;
  RXF2SIDH = 0b01010000;

  // Filter3 0b10000010010 (0x412)
  RXF3SIDL = 0b01000000;
  RXF3SIDH = 0b10000010;

  // Filter4 0b11011100001 (0x6E1)
  // Sample the first of the battery values
  RXF4SIDL = 0b00100000;
  RXF4SIDH = 0b11011100;

  // Filter5 0b01010011000 (0x298)
  RXF5SIDL = 0b00000000;
  RXF5SIDH = 0b01010011;

  // CAN bus baud rate

  BRGCON1 = 0x01; // SET BAUDRATE to 500 Kbps
  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
    }

  // Hook in...
  vehicle_fn_poll0 = &vehicle_mitsubishi_poll0;
  vehicle_fn_poll1 = &vehicle_mitsubishi_poll1;
  vehicle_fn_ticker1 = &vehicle_mitsubishi_ticker1;
  vehicle_fn_ticker10 = &vehicle_mitsubishi_ticker10;

  net_fnbits |= NET_FN_INTERNALGPS;   // Require internal GPS
  net_fnbits |= NET_FN_12VMONITOR;    // Require 12v monitor

  return TRUE;
  }
