ESP32車上診斷系統(OBD)模擬器

本文旨在補充《超圖解ESP32應用實作》第18章的車上診斷系統(OBD)實驗,方便沒有汽車的讀者使用ESP32當作OBD模擬器,傳遞虛擬的行車速度和引擎轉速值給另一個ESP32,並且把數值顯示在連線的手機瀏覽器。

本文的麵包板組裝電路,沿用《超圖解ESP32應用實作》第17章的「動手做17-1:ESP32 CAN匯流排通訊實驗」,加上一個類比搖桿來模擬車速和引擎轉速值:

  • 水平(X)搖桿的輸入值:改變車速值0~180 (km/h)。
  • 垂直(Y)搖桿的輸入值:改變引擎轉速值1500~4000 (RPM)

連接類比搖桿的ESP32開發板A,扮演車上診斷系統(OBD),執行OBD模擬程式;開發板B則是OBD診斷裝置,執行第18章「動手做18-2:在手機瀏覽器呈現即時車速和引擎轉速」的程式。

ESP32車上診動系統(OBD)模擬程式

OBD模擬器修改自coniferconifer編寫的ESP32-ECU-emulator,筆者僅刪除一些不必要的變數和前置處理器指令,然後修改CAN收發器的接腳設定,並且加入讀取搖桿數值的程式碼,請編譯底下的程式,上傳到「開發板A」備用:

#include <CAN.h>
#include <OBD2.h>
#define CTX_PIN   21  // CAN收發器傳送腳
#define CRX_PIN   22  // CAN收發器接收腳
#define IN_X 32       // 可變電阻X(水平搖桿)的輸入腳
#define IN_Y 33       // 可變電阻Y(垂直搖桿)的輸入腳
#define ECU_ID 0x7e8 //ECU id + 8
#define MIL_ON 0x80
#define DTC_CNT 0x01

uint8_t DTC[] = { MIL_ON | DTC_CNT , 0x00, 0x00} ; //A,B,C,D
uint16_t freezeDTC = 0x1234;
uint16_t fuelSystemStatus = 0x0200;
float engineEfficiency = 22.75; // %
float shortTermFuelTrimBank1 = -5.47;// %
float longTermFuelTrimBank1 = 7.2;// %
float shortTermFuelTrimBank2 = -5.47;// %
float longTermFuelTrimBank2 = 7.2;// %


uint8_t fuelPressure = 765;
uint8_t intakeManifoldAbsolutePressure = 255;
uint16_t engineRPM = 3925;
uint8_t vehicleSpeed = 120;
uint8_t timingAdvance = 8;
uint8_t airIntakeTemperature = 42 ;
float mafAirFlowRate = 6.50 ;
float throttlePosition = 5.5 ;// %
uint8_t commandedSecondaryAirStatus = 1;
uint8_t oxygenSensorsPresentIn2Banks = 3;

float oxygenSensor1 = 1.23 ; //Volt
float shortTermFuelTrim = 99.2; // %

uint8_t obdStandardThisVehicleConformsTo = 0x0a;
uint8_t oxygenSensorsPresentIn4Banks = 0x01;
uint8_t auxiliaryInputStatus = 0x01;
uint16_t distanceTraveledWithMilOn = 1000; // km DISTANCE_TRAVELED_WITH_MIL_ON
float fuelRailPressure = 5177.265; //kPa
float fuelRailGaugePressure = 655350.0; //kPa
uint8_t engineCoolantTemperature = 82;
uint8_t fuelLevel = 80; // %

float oxygenSensorFuelAir = 1.0; //ratio
float oxygenSensorFuelAirVoltage = 1.23; //volt max 8V

float controlModuleVoltage = 14.24;//Volt
float commandedEGR = 1.8; // %
float EGRError = 0.0 ; // %
float commandedEvaporativePurge = 10.0; //% COMMANDED_EVAPORATIVE_PURGE
uint8_t warmUpsSinceCodesCleared = 255; // counts WARM_UPS_SINCE_CODES_CLEARED
uint16_t distanceTraveledSinceCodesCleared = 30613; //km DISTANCE_TRAVELED_SINCE_CODES_CLEARED
float evapSystemVaporPressure = 21.34 ; // EVAP_SYSTEM_VAPOR_PRESSURE
uint8_t absoluteBarometricPressure = 215; //kPa ABSOLULTE_BAROMETRIC_PRESSURE

float catalystTemperatureBank1Sensor1 = 48.0; //dgree CATALYST_TEMPERATURE_BANK_1_SENSOR_1
float absoluteLoadValue = 19.61; //% ABSOLUTE_LOAD_VALUE
uint8_t ambientAirTemperature = 31; //degree
uint8_t fuelType = 1; //FUEL_TYPE
int i = 0;

uint8_t pidList1[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList2[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList3[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList4[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList5[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList6[4] = {0xff  , 0xff, 0xff, 0xff} ;
uint8_t pidList7[4] = {0xff  , 0xff, 0xff, 0xff} ;

void setPidList1_20(uint8_t pid)
{
  Serial.printf("PidList1_20 %d %02x %02x %02x %02x\r\n", pid, pidList1[0], pidList1[1], pidList1[2], pidList1[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList1[0]); CAN.write(pidList1[1]); CAN.write(pidList1[2]); CAN.write(pidList1[3]);
  CAN.endPacket();
}
void setPidList21_40(uint8_t pid)
{
  Serial.printf("PidList1_20 %d %02x %02x %02x %02x\r\n", pid, pidList2[0], pidList2[1], pidList2[2], pidList2[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList2[0]); CAN.write(pidList2[1]); CAN.write(pidList2[2]); CAN.write(pidList2[3]);
  CAN.endPacket();
}
void setPidList41_60(uint8_t pid)
{
  Serial.printf("PidList41-60 %d %02x %02x %02x %02x\r\n", pid, pidList3[0], pidList3[1], pidList3[2], pidList3[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList3[0]); CAN.write(pidList3[1]); CAN.write(pidList3[2]); CAN.write(pidList3[3]);
  CAN.endPacket();
}
void setPidList61_80(uint8_t pid)
{
  Serial.printf("PidList61-80 %d %02x %02x %02x %02x\r\n", pid, pidList4[0], pidList4[1], pidList4[2], pidList4[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList4[0]); CAN.write(pidList4[1]); CAN.write(pidList4[2]); CAN.write(pidList4[3]);
  CAN.endPacket();
}
void setPidList81_a0(uint8_t pid)
{
  Serial.printf("PidList81-a0 %d %02x %02x %02x %02x\r\n", pid, pidList5[0], pidList5[1], pidList5[2], pidList5[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList5[0]); CAN.write(pidList5[1]); CAN.write(pidList5[2]); CAN.write(pidList5[3]);
  CAN.endPacket();
}
void setPidLista1_c0(uint8_t pid)
{
  Serial.printf("PidListA1-C0 %d %02x %02x %02x %02x\r\n", pid, pidList6[0], pidList6[1], pidList6[2], pidList6[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList6[0]); CAN.write(pidList6[1]); CAN.write(pidList6[2]); CAN.write(pidList6[3]);
  CAN.endPacket();
}
void setPidListc1_e0(uint8_t pid)
{
  Serial.printf("PidListC1-E0 %d %02x %02x %02x %02x\r\n", pid, pidList7[0], pidList7[1], pidList7[2], pidList7[3]);
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(pid);
  CAN.write(pidList7[0]); CAN.write(pidList7[1]); CAN.write(pidList7[2]); CAN.write(pidList7[3]);
  CAN.endPacket();
}

/*
   Monitor status since DTCs cleared.
   (Includes malfunction indicator lamp (MIL) status and number of DTCs.)
*/
void setDTC(uint8_t *DTC)
{
  uint8_t *dtc = DTC;
  Serial.println("DTC");
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(MONITOR_STATUS_SINCE_DTCS_CLEARED);
  CAN.write(*dtc++);
  CAN.write(*dtc++);
  CAN.write(*dtc++);
  CAN.write(*dtc);
  CAN.endPacket();
}
void set4bytes(uint32_t data, uint8_t PID)
{
  CAN.beginPacket(ECU_ID);
  CAN.write(0x06); //DLC 6
  CAN.write(0x41);
  CAN.write(PID);
  CAN.write((uint8_t)(data >> 24));
  CAN.write((uint8_t)(data >> 16));
  CAN.write((uint8_t)(data >> 8));
  CAN.write((uint8_t)(data & 0x000000ff));
  CAN.endPacket();
}
void set2bytes(uint16_t data, uint8_t PID) {
  CAN.beginPacket(ECU_ID);
  CAN.write(4); //DLC 4
  CAN.write(0x41);
  CAN.write(PID);
  CAN.write((uint8_t)(data >> 8));
  CAN.write((uint8_t)(data & 0x00ff));
  CAN.endPacket();
}

void set1byte( uint8_t data, uint8_t PID) {
  CAN.beginPacket(ECU_ID);
  CAN.write(0x03); //DLC 3
  CAN.write(0x41);
  CAN.write(PID);
  CAN.write(data);
  CAN.endPacket();
}

void odb2responder(void * parameter) {
  int valX, valY;  // 暫存類比搖桿輸入值
  
  while (1) {
    int packetSize = CAN.parsePacket();
    if (packetSize) {
      if (!CAN.packetRtr()) {
        if (!CAN.packetExtended()) {
          uint8_t len = CAN.read();
          uint8_t service = CAN.read();
          uint8_t pid = CAN.read();

          Serial.printf("DLC %02x 服務 %02x PID %02x\r\n", len, service, pid);
          if (len != 2) break;

          switch (service) {
            case 0x01:
              switch (pid) {
                case PIDS_SUPPORT_01_20:
                  Serial.println("PID list requested");
                  setPidList1_20(pid);
                  break;
                case PIDS_SUPPORT_21_40:
                  setPidList21_40(pid);
                  break;
                case PIDS_SUPPORT_41_60:
                  setPidList41_60(pid);
                  break;
                case 0x60:
                  setPidList61_80(pid);
                  break;
                case 0x80:
                  setPidList81_a0(pid);
                  break;
                case 0xa0:
                  setPidLista1_c0(pid);
                  break;
                case 0xc0:
                  setPidListc1_e0(pid);
                  break;

                case MONITOR_STATUS_SINCE_DTCS_CLEARED:
                  setDTC(DTC);
                  break;
                case FREEZE_DTC:
                  set2bytes(freezeDTC, pid);
                  break;
                case FUEL_SYSTEM_STATUS:
                  set2bytes(fuelSystemStatus, pid);
                  break;
                case CALCULATED_ENGINE_LOAD:  // engine efficiency
                  if (engineEfficiency > 100.0 ) engineEfficiency = 100.0;
                  if (engineEfficiency < 0 ) engineEfficiency = 0;
                  set1byte( (uint8_t)((engineEfficiency * 255.0) / 100.0), pid);
                  break;
                case ENGINE_COOLANT_TEMPERATURE:  // engine coolant temperature
                  if (engineCoolantTemperature < -40) engineCoolantTemperature = -40;
                  if (engineCoolantTemperature > 215 ) engineCoolantTemperature = 215;
                  set1byte(engineCoolantTemperature + 40, pid);
                  break;
                case SHORT_TERM_FUEL_TRIM_BANK_1  :
                  set1byte((uint8_t)(((shortTermFuelTrimBank1 + 100.0) * 128.0) / 100.0), pid);
                  break;
                case LONG_TERM_FUEL_TRIM_BANK_1  :
                  set1byte((uint8_t)(((longTermFuelTrimBank1 + 100.0) * 128.0) / 100.0), pid);
                  break;
                case SHORT_TERM_FUEL_TRIM_BANK_2  :
                  set1byte((uint8_t)(((shortTermFuelTrimBank2 + 100.0) * 128.0) / 100.0), pid);
                  break;
                case LONG_TERM_FUEL_TRIM_BANK_2  :
                  set1byte((uint8_t)(((longTermFuelTrimBank2 + 100.0) * 128.0) / 100.0), pid);
                  break;
                case FUEL_PRESSURE :
                  set1byte(fuelPressure / 3, pid);
                  break;
                case INTAKE_MANIFOLD_ABSOLUTE_PRESSURE:
                  set1byte(intakeManifoldAbsolutePressure, pid);
                  break;
                case ENGINE_RPM:  // rpm
                  valY = analogRead(IN_Y);  // 垂直(Y)搖桿的輸入值
                  engineRPM = (uint16_t)map(valY, 0, 1023, 1500, 4000);  // 引擎轉速
                  set2bytes( engineRPM, pid);
                  break;
                case VEHICLE_SPEED:  // speed
                  valX = analogRead(IN_X);  // 水平(X)搖桿的輸入值
                  vehicleSpeed = (uint8_t)map(valX, 0, 1023, 0, 180);   // 車速
                  set1byte(vehicleSpeed, pid );
                  break;
                case TIMING_ADVANCE :
                  set1byte((timingAdvance + 64) * 2, pid);
                  break;
                case AIR_INTAKE_TEMPERATURE:
                  set1byte(airIntakeTemperature + 40, pid );
                  break;
                case MAF_AIR_FLOW_RATE:
                  set2bytes((uint16_t)(mafAirFlowRate * 100.0), pid );
                  break;
                case THROTTLE_POSITION  :
                  set1byte((uint8_t)((throttlePosition * 255.0) / 100.0), pid);
                  break;
                case COMMANDED_SECONDARY_AIR_STATUS :
                  set1byte(commandedSecondaryAirStatus, pid );
                  break;
                case OXYGEN_SENSORS_PRESENT_IN_2_BANKS:
                  set1byte( oxygenSensorsPresentIn2Banks, pid);
                  break;
                case OXYGEN_SENSOR_1_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_2_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_3_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_4_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_5_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_6_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_7_SHORT_TERM_FUEL_TRIM:
                case OXYGEN_SENSOR_8_SHORT_TERM_FUEL_TRIM:
                  if ( oxygenSensor1 > 1.275 ) oxygenSensor1 = 1.275;
                  if ( oxygenSensor1 < 0 ) oxygenSensor1 = 0.0;
                  if (shortTermFuelTrim < -100.0 ) shortTermFuelTrim = -100.0;
                  if (shortTermFuelTrim > 99.2 ) shortTermFuelTrim = 99.2;
                  set2bytes((uint16_t)((oxygenSensor1 * 200.0) * 256.0 + (shortTermFuelTrim + 100.0) * 128.0 / 100.0), pid);
                  break;

                case OBD_STANDARDS_THIS_VEHICLE_CONFORMS_TO:
                  set1byte(obdStandardThisVehicleConformsTo, pid );
                  break;
                case OXYGEN_SENSORS_PRESENT_IN_4_BANKS :
                  set1byte(oxygenSensorsPresentIn4Banks, pid );
                  break;
                case AUXILIARY_INPUT_STATUS:
                  set1byte(auxiliaryInputStatus, pid );
                  break;
                case RUN_TIME_SINCE_ENGINE_START:
                  set2bytes( (uint16_t)(millis() / 1000) , pid);
                  break;
                case DISTANCE_TRAVELED_WITH_MIL_ON  :
                  set2bytes(distanceTraveledWithMilOn , pid);
                  break;
                case FUEL_RAIL_PRESSURE:
                  if ( fuelRailPressure > 5177.265 ) fuelRailPressure = 5177.265;
                  if ( fuelRailPressure < 0) fuelRailPressure = 0.0;
                  set2bytes((uint16_t)(fuelRailPressure / 0.079), pid);
                  break;
                case FUEL_RAIL_GAUGE_PRESSURE:
                  if ( fuelRailGaugePressure > 655350 )fuelRailGaugePressure = 655350.0;
                  if ( fuelRailGaugePressure < 0 ) fuelRailGaugePressure = 0.0;
                  set2bytes((uint16_t)(fuelRailGaugePressure / 10), pid );
                  break;

                case OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_2_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_3_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_4_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_5_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_6_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_7_FUEL_AIR_EQUIVALENCE_RATIO  :
                case OXYGEN_SENSOR_8_FUEL_AIR_EQUIVALENCE_RATIO  :
                  set4bytes((uint32_t) (oxygenSensorFuelAir * 65535.0 / 2.0) << 16 + (uint32_t)((oxygenSensorFuelAirVoltage) * 65535.0 / 8.0), pid);
                  break;

                case COMMANDED_EGR:
                  set1byte( (uint8_t)((commandedEGR * 255.0) / 100.0), pid );
                  break;
                case EGR_ERROR  :
                  set1byte( (uint8_t)((EGRError + 100) * 128.0 / 100.0), pid );
                  break;
                case COMMANDED_EVAPORATIVE_PURGE:
                  set1byte((uint8_t)((commandedEvaporativePurge * 255.0) / 100.0), pid );
                  break;
                case FUEL_TANK_LEVEL_INPUT:  // fuel level
                  set1byte( (uint8_t)((fuelLevel * 255) / 100), pid );
                  break;
                case WARM_UPS_SINCE_CODES_CLEARED :
                  set1byte((uint8_t)warmUpsSinceCodesCleared , pid );
                  break;
                case DISTANCE_TRAVELED_SINCE_CODES_CLEARED:
                  break;
                  set2bytes((uint16_t) distanceTraveledSinceCodesCleared, pid);
                  break;
                case EVAP_SYSTEM_VAPOR_PRESSURE:
                  if ( evapSystemVaporPressure > 8191.75 ) evapSystemVaporPressure = 8191.75;
                  if (evapSystemVaporPressure < -8192  )evapSystemVaporPressure = -8, 192;
                  set2bytes((int16_t)(evapSystemVaporPressure * 4.0), pid);
                  break;
                case ABSOLULTE_BAROMETRIC_PRESSURE:
                  set1byte(absoluteBarometricPressure, pid);
                  break;
                case CATALYST_TEMPERATURE_BANK_1_SENSOR_1 :
                case CATALYST_TEMPERATURE_BANK_2_SENSOR_1 :
                case CATALYST_TEMPERATURE_BANK_1_SENSOR_2 :
                case CATALYST_TEMPERATURE_BANK_2_SENSOR_2 :
                  if (catalystTemperatureBank1Sensor1 > 6513.5) catalystTemperatureBank1Sensor1 = 6513.5;
                  if (catalystTemperatureBank1Sensor1 < -40.0) catalystTemperatureBank1Sensor1 = -40.0;
                  set2bytes( (uint16_t)((catalystTemperatureBank1Sensor1 + 40.0) * 10.0), pid);
                  break;
                case CONTROL_MODULE_VOLTAGE:
                  set2bytes( (uint16_t)(controlModuleVoltage * 1000.0), pid);
                  break;
                case ABSOLUTE_LOAD_VALUE:
                  if (absoluteLoadValue > 25700.0 )  absoluteLoadValue = 25700.0;
                  set2bytes( (uint16_t)((absoluteLoadValue * 255.0) / 100.0) , pid);
                  break;
                case AMBIENT_AIR_TEMPERATURE  :
                  set1byte((uint8_t) ambientAirTemperature + 40, pid);
                  break;
                case FUEL_TYPE  :
                  set1byte((uint8_t) fuelType, pid);
                  break;
                default:
                  break;
              }
              break;
            case 0x09:
              switch (pid) {
                case 0x02:
                  // setVIN();
                  break;
                case 0x0a:
                  //  setECUname();
                  break;
                default:
                  break;
              }
              break;
            default:
              break;
          }

        }

        while (CAN.available()) {
          CAN.read();
        }
        delay(1);
      }
    }
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 ECU emulator");
  CAN.setPins(CRX_PIN, CTX_PIN);  // 指定CAN收發器的接腳

  if (!CAN.begin(500e3)) {         // 嘗試用500Kbps連線
    Serial.println ("CAN初始化失敗~");
    while (1);  // 若初始化失敗,程式將停在這裡。
  } else {
    Serial.println ("CAN初始化完畢");
  }

  analogSetAttenuation(ADC_11db);  // 設定類比輸入電壓上限3.6V
  analogSetWidth(10);
  
  xTaskCreatePinnedToCore( odb2responder, "odb2responder", 8096, NULL, 1, NULL, 1);
}

void loop() {
  // 每隔一秒更新虛擬數據
  fuelLevel += 1;
  shortTermFuelTrimBank1 = (float)random(1, 10) - 5.0;
  mafAirFlowRate = (float)random(1, 30) / 3.0;
  throttlePosition += 1;
  engineCoolantTemperature += 1; engineCoolantTemperature = engineCoolantTemperature % 128;
  engineEfficiency += 1.0; engineEfficiency = (float)((int)engineEfficiency % 100);
  delay(1000);
}

這個程式一開始宣告了一些儲存OBD參數的變數,例如:fuelSystemStatus(燃料系統狀態)、engineEfficiency(引擎效率)、fuelPressure(燃油壓力)、engineRPM(引擎轉速)、vehicleSpeed(車輛速度)…,當然這些數值都是虛構的,其中一些值在loop()中,每隔1秒更新,例如,引擎轉速和車輛速度值原本透過底下的敘述更新,筆者將它們刪除:

vehicleSpeed += 1;   // 速度值+1
engineRPM += random(1, 10);  // 隨機設定1~9轉速值 

在setup()中,程式把一個FreeRTOS任務“odb2responder”指定給核心1執行,odb2responder任務負責持續偵聽CAN匯流排的訊息並回應相關請求,假設偵聽到 (0x7DF, 0x02, 0x01, 0x0D),代表「即時行車速度」請求的訊息,這個程式片段將被執行:

case VEHICLE_SPEED: // speed
  valX = analogRead(IN_X);         // 讀取水平(X)搖桿的輸入值 
  vehicleSpeed = (uint8_t)map(valX, 0, 1023, 0, 180);  // 將資料對應成0~180車速 
  set1byte(vehicleSpeed, pid );    // 發出1位元組資料,pid值即是0x0D
  break;

讀取OBD訊息的程式

開發板B執行的是「動手做18-2:在手機瀏覽器呈現即時車速和引擎轉速」單元的程式,只是調整了CAN收發器的接腳設定:

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsServer.h>
#include <SPIFFS.h>
#include <esp32_can.h>
#include <esp32_obd2.h>
#define INTERVAL 1000
#define CTX_PIN  21
#define CRX_PIN  22
#define LED_PIN  5

const char* ssid = "ESP32-OBDII";
const char* password = "87654321";

AsyncWebServer server(80); // 建立HTTP伺服器物件
AsyncWebSocket ws("/ws");  // 建立WebSocket物件

void onSocketEvent(AsyncWebSocket *server,
                   AsyncWebSocketClient *client,
                   AwsEventType type,
                   void *arg,
                   uint8_t *data,
                   size_t len)
{
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("來自%s的用戶%u已連線\n", client->remoteIP().toString().c_str(), client->id());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("用戶%u已離線\n", client->id());
      break;
    case WS_EVT_ERROR:
      Serial.printf("用戶%u出錯了:%s\n", client->id(), (char *)data);
      break;
    case WS_EVT_DATA:
      Serial.printf("用戶%u傳入資料:%s\n", client->id(), (char *)data);
      break;
  }
}

void notifyClients() {
  JsonDocument doc;

  doc["spd"] = OBD2.pidRead(VEHICLE_SPEED);
  doc["rpm"] = OBD2.pidRead(ENGINE_RPM);
  String output;
  serializeJson(doc, output);
  ws.textAll(output);        // 向所有連線的用戶端傳遞JSON字串
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);

  if (!SPIFFS.begin(true)) {
    Serial.println("無法載入SPIFFS記憶體");
    return;
  }

  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  // 設置首頁
  server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html");
  server.serveStatic("/favicon.ico", SPIFFS, "/www/favicon.ico");
  server.serveStatic("/fonts/SevenSegment.woff2", SPIFFS, "/www/fonts/SevenSegment.woff2");
  // 查無此頁
  server.onNotFound([](AsyncWebServerRequest * req) {
    req->send(404, "text/plain", "Not found");
  });

  ws.onEvent(onSocketEvent); // 附加事件處理程式
  server.addHandler(&ws);
  server.begin(); // 啟動網站伺服器
  Serial.println("HTTP伺服器開工了~");

  // 處理OBD(CAN)通訊
  CAN0.setCANPins((gpio_num_t)CRX_PIN, (gpio_num_t)CTX_PIN);
  Serial.println("嘗試連線到OBD2 CAN匯流排…");
  while (1) {
    // 嘗試連線到OBD2 CAN匯流排…
    if (!OBD2.begin()) {
      Serial.println("無法連線!");
      digitalWrite(LED_PIN, LOW);
      delay(500);
      digitalWrite(LED_PIN, HIGH);
      delay(1000);
    } else {
      //連線成功!
      Serial.println("連線成功!");
      digitalWrite(LED_PIN, LOW);
      break;
    }
  }
}

void loop() {
  static uint32_t prevTime = 0;   // 前次時間,宣告成「靜態」變數。
  uint32_t now = millis();        // 目前時間

  if (now - prevTime >= INTERVAL) {
    prevTime = now;

    notifyClients();  // 向網路用戶端傳遞感測資料
  }

  ws.cleanupClients();
}

編譯執行此程式之前,請先上傳data資料夾裡的網頁檔。把開發板A和B都接上電腦,然後將手機或電腦的Wi-Fi連上開發板B,再開啟瀏覽器連到“192.168.4.1”,即可看到開發板A傳入的OBD模擬資料;撥動搖桿,引擎轉速和行車速度也會跟著改變。

讀取所有OBD模擬值的程式

充當OBD診斷器的開發板B,可以執行Sandeep Mistry先生編寫的OBD2_03_DataPrinter範例程式,請記得要修改CAN收發器的接腳設定。編譯上傳到開發板B,它將序列埠監視窗顯示開發板A傳入的各項虛擬參數值:

Posts created 483

2 thoughts on “ESP32車上診斷系統(OBD)模擬器

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top