Convert all projects to PlatformIO

- JC3636W518 sketches: pioarduino 51.03.05 (Arduino core 3.0.5), ESP32-S3
  16MB QIO / OPI PSRAM, vendor libs via lib_extra_dirs, per-project lv_conf.h
- display_gif_embedded uses default_16MB.csv (firmware 3.4 MB > huge_app)
- elm327_wifi_bridge: espressif32 6.10.0 on esp32dev, pinned lib_deps
- elmulator: library in lib/, one env per example, duplicate copies removed
- .ino -> src/main.cpp, headers/lv_conf.h -> include/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patriandClaude Opus 5.5 committed 2026-10-23 00:20:33 +02:00
1 parent 9cefb9ba75
commit 5c04e3f0df
81 files changed
+863 -5463

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
# PlatformIO build output and IDE files
.pio/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
+26 -11
View File
@@ -7,23 +7,38 @@ Because the ESP32-S3 has no Bluetooth Classic, the design used a second (classic
ELM327 --(BT Classic SPP)--> classic ESP32 (elm327_wifi_bridge) --(WiFi AP, HTTP /speed)--> JC3636W518 (display_speed_wifi)
```
Split out of `2-Program Example/Demo_Arduino/ST77916_LVGL_DEMO/` on 2026-10-22. History is in git.
Split out of `2-Program Example/Demo_Arduino/ST77916_LVGL_DEMO/` on 2026-10-22 and converted to PlatformIO. History is in git.
## Sketches
## Projects (all PlatformIO)
| Folder | Runs on | What it does | Was |
|---|---|---|---|
| `display_speed_wifi/` | JC3636W518 | GIF background from SD (`A:wallpaper.gif`) + joins AP `ESP32-Access-Point`, polls `http://192.168.4.1/speed`, shows it in a label | `ST77916_LVGL_DEMO/8.4/` |
| `display_gif_sd/` | JC3636W518 | GIF from SD (`A:wallpaper.gif`) + static text label | `ST77916_LVGL_DEMO/working version/` |
| `display_gif_embedded/` | JC3636W518 | GIF compiled into firmware (`loligod.c`, 13.9 MB source). WiFi code commented out. Stops in `setup()` if no SD card | `ST77916_LVGL_DEMO/` (top level) |
| `elm327_wifi_bridge/` | **classic ESP32** (not S3) | BT Classic → ELM327 named `OBDII` (PIN 1234), WiFi AP `ESP32-Access-Point` / `123456789`, serves `/speed` (kph). Needs esp32 core 2.0.x, ESPAsyncWebServer, ELMduino | `ST77916_LVGL_DEMO/esp23_elm327_to_wifi/` |
| `elmulator/` | classic ESP32 | ELMulator library + examples: fake ELM327 over BT/WiFi for testing without a car | `ST77916_LVGL_DEMO/ELMulator-master/` |
| `display_gif_embedded/` | JC3636W518 | GIF compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.4 MB firmware, uses `default_16MB.csv` with 6.25 MB app slots). WiFi code commented out. Stops in `setup()` if no SD card | `ST77916_LVGL_DEMO/` (top level) |
| `elm327_wifi_bridge/` | **classic ESP32** (not S3) | BT Classic → ELM327 named `OBDII` (PIN 1234), WiFi AP `ESP32-Access-Point` / `123456789`, serves `/speed` (kph) | `ST77916_LVGL_DEMO/esp23_elm327_to_wifi/` |
| `elmulator/` | classic ESP32 | Fake ELM327 for testing without a car. Library in `lib/ELMulator`, envs `simple`, `custom`, `wifi` (BT name `OBDII`) | `ST77916_LVGL_DEMO/ELMulator-master/` |
| `assets/gifs/` | — | GIF files (`dump/` = unused collection) | `ST77916_LVGL_DEMO/gifs/` |
| `_archive/lvgl9_broken/` | — | Unfinished LVGL 9 port, does not compile | `ST77916_LVGL_DEMO/9.1/` |
| `_archive/lvgl9_broken/` | — | Unfinished LVGL 9 port, does not compile, not converted | `ST77916_LVGL_DEMO/9.1/` |
## Notes
- Each display sketch carries its own `pincfg.h`, `scr_st77916.h` and `lv_conf.h`. The Arduino LVGL library does **not** read the sketch's `lv_conf.h` — the one used is `Documents\Arduino\libraries\lv_conf.h`. Copy the sketch's version there before building:
## Build / upload
Open a project folder in VS Code with the PlatformIO extension, or from a terminal:
```
pio run -d display_speed_wifi # build
pio run -d display_speed_wifi -t upload # flash (COM21)
pio device monitor -d display_speed_wifi # serial monitor
pio run -d elmulator -e wifi -t upload --upload-port COMxx
```
(`pio` = `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` if not on PATH.)
## Configuration
- **JC3636W518 projects:** pioarduino platform `51.03.05` (= Arduino core 3.0.5), board `esp32-s3-devkitc-1` with 16 MB QIO flash, OPI PSRAM, `huge_app.csv` (3 MB app), USB-CDC on boot, COM21.
Libraries come from the vendor folder via `lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library`.
Each project uses its **own `include/lv_conf.h`** (`-DLV_CONF_INCLUDE_SIMPLE -Iinclude`):
- `display_speed_wifi`: LVGL heap 4 MB in PSRAM (`ps_malloc`)
- `display_gif_sd`: LVGL heap 7 MB in PSRAM
- `montserrat_100.c` (100 px digits font) is present but not referenced by any sketch yet.
- `display_gif_embedded` will not fit the 3 MB "Huge APP" partition with `loligod.c`; needs a custom partition table or the GIF on SD.
- SD-based GIFs use LVGL drive letter `A:`, which needs an LVGL filesystem driver configured in `lv_conf.h`.
- **Classic ESP32 projects:** official `espressif32@6.10.0` (Arduino core 2.0.17), board `esp32dev`, `huge_app.csv`. Set `upload_port` to the classic ESP32's COM port.
Bridge libraries: `powerbroker2/ELMduino@3.4.1`, `esphome/ESPAsyncWebServer-esphome@3.4.0`.
## Notes
- `src/montserrat_100.c` (100 px digits font) is present but not referenced by any sketch yet.
- SD-based GIFs use LVGL drive `A:` = `LV_USE_FS_FATFS` (reads through ESP-IDF FatFS on the SD_MMC mount). Not yet verified on hardware.
@@ -0,0 +1,29 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
; GIF compiled into firmware -> 3.4 MB, too big for huge_app (3 MB); 6.25 MB app slots
board_build.partitions = default_16MB.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,192 +1,194 @@
#include "scr_st77916.h"
#include "SD_MMC.h"
#include "loligod.c"
// https://randomnerdtutorials.com/esp32-client-server-wi-fi/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
LV_IMG_DECLARE(loligod);
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
// lv_fs_file_t file;
// if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
// lv_fs_close(&file); // File exists, proceed with loading
// lv_gif_set_src(img, "A:wallpaper.gif");
// Serial.println("GIF Loaded Successfully");
// } else {
// Serial.println("Failed to Load GIF: File not found or inaccessible");
// }
lv_gif_set_src(img, &loligod);
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
lv_obj_t* label1;
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &Montserrat); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "0");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_transform_zoom(label1, 256 * 2, 0); // Zoom: 256 = 1x scale (default)
}
void connectToWifi() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Wifi not connected");
return;
}
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout <= 20) {
timeout++;
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
display_animation();
//connectToWifi();
//display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
// if (WiFi.status() == WL_CONNECTED) {
// String speed = httpGETRequest("http://192.168.4.1/speed");
// Serial.print("Speed: ");
// Serial.println(speed);
// lv_label_set_text(label1, speed.c_str());
// } else {
// Serial.println("WiFi Disconnected");
// connectToWifi();
// }
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "--";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
#include <Arduino.h>
#include "scr_st77916.h"
#include "SD_MMC.h"
// https://randomnerdtutorials.com/esp32-client-server-wi-fi/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
String httpGETRequest(const char* serverName);
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
LV_IMG_DECLARE(loligod);
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
// lv_fs_file_t file;
// if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
// lv_fs_close(&file); // File exists, proceed with loading
// lv_gif_set_src(img, "A:wallpaper.gif");
// Serial.println("GIF Loaded Successfully");
// } else {
// Serial.println("Failed to Load GIF: File not found or inaccessible");
// }
lv_gif_set_src(img, &loligod);
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
lv_obj_t* label1;
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &Montserrat); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "0");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_transform_zoom(label1, 256 * 2, 0); // Zoom: 256 = 1x scale (default)
}
void connectToWifi() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Wifi not connected");
return;
}
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout <= 20) {
timeout++;
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
display_animation();
//connectToWifi();
//display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
// if (WiFi.status() == WL_CONNECTED) {
// String speed = httpGETRequest("http://192.168.4.1/speed");
// Serial.print("Speed: ");
// Serial.println(speed);
// lv_label_set_text(label1, speed.c_str());
// } else {
// Serial.println("WiFi Disconnected");
// connectToWifi();
// }
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "--";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
@@ -0,0 +1,28 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
board_build.partitions = huge_app.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,116 +1,117 @@
#include "scr_st77916.h"
#include "SD_MMC.h"
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
}
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
lv_obj_t* label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &LV_FONT_MONTSERRAT_46); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "Test text");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
display_animation();
display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
}
#include <Arduino.h>
#include "scr_st77916.h"
#include "SD_MMC.h"
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
}
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
lv_obj_t* label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &LV_FONT_MONTSERRAT_46); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "Test text");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
display_animation();
display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
}
@@ -0,0 +1,28 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
board_build.partitions = huge_app.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,181 +1,184 @@
#include "scr_st77916.h"
#include "SD_MMC.h"
// https://randomnerdtutorials.com/esp32-client-server-wi-fi/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
}
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
lv_obj_t* label1;
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &Montserrat); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "0");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_transform_zoom(label1, 256 * 2, 0); // Zoom: 256 = 1x scale (default)
}
void connectToWifi() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
connectToWifi();
display_animation();
display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
if (WiFi.status() == WL_CONNECTED) {
String speed = httpGETRequest("http://192.168.4.1/speed");
Serial.print("Speed: ");
Serial.println(speed);
lv_label_set_text(label1, speed.c_str());
} else {
Serial.println("WiFi Disconnected");
connectToWifi();
}
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "--";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
#include <Arduino.h>
#include "scr_st77916.h"
#include "SD_MMC.h"
// https://randomnerdtutorials.com/esp32-client-server-wi-fi/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
String httpGETRequest(const char* serverName);
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
}
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
lv_obj_t* label1;
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &Montserrat); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "0");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_transform_zoom(label1, 256 * 2, 0); // Zoom: 256 = 1x scale (default)
}
void connectToWifi() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
connectToWifi();
display_animation();
display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
if (WiFi.status() == WL_CONNECTED) {
String speed = httpGETRequest("http://192.168.4.1/speed");
Serial.print("Speed: ");
Serial.println(speed);
lv_label_set_text(label1, speed.c_str());
} else {
Serial.println("WiFi Disconnected");
connectToWifi();
}
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "--";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
@@ -0,0 +1,18 @@
; ELM327 -> WiFi bridge — runs on a CLASSIC ESP32 (needs Bluetooth Classic, which the ESP32-S3 lacks)
; Original note: "needs esp32 2.0.* in boards manager" -> official platform 6.10.0 = Arduino core 2.0.17
[env:esp32dev]
platform = espressif32@6.10.0
board = esp32dev
framework = arduino
; BluetoothSerial + WiFi + web server do not fit the default 1.2 MB app partition
board_build.partitions = huge_app.csv
lib_deps =
powerbroker2/ELMduino@3.4.1
esphome/ESPAsyncWebServer-esphome@3.4.0
; upload_port = COMxx ; set to the classic ESP32's port (COM21 is the JC3636W518)
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,95 +1,96 @@
// Import required libraries
#include "WiFi.h"
#include <ESPAsyncWebServer.h>
// needs esp32 2.0.* in boards manaager // https://forum.arduino.cc/t/libary-problem-with-esp32-web-server/1282583/6
// Set your access point network credentials
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
#include "BluetoothSerial.h"
#include "ELMduino.h"
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
BluetoothSerial SerialBT;
#define ELM_PORT SerialBT
#define DEBUG_PORT Serial
ELM327 myELM327;
uint32_t kph = 0;
String readSpeed() {
if (ELM_PORT.isClosed()) {
return "0";
}
float tempkph = myELM327.kph();
if (myELM327.nb_rx_state == ELM_SUCCESS) {
kph = (uint32_t)tempkph;
Serial.print("kph: ");
Serial.println(kph);
} else if (myELM327.nb_rx_state != ELM_GETTING_MSG)
myELM327.printError();
return String(kph);
// return "123";
}
void connectElm() {
if (!ELM_PORT.connect("OBDII")) {
DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1");
// while (1)
// ;
return;
}
if (!myELM327.begin(ELM_PORT, true, 2000)) {
Serial.println("Couldn't connect to OBD scanner - Phase 2");
// while (1)
// ;
return;
}
Serial.println("Connected to ELM327");
}
void setup() {
DEBUG_PORT.begin(115200);
SerialBT.setPin("1234");
ELM_PORT.begin("ArduHUD", true);
Serial.println("Starting ELM327 to wifi bridge");
//connectElm();
// Setting the ESP as an access point
Serial.print("Setting AP (Access Point)…");
// Remove the password parameter, if you want the AP (Access Point) to be open
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
server.on("/speed", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/plain", readSpeed().c_str());
});
// Start server
server.begin();
}
void loop() {
if (ELM_PORT.isClosed()) {
Serial.println("Trying to reconnect to ELM327");
connectElm();
}
}
#include <Arduino.h>
// Import required libraries
#include "WiFi.h"
#include <ESPAsyncWebServer.h>
// needs esp32 2.0.* in boards manaager // https://forum.arduino.cc/t/libary-problem-with-esp32-web-server/1282583/6
// Set your access point network credentials
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
#include "BluetoothSerial.h"
#include "ELMduino.h"
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
BluetoothSerial SerialBT;
#define ELM_PORT SerialBT
#define DEBUG_PORT Serial
ELM327 myELM327;
uint32_t kph = 0;
String readSpeed() {
if (ELM_PORT.isClosed()) {
return "0";
}
float tempkph = myELM327.kph();
if (myELM327.nb_rx_state == ELM_SUCCESS) {
kph = (uint32_t)tempkph;
Serial.print("kph: ");
Serial.println(kph);
} else if (myELM327.nb_rx_state != ELM_GETTING_MSG)
myELM327.printError();
return String(kph);
// return "123";
}
void connectElm() {
if (!ELM_PORT.connect("OBDII")) {
DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1");
// while (1)
// ;
return;
}
if (!myELM327.begin(ELM_PORT, true, 2000)) {
Serial.println("Couldn't connect to OBD scanner - Phase 2");
// while (1)
// ;
return;
}
Serial.println("Connected to ELM327");
}
void setup() {
DEBUG_PORT.begin(115200);
SerialBT.setPin("1234");
ELM_PORT.begin("ArduHUD", true);
Serial.println("Starting ELM327 to wifi bridge");
//connectElm();
// Setting the ESP as an access point
Serial.print("Setting AP (Access Point)…");
// Remove the password parameter, if you want the AP (Access Point) to be open
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
server.on("/speed", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/plain", readSpeed().c_str());
});
// Start server
server.begin();
}
void loop() {
if (ELM_PORT.isClosed()) {
Serial.println("Trying to reconnect to ELM327");
connectElm();
}
}
-1
View File
@@ -4,5 +4,4 @@
.vscode/launch.json
.vscode/ipch
.vscode/extensions.json
platformio.ini
@@ -1,162 +0,0 @@
#include "ATCommands.h"
#include "definitions.h"
#if USE_WIFI
ATCommands::ATCommands(OBDWiFiComm *connection) {
this->connection = connection;
}
#else
ATCommands::ATCommands(OBDSerialComm *connection) {
this->connection = connection;
}
#endif
ATCommands::~ATCommands() {
operator delete(this->connection);
}
bool ATCommands::process(const String& command) {
bool processed = false;
if (isATCommand(command)) {
processed = true;
processCommand(command);
DEBUG("RX AT: " + command);
}
return processed;
}
void ATCommands::processCommand(const String& command) {
// if space is enabled (ex: AT H[charPosition])
uint8_t offset = 2;
if (command.charAt(2) == 0x20) {
offset = 3;
}
// refer to ELM327 specs
String specificCommand = command.substring(offset, command.length());
if (specificCommand.startsWith("D",offset)) {
ATCommands::ATD();
} else if (specificCommand.startsWith("Z")) {
ATCommands::ATZ();
} else if (specificCommand.startsWith("I")) {
ATCommands::ATI();
} else if (specificCommand.startsWith("E")) {
ATCommands::ATEx(specificCommand);
} else if (specificCommand.startsWith("L")) {
ATCommands::ATLx(specificCommand);
} else if (specificCommand.startsWith("M")) {
ATCommands::ATMx(specificCommand);
} else if (specificCommand.startsWith("S")) {
if (specificCommand.startsWith("SP")) {
ATCommands::ATSPx(specificCommand);
} else {
ATCommands::ATSx(specificCommand);
}
} else if (specificCommand.startsWith("H")) {
ATCommands::ATHx(specificCommand);
} else if (specificCommand.startsWith("AT")) {
ATCommands::ATATx(specificCommand);
} else if (specificCommand.startsWith("DPN")) {
ATCommands::ATDPN();
} else if (specificCommand.startsWith("DESC") || specificCommand.startsWith("@1")) {
ATCommands::ATDESC();
} else if (specificCommand.startsWith("PC")) {
ATCommands::ATPC();
} else if (specificCommand.startsWith("RV")){
ATCommands::ATRV();
} else {
// lets assume we process any at command
connection->writeEndOK();
}
}
// set all to defaults
void ATCommands::ATD() {
connection->setToDefaults();
connection->writeEndOK();
}
// reset all
void ATCommands::ATZ() {
connection->setToDefaults();
connection->writeTo(ID);
connection->writeEndOK();
}
// Print the version ID
void ATCommands::ATI() {
connection->writeTo(ID);
connection->writeEndOK();
}
// send description
void ATCommands::ATDESC() {
connection->writeTo(DESC);
connection->writeEndOK();
}
// set echoEnable 0=off 1=on
void ATCommands::ATEx(String& cmd) {
connection->setEcho(cmd.equals("E0") ? false : true);
connection->writeEndOK();
}
// set memory off=0 on=1
void ATCommands::ATMx(String& cmd) {
connection->setMemory(cmd.equals("M0") ? false : true);
connection->writeEndOK();
}
// line feeds off=0 on=1
void ATCommands::ATLx(String& cmd) {
connection->setLineFeeds(cmd.equals("L0") ? false : true);
connection->writeEndOK();
}
// ATSx printing spaces off=0 on=1
void ATCommands::ATSx(String& cmd) {
connection->setWhiteSpaces(cmd.equals("S0") ? false : true);
connection->writeEndOK();
}
// Headers off=0 on=1
void ATCommands::ATHx(String& cmd) {
connection->setHeaders(cmd.equals("H0") ? false : true);
connection->writeEndOK();
}
// ATSPx Define protocol 0=auto
void ATCommands::ATSPx(String& cmd) {
connection->writeEndOK();
}
// set protocol
void ATCommands::ATDPN() {
connection->writeTo(PROTOCOL);
connection->writeEndOK();
}
// AT AT2 adaptative time control
void ATCommands::ATATx(String& cmd) {
connection->writeEndOK();
}
// Terminates current diagnostic session. Protocol close
void ATCommands::ATPC() {
connection->writeEndOK();
}
void ATCommands::ATRV() {
connection->writeTo("AT RV\r13.5V");
connection->writeEnd();
}
// return true ir connectionand is AT
bool ATCommands::isATCommand(const String& command) {
toUpperCase(command.charAt(0));
return command.startsWith("AT") ? true : false;
}
@@ -1,65 +0,0 @@
#ifndef ELMulator_ATCommands_h
#define ELMulator_ATCommands_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
class ATCommands
{
public:
#if USE_WIFI
ATCommands(OBDWiFiComm *connection);
#else
ATCommands(OBDSerialComm *connection);
#endif
~ATCommands();
bool process(const String &string);
private:
// Variables
#if USE_WIFI
OBDWiFiComm *connection;
#else
OBDSerialComm *connection;
#endif
void ATD();
void ATZ();
void ATI();
void ATEx(String &x);
void ATMx(String &x);
void ATLx(String &x);
void ATSx(String &x);
void ATHx(String &x);
void ATSPx(String &x);
void ATATx(String &cmd);
void ATPC();
void ATDPN();
void ATDESC();
void ATRV();
void processCommand(const String &command);
bool isATCommand(const String &command);
};
#endif
@@ -1,218 +0,0 @@
#include "ELMulator.h"
#if USE_WIFI
ELMulator::ELMulator()
{
_connection = new OBDWiFiComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#else
ELMulator::ELMulator()
{
_connection = new OBDSerialComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#endif
ELMulator::~ELMulator() {}
void ELMulator::init(const String &deviceName, bool registerPids)
{
_connection->init(deviceName);
if (registerPids)
{
registerAllMode01Pids();
}
}
void ELMulator::registerAllMode01Pids()
{
// Set up PIDs 0x00 to 0x65
// Will be provided a response with mock data value if no special handling is performed.
// Requests for PID > 0x65 are not currently supported - will return "NO DATA"
for (int i = 0; i < 0x66; i++)
{
_pidProcessor->registerMode01Pid(i);
}
}
bool ELMulator::readELMRequest()
{
do
{
elmRequest.clear(); // clear buffer from previous requests
_connection->readData(elmRequest);
elmRequest.toUpperCase();
// TODO ignore spaces, and all control chars (tab, etc)
// TODO accept single carriage return as repeat last command at or pid
// ignore null i.e 00
} while (processRequest(elmRequest)); // processRequest handles all non PID requests (AT commands, errors etc)
return true; // We have received a valid PID request we need to respond to
}
void ELMulator::sendELMResponse()
{
// Respond to any mode 01 request with a mock sensor value
if (isMode01(elmRequest))
{
uint8_t pidCode = getPidCode(elmRequest);
writePidResponse(elmRequest, responseBytes[pidCode], getMockSensorValue());
return;
}
// Not a mode 01 PID request. Report it as not supported (ie, "NO DATA");
else
{
writePidNotSupported();
}
}
void ELMulator::begin()
{
while (readELMRequest())
{
sendELMResponse();
}
}
uint8_t ELMulator::getPidCode(const String &request)
{
return _pidProcessor->getPidCodeFromRequest(request);
}
bool ELMulator::registerMode01Pid(uint32_t pid)
{
return _pidProcessor->registerMode01Pid(pid);
}
bool ELMulator::registerMode01MILResponse(const String &response)
{
return _pidProcessor->registerMode01MILResponse(response);
}
bool ELMulator::registerMode03Response(const String &response)
{
return _pidProcessor->registerMode03Response(response);
}
void ELMulator::writePidNotSupported()
{
_connection->writeEndNoData();
}
void ELMulator::writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value)
{
_pidProcessor->writePidResponse(requestPid, numberOfBytes, value);
}
void ELMulator::writeResponse(const String &response)
{
_connection->writeTo(response.c_str());
_connection->writeEnd();
}
bool ELMulator::processRequest(String &command)
{
// TODO: check for no 0X0D char in command, return ? and >
// TODO: receive only <cr>, repeat last command
// carriage return, means repeat last command
if (command == "\n")
{
command = _lastCommand;
}
// empty command, do not send to user
if (command.length() == 0)
{
return true;
}
// Check for AT command
if (_atProcessor->process(command))
{
_lastCommand = command;
return true;
}
// Check for a valid hex string, return error if false
if (!isValidHex(command.c_str()))
{
_connection->writeEndUnknown();
DEBUG("Invalid HEX command: " + command);
return true;
}
// Check for a valid PID request
if (_pidProcessor->process(command))
{
_lastCommand = command;
return true;
}
return false;
}
bool ELMulator::isValidHex(const char *pid)
{
return (pid[strspn(pid, "0123456789abcdefABCDEF")] == 0);
}
bool ELMulator::isMode01(const String &command)
{
return _pidProcessor->isMode01(command);
}
bool ELMulator::isMode22(const String &command)
{
return _pidProcessor->isMode22(command);
}
bool ELMulator::isMode03(const String &command)
{
return _pidProcessor->isMode03(command);
}
bool ELMulator::isMode01MIL(const String &command)
{
return _pidProcessor->isMode01MIL(command);
}
/**
* Fake some data
* start counter = 0
* every call to getFakeSensorData, will increment the counter to a max of 255 (0xff)
* When 0xff is reached the cycle is reverted
* and the counter will decrement by one until 0 is reached
*/
uint32_t ELMulator::getMockSensorValue()
{
if (isCycleUp)
{
cycle++;
if (cycle == 0xff)
{
isCycleUp = false;
}
}
else
{
cycle--;
if (cycle == 0)
{
isCycleUp = true;
}
}
return cycle;
}
@@ -1,138 +0,0 @@
#ifndef ELMulator_ELMulator_h
#define ELMulator_ELMulator_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
#include "ATCommands.h"
#include "PidProcessor.h"
#include "definitions.h"
class ELMulator
{
public:
/**
* When creating a new instance of ELMulator with an attached
* BT module, we need to set the Bluetooth serial parameters
*
* @param baudRate - rate for bluetooth serial port (normally 9600)
* @param rxPin - the Arduino RX pin for the Bluetooth module (serial port)
* @param txPin - the Arduino TX pin for the Bluetooth module (serial port)
* @return
*/
ELMulator(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
ELMulator();
~ELMulator();
void init(const String &deviceName, const bool registerPids = false);
/**
* Waits and Reads a request from OBD client software (example: torque)
*
* It will only return if the OBD client software
* request's one of the previous registred PID (see: registerMode01Pid())
*
* Any other request will be automaticly handle or it will be rejected.
*
* ELMulator lib will automaticly handle:
* - any AT command
* - PID's support request like 0100, 0120, 0140, 0160, ...
*
* @return the request PID (Vehicle speed ex: 010D )
*/
bool readELMRequest();
void sendELMResponse();
void begin();
/**
* Registry the PID's (sensors) your arduino will support.
* Currently ELMulator lib only supports MODE 01 PID's
*
* In OBDII MODE 01 PID's, are request
* to Show current data for a given sensor identified by a PID
*
* List of MODE 01 PID's: https://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01
*
* Example, for Vehicle speed, where OBDII PID code is 0D:
*
* registerMode01Pid(0x0D)
*
* @param pidHexId
* @return returns true if the entered ID is in the valide range [0x00, 0xFF]
*/
bool registerMode01Pid(uint32_t pidHexId);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
uint8_t getPidCodeOnly(uint16_t hexCommand);
/**
* Used to informe the OBD client that the PID,
* it has requested is not supported
*
* In normal conditions, you will only receive,
* request for the PID you have registred
* All other request is automaticly handle by GTTuboEcu lib
*/
void writePidNotSupported();
/**
* Respond to a given PID request from OBDII client (ex: torque)
*
* @param requestPid - the received pid
* @param numberOfBytes - number of bytes this PID response VALUE is supposed to have
* @param value - sensor value for this PID,
* lib will convert it to HEX
* with the appropriated number of HEX chars (numberOfChars)
*/
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
// Write the response back to the requestor without PID formatting, etc
// Just pass the response string on. Useful for testing with a specific response
// that has been pre-configured.
void writeResponse(const String &response);
bool isMode01(const String &command);
bool isMode03(const String &command);
bool isMode01MIL(const String &command);
bool isMode22(const String &command);
uint8_t getPidCode(const String &request);
void registerAllMode01Pids();
uint32_t getMockSensorValue();
String elmRequest;
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
ATCommands *_atProcessor;
PidProcessor *_pidProcessor;
String _lastCommand;
bool isCycleUp = true;
uint32_t cycle = 0;
bool processRequest(String &command);
bool isValidHex(const char *pid);
};
#endif
@@ -1,145 +0,0 @@
#include "OBDSerialComm.h"
#include "definitions.h"
OBDSerialComm::OBDSerialComm() {
}
OBDSerialComm::~OBDSerialComm() {
operator delete(serial);
}
void OBDSerialComm::init(const String& deviceName) {
Serial.println("Starting BT . . .");
serial = new BluetoothSerial();
//delay(2000);
serial->begin(deviceName, false);
setToDefaults();
}
void OBDSerialComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
serial->flush();
};
void OBDSerialComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDSerialComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDSerialComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDSerialComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDSerialComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDSerialComm::writeTo(char const *response) {
serial->print(response);
}
void OBDSerialComm::writeTo(uint8_t cChar) {
serial->print(cChar);
}
void OBDSerialComm::writeEndPidTo(char const *response) {
if (whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDSerialComm::readData(String& rxData) {
serial->flush(); // temp remove this
rxData = serial->readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable()) {
writeTo(rxData.c_str());
}
//return rxData;
}
void OBDSerialComm::setBaudRate(uint32_t rate) {
this->baudRate = rate;
}
long OBDSerialComm::getBaudRate() {
return this->baudRate;
}
bool OBDSerialComm::isEchoEnable() {
return this->echoEnable;
}
void OBDSerialComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDSerialComm::setStatus(STATUS status) {
this->status = status;
}
void OBDSerialComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDSerialComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDSerialComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDSerialComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDSerialComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,90 +0,0 @@
#ifndef ELMulator_OBDSerialComm_h
#define ELMulator_OBDSerialComm_h
#include <Arduino.h>
#include "definitions.h"
#include <BluetoothSerial.h>
class OBDSerialComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
// // Bluetooth is not built in, we are using a BT module via GPIO
// OBDSerialComm(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
OBDSerialComm();
~OBDSerialComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
uint32_t baudRate; // Serial Baud Rate
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void setBaudRate(uint32_t rate);
long getBaudRate();
void addSpacesToResponse(const char *response, char string[]);
#ifndef BLUETOOTH_BUILTIN
HardwareSerial *serial; // lib to communicate with bluetooth
#else
BluetoothSerial *serial;
#endif
};
#endif
@@ -1,151 +0,0 @@
#include "OBDWiFiComm.h"
#include "definitions.h"
WiFiServer server(35000);
IPAddress localIP = IPAddress(192, 168, 0, 10);
IPAddress gateway = IPAddress(192, 168, 0, 10);
IPAddress subnet = IPAddress(255,255,255,0);
OBDWiFiComm::OBDWiFiComm() {
}
OBDWiFiComm::~OBDWiFiComm() {
}
void OBDWiFiComm::init(const String& deviceName) {
Serial.println("Starting AP . . .");
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(deviceName);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
Serial.print("AP SSID: ");
Serial.println(deviceName);
server.begin();
setToDefaults();
}
void OBDWiFiComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
};
void OBDWiFiComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDWiFiComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDWiFiComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDWiFiComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDWiFiComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDWiFiComm::writeTo(char const *response) {
client.print(response);
}
void OBDWiFiComm::writeTo(uint8_t cChar) {
client.print(cChar);
}
void OBDWiFiComm::writeEndPidTo(char const *response) {
if (this->whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDWiFiComm::readData(String& rxData) {
if (!client.connected()) {
client = server.available();
}
if(client)
{
rxData
= client.readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable())
{
writeTo(rxData.c_str());
}
}
}
bool OBDWiFiComm::isEchoEnable() {
return this->echoEnable;
}
void OBDWiFiComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDWiFiComm::setStatus(STATUS status) {
this->status = status;
}
void OBDWiFiComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDWiFiComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDWiFiComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDWiFiComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDWiFiComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,78 +0,0 @@
#ifndef ELMulator_OBDWiFiComm_h
#define ELMulator_OBDWiFiComm_h
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include "definitions.h"
class OBDWiFiComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
OBDWiFiComm();
~OBDWiFiComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void addSpacesToResponse(const char *response, char string[]);
WiFiClient client;
};
#endif
@@ -1,195 +0,0 @@
#include "PidProcessor.h"
#if USE_WIFI
PidProcessor::PidProcessor(OBDWiFiComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#else
PidProcessor::PidProcessor(OBDSerialComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#endif
bool PidProcessor::process(String& command) {
bool processed = false;
if (command.length() > 4)
{
command = command.substring(0, 4); // remove num_responses value if present; lib only handles single responses
}
if (!isMode01(command) & !isMode22(command))
{
_connection->writeEndNoData();
return false;
}
uint16_t hexCommand = strtoul(command.c_str(), NULL, HEX);
uint8_t pid = getPidCodeFromHex(hexCommand);
if (isSupportedPidRequest(pid)) { //reqeust to return a list of valid PIDs we can respond to
processed = true;
uint32_t supportedPids = getSupportedPids(pid);
writePidResponse(command, 4, supportedPids);
}
return processed;
}
void PidProcessor::writePidResponse(const String& requestPid, uint8_t numberOfBytes, uint32_t value) {
uint8_t nHexChars = PID_N_BYTES * N_CHARS_IN_BYTE + numberOfBytes * N_CHARS_IN_BYTE ;
char responseArray[nHexChars + 1]; // one more for termination char
getFormattedResponse(responseArray, nHexChars, requestPid, value);
_connection->writeEndPidTo(responseArray);
}
/**
* adds a supported pid, so it can answer to pid support request, ex 0100, 0120, ...
*/
bool PidProcessor::registerMode01Pid(uint32_t pid) {
// pid must be off type 01
if (pid > 0x00 && pid < 0x0200) {
// remove PidMode, only use pid code
pid = getPidCodeFromHex(pid);
setPidBit(pid);
char buffer[4];
sprintf(buffer, "%02X", pid);
DEBUG("Registered PID: " + String(buffer ));
return true;
}
return false;
}
bool PidProcessor::isMode01(const String& command)
{
return command.startsWith("01") ? true : false;
}
bool PidProcessor::isMode01MIL(const String& command)
{
return command.startsWith("0101") ? true : false;
}
bool PidProcessor::isMode03(const String& command)
{
return command.startsWith("03") ? true : false;
}
bool PidProcessor::isMode22(const String& command)
{
return command.startsWith("22") ? true : false;
}
/**
* return true if:
* - 1000
* - 1020
* - 1040
* - 1060
* ....
*/
bool PidProcessor::isSupportedPidRequest(uint8_t pid) {
return pid == getPidIntervalId(pid);
}
/**
* returns the interval RANGEID for the given pid (ex: 00, 20, 40, ...)
*/
uint8_t PidProcessor::getPidIntervalId(uint8_t pidcode) {
return getPidIntervalIndex(pidcode) * PID_INTERVAL_OFFSET;
}
/**
* returns the interval INDEX for the given pid (0,1,2,3,4,...)
*/
uint8_t PidProcessor::getPidIntervalIndex(uint8_t pidcode) {
return pidcode / PID_INTERVAL_OFFSET;
}
/**
* Receives a mode pid ex 010C ,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromHex(uint16_t hexCommand) {
if (hexCommand >= 0x0100) {
hexCommand -= 0x100;
}
return (uint8_t) hexCommand; // return only the lower 8 bits (trim off num_messages or other extra bytes if present)
}
/**
* Receives a complete PID request,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromRequest(const String& request) {
uint16_t hexCommand = strtoul(request.c_str(), NULL, HEX);
return getPidCodeFromHex(hexCommand);
}
void PidProcessor::setPidBit(uint8_t pid) {
uint8_t arrayIndex = getPidIntervalIndex(pid);
if(isSupportedPidRequest(pid)) {
arrayIndex -= 1;
}
uint8_t bitPosition = getPidBitPosition(pid);
bitWrite(pidMode01Supported[arrayIndex], bitPosition, 1);
// mark available intervals
for (int i = 0; i < arrayIndex; i++) {
bitWrite(pidMode01Supported[i], 0, 1);
}
}
uint8_t PidProcessor::getPidBitPosition(uint8_t pid) {
uint8_t intervalId = getPidIntervalId(pid) ;
if(isSupportedPidRequest(pid)) {
intervalId -= PID_INTERVAL_OFFSET;
}
uint16_t pos = pid - intervalId;
pos = PID_INTERVAL_OFFSET - pos;
return pos;
}
uint32_t PidProcessor:: getSupportedPids(uint8_t pid) {
uint8_t index = getPidIntervalIndex(pid);
return pidMode01Supported[index];
}
void PidProcessor::getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String& pid, uint32_t value) {
uint8_t nValueChars = totalNumberOfChars - PID_N_BYTES * N_CHARS_IN_BYTE;
char cValue[3] = {0};
itoa(nValueChars, cValue, DEC);
String mask = "%s%0";
mask.concat(cValue);
mask.concat("lX\0");
String pidResponse = convertToPidResponse(pid);
sprintf(response, mask.c_str(), pidResponse.c_str(), value);
DEBUG("RX PID: " + pid + " - TX: " + String(response));
}
String PidProcessor::convertToPidResponse(String pid) {
String x = "4";
x.concat(pid.substring(1, pid.length()));
return x;
}
void PidProcessor::resetPidMode01Array() {
for(uint8_t i = 0; i < N_MODE01_INTERVALS; i++ ) {
pidMode01Supported[i] = 0x0;
}
}
@@ -1,62 +0,0 @@
#ifndef ELMulator_PIDPROCESSOR_H
#define ELMulator_PIDPROCESSOR_H
#include "definitions.h"
#include <Arduino.h>
#include <WString.h>
#include <Print.h>
#include "OBDWiFiComm.h"
#include "OBDSerialComm.h"
class PidProcessor
{
public:
PidProcessor(OBDWiFiComm *connection);
PidProcessor(OBDSerialComm *connection);
bool process(String &string);
bool registerMode01Pid(uint32_t pid);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
uint8_t getPidCodeFromHex(uint16_t hexCommand);
uint8_t getPidCodeFromRequest(const String &command);
bool isMode01(const String &command);
bool isMode01MIL(const String &command);
bool isMode03(const String &command);
bool isMode22(const String &command);
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
uint32_t pidMode01Supported[N_MODE01_INTERVALS];
bool isSupportedPidRequest(uint8_t pid);
uint32_t getSupportedPids(uint8_t pidcode);
uint8_t getPidIntervalId(uint8_t pidcode);
uint8_t getPidIntervalIndex(uint8_t pidcode);
void setPidBit(uint8_t pid);
uint8_t getPidBitPosition(uint8_t pidcode);
String convertToPidResponse(String pid);
void getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String &pid, uint32_t value);
void resetPidMode01Array();
};
#endif // ELMulator_PIDPROCESSOR_H
@@ -1,294 +0,0 @@
#ifndef ELMulator_DEFINITIONS_H
#define ELMulator_DEFINITIONS_H
#include <stdint.h>
// true == We are running on hardware that has builtin Bluetooth
// false == We are using hardware that has BT module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
#define USE_WIFI false
#define DO_DEBUG true
#define DEBUG(x) do {if (DO_DEBUG) { Serial.println(x); } } while (0)
#define xtoc(x) ((x < 10) ? ('0' + x) : ('A' - 10 + x))
#define getNumOfHexChars(nBytes) (nBytes * 2)
#define PID_N_BYTES 2
#define N_CHARS_IN_BYTE 2
// Device ID
#define ID "ELM327 / ELMulator V1.3.0"
#define DESC "ELMulator OBD2 Arduino library, based on ELM327 protocol"
#define PROTOCOL "6" // canbus 500k 11 bit protocol id for elm.
// Char representing end of serial string
#define SERIAL_END_CHAR 0x0D
#define SERIAL_READ_TIMEOUT 20000L
#define WIFI_END_CHAR 0x0A
const uint8_t maxPid = 0xFF;
const uint8_t N_MODE01_INTERVALS = 7;
const uint8_t PID_INTERVAL_OFFSET = 0x20;
const uint8_t MAX_REQUEST_SIZE = 40;
//-------------------------------------------------------------------------------------//
// Protocol IDs
//-------------------------------------------------------------------------------------//
const char AUTOMATIC = '0';
const char SAE_J1850_PWM_41_KBAUD = '1';
const char SAE_J1850_PWM_10_KBAUD = '2';
const char ISO_9141_5_BAUD_INIT = '3';
const char ISO_14230_5_BAUD_INIT = '4';
const char ISO_14230_FAST_INIT = '5';
const char ISO_15765_11_BIT_500_KBAUD = '6';
const char ISO_15765_29_BIT_500_KBAUD = '7';
const char ISO_15765_11_BIT_250_KBAUD = '8';
const char ISO_15765_29_BIT_250_KBAUD = '9';
const char SAE_J1939_29_BIT_250_KBAUD = 'A';
const char USER_1_CAN = 'B';
const char USER_2_CAN = 'C';
//-------------------------------------------------------------------------------------//
// PIDs (https://en.wikipedia.org/wiki/OBD-II_PIDs)
//-------------------------------------------------------------------------------------//
const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded
const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded
const uint8_t FREEZE_DTC = 2; // 0x02 -
const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded
const uint8_t ENGINE_LOAD = 4; // 0x04 - %
const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - %
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - %
const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa
const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa
const uint8_t ENGINE_RPM = 12; // 0x0C - rpm
const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h
const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC
const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C
const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s
const uint8_t THROTTLE_POSITION = 17; // 0x11 - %
const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded
const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V %
const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V %
const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V %
const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V %
const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V %
const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V %
const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V %
const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V %
const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded
const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded
const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec
const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded
const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km
const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa
const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa
const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V
const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V
const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V
const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V
const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V
const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V
const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V
const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V
const uint8_t COMMANDED_EGR = 44; // 0x2C - %
const uint8_t EGR_ERROR = 45; // 0x2D - %
const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - %
const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - %
const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count
const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km
const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa
const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa
const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA
const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA
const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA
const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA
const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA
const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA
const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA
const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C
const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded
const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded
const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V
const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - %
const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio
const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - %
const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C
const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - %
const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - %
const uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - %
const uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - %
const uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - %
const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - %
const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min
const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min
const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa
const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s
const uint8_t FUEL_TYPE = 81; // 0x51 - ref table
const uint8_t ETHANOL_FUEL_PERCENT = 82; // 0x52 - %
const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa
const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - %
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - %
const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa
const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - %
const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - %
const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C
const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - °
const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h
const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded
const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded
const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - %
const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - %
const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm
const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - %
const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded
const uint8_t ODOMETER = 166; // 0xA6 - km * 10
const uint8_t SERVICE_01 = 1;
const uint8_t SERVICE_02 = 2;
const uint8_t SERVICE_03 = 3;
//-------------------------------------------------------------------------------------//
// AT commands (https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf)
//-------------------------------------------------------------------------------------//
const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General
const char * const DISP_DEVICE_ID = "AT @2"; // General
const char * const STORE_DEVICE_ID = "AT @3 %s"; // General
const char * const REPEAT_LAST_COMMAND = "AT \r"; // General
const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General
const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD
const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD
const char * const DUMP_BUFFER = "AT BD"; // OBD
const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD
const char * const TRY_BAUD_DIVISOR = "AT BRD %02d"; // General
const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %02d"; // General
const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN
const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN
const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN
const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %02d"; // CAN
const char * const SET_ID_FILTER = "AT CF %s"; // CAN
const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN
const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN
const char * const SET_ID_MASK = "AT CM %s"; // CAN
const char * const SET_CAN_PRIORITY = "AT CP %02d"; // CAN
const char * const SHOW_CAN_STATUS = "AT CS"; // CAN
const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN
const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN
const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %04d"; // Volts
const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts
const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General
const char * const DISP_DLC_OFF = "AT D0"; // CAN
const char * const DISP_DLC_ON = "AT D1"; // CAN
const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939
const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD
const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD
const char * const ECHO_OFF = "AT E0"; // General
const char * const ECHO_ON = "AT E1"; // General
const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN
const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN
const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %c"; // CAN
const char * const FORGE_EVENTS = "AT FE"; // General
const char * const PERFORM_FAST_INIT = "AT FI"; // ISO
const char * const HEADERS_OFF = "AT H0"; // OBD
const char * const HEADERS_ON = "AT H1"; // OBD
const char * const DISP_ID = "AT I"; // General
const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO
const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO
const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO
const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850
const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850
const char * const IFRS_OFF = "AT IFR0"; // J1850
const char * const IFRS_AUTO = "AT IFR1"; // J1850
const char * const IFRS_ON = "AT IFR2"; // J1850
const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other
const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %02d"; // ISO
const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850
const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850
const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850
const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850
const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850
const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850
const char * const DISP_KEY_WORDS = "AT KW"; // ISO
const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO
const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO
const char * const LINEFEEDS_OFF = "AT L0"; // General
const char * const LINEFEEDS_ON = "AT L1"; // General
const char * const LOW_POWER_MODE = "AT LP"; // General
const char * const MEMORY_OFF = "AT M0"; // General
const char * const MEMORY_ON = "AT M1"; // General
const char * const MONITOR_ALL = "AT MA"; // OBD
const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939
const char * const MONITOR_FOR_RECEIVER = "AT MR %02d"; // OBD
const char * const MONITOR_FOR_TRANSMITTER = "AT MT %02d"; // OBD
const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD
const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD
const char * const PROTOCOL_CLOSE = "AT PC"; // OBD
const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF";// PPs
const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs
const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF";// PPs
const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs
const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs
const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs
const char * const RESPONSES_OFF = "AT R0"; // OBD
const char * const RESPONSES_ON = "AT R1"; // OBD
const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %02d"; // OBD
const char * const READ_STORED_DATA = "AT RD"; // General
const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN
const char * const READ_VOLTAGE = "AT RV"; // Volts
const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD
const char * const PRINTING_SPACES_ON = "AT S1"; // OBD
const char * const STORE_DATA_BYTE = "AT SD "; // General
const char * const SET_HEADER = "AT SH %s"; // OBD
const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO
const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD
const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD
const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD
const char * const SET_REC_ADDRESS = "AT SR %02d"; // OBD
const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD
const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %02d"; // OBD
const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %02d"; // ISO
const char * const SET_TESTER_ADDRESS_TO = "AT TA %02d"; // OBD
const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD
const char * const TRY_PROT_H = "AT TP %c"; // OBD
const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN
const char * const VARIABLE_DLC_ON = "AT V1"; // CAN
const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO
const char * const WARM_START = "AT WS"; // General
const char * const RESET_ALL = "AT Z"; // General
const uint8_t responseBytes[0xA9] =
{
4, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2,
4, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2,
4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1,
4, 1, 1, 2, 5, 2, 5, 3, 3, 7, 5, 5, 5, 11, 9, 3, 10, 6, 5, 5, 5, 7, 7, 5, 9, 9, 7, 7, 9, 1, 1, 13,
4, 41, 41, 9, 1, 10, 5, 5, 13, 41, 41, 7, 17, 1, 1, 7, 3, 5, 2, 3, 12, 9, 9, 6, 4, 17, 4, 2, 9
};
#endif //ELMulator_DEFINITIONS_H
@@ -1,162 +0,0 @@
#include "ATCommands.h"
#include "definitions.h"
#if USE_WIFI
ATCommands::ATCommands(OBDWiFiComm *connection) {
this->connection = connection;
}
#else
ATCommands::ATCommands(OBDSerialComm *connection) {
this->connection = connection;
}
#endif
ATCommands::~ATCommands() {
operator delete(this->connection);
}
bool ATCommands::process(const String& command) {
bool processed = false;
if (isATCommand(command)) {
processed = true;
processCommand(command);
DEBUG("RX AT: " + command);
}
return processed;
}
void ATCommands::processCommand(const String& command) {
// if space is enabled (ex: AT H[charPosition])
uint8_t offset = 2;
if (command.charAt(2) == 0x20) {
offset = 3;
}
// refer to ELM327 specs
String specificCommand = command.substring(offset, command.length());
if (specificCommand.startsWith("D",offset)) {
ATCommands::ATD();
} else if (specificCommand.startsWith("Z")) {
ATCommands::ATZ();
} else if (specificCommand.startsWith("I")) {
ATCommands::ATI();
} else if (specificCommand.startsWith("E")) {
ATCommands::ATEx(specificCommand);
} else if (specificCommand.startsWith("L")) {
ATCommands::ATLx(specificCommand);
} else if (specificCommand.startsWith("M")) {
ATCommands::ATMx(specificCommand);
} else if (specificCommand.startsWith("S")) {
if (specificCommand.startsWith("SP")) {
ATCommands::ATSPx(specificCommand);
} else {
ATCommands::ATSx(specificCommand);
}
} else if (specificCommand.startsWith("H")) {
ATCommands::ATHx(specificCommand);
} else if (specificCommand.startsWith("AT")) {
ATCommands::ATATx(specificCommand);
} else if (specificCommand.startsWith("DPN")) {
ATCommands::ATDPN();
} else if (specificCommand.startsWith("DESC") || specificCommand.startsWith("@1")) {
ATCommands::ATDESC();
} else if (specificCommand.startsWith("PC")) {
ATCommands::ATPC();
} else if (specificCommand.startsWith("RV")){
ATCommands::ATRV();
} else {
// lets assume we process any at command
connection->writeEndOK();
}
}
// set all to defaults
void ATCommands::ATD() {
connection->setToDefaults();
connection->writeEndOK();
}
// reset all
void ATCommands::ATZ() {
connection->setToDefaults();
connection->writeTo(ID);
connection->writeEndOK();
}
// Print the version ID
void ATCommands::ATI() {
connection->writeTo(ID);
connection->writeEndOK();
}
// send description
void ATCommands::ATDESC() {
connection->writeTo(DESC);
connection->writeEndOK();
}
// set echoEnable 0=off 1=on
void ATCommands::ATEx(String& cmd) {
connection->setEcho(cmd.equals("E0") ? false : true);
connection->writeEndOK();
}
// set memory off=0 on=1
void ATCommands::ATMx(String& cmd) {
connection->setMemory(cmd.equals("M0") ? false : true);
connection->writeEndOK();
}
// line feeds off=0 on=1
void ATCommands::ATLx(String& cmd) {
connection->setLineFeeds(cmd.equals("L0") ? false : true);
connection->writeEndOK();
}
// ATSx printing spaces off=0 on=1
void ATCommands::ATSx(String& cmd) {
connection->setWhiteSpaces(cmd.equals("S0") ? false : true);
connection->writeEndOK();
}
// Headers off=0 on=1
void ATCommands::ATHx(String& cmd) {
connection->setHeaders(cmd.equals("H0") ? false : true);
connection->writeEndOK();
}
// ATSPx Define protocol 0=auto
void ATCommands::ATSPx(String& cmd) {
connection->writeEndOK();
}
// set protocol
void ATCommands::ATDPN() {
connection->writeTo(PROTOCOL);
connection->writeEndOK();
}
// AT AT2 adaptative time control
void ATCommands::ATATx(String& cmd) {
connection->writeEndOK();
}
// Terminates current diagnostic session. Protocol close
void ATCommands::ATPC() {
connection->writeEndOK();
}
void ATCommands::ATRV() {
connection->writeTo("AT RV\r13.5V");
connection->writeEnd();
}
// return true ir connectionand is AT
bool ATCommands::isATCommand(const String& command) {
toUpperCase(command.charAt(0));
return command.startsWith("AT") ? true : false;
}
@@ -1,65 +0,0 @@
#ifndef ELMulator_ATCommands_h
#define ELMulator_ATCommands_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
class ATCommands
{
public:
#if USE_WIFI
ATCommands(OBDWiFiComm *connection);
#else
ATCommands(OBDSerialComm *connection);
#endif
~ATCommands();
bool process(const String &string);
private:
// Variables
#if USE_WIFI
OBDWiFiComm *connection;
#else
OBDSerialComm *connection;
#endif
void ATD();
void ATZ();
void ATI();
void ATEx(String &x);
void ATMx(String &x);
void ATLx(String &x);
void ATSx(String &x);
void ATHx(String &x);
void ATSPx(String &x);
void ATATx(String &cmd);
void ATPC();
void ATDPN();
void ATDESC();
void ATRV();
void processCommand(const String &command);
bool isATCommand(const String &command);
};
#endif
@@ -1,218 +0,0 @@
#include "ELMulator.h"
#if USE_WIFI
ELMulator::ELMulator()
{
_connection = new OBDWiFiComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#else
ELMulator::ELMulator()
{
_connection = new OBDSerialComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#endif
ELMulator::~ELMulator() {}
void ELMulator::init(const String &deviceName, bool registerPids)
{
_connection->init(deviceName);
if (registerPids)
{
registerAllMode01Pids();
}
}
void ELMulator::registerAllMode01Pids()
{
// Set up PIDs 0x00 to 0x65
// Will be provided a response with mock data value if no special handling is performed.
// Requests for PID > 0x65 are not currently supported - will return "NO DATA"
for (int i = 0; i < 0x66; i++)
{
_pidProcessor->registerMode01Pid(i);
}
}
bool ELMulator::readELMRequest()
{
do
{
elmRequest.clear(); // clear buffer from previous requests
_connection->readData(elmRequest);
elmRequest.toUpperCase();
// TODO ignore spaces, and all control chars (tab, etc)
// TODO accept single carriage return as repeat last command at or pid
// ignore null i.e 00
} while (processRequest(elmRequest)); // processRequest handles all non PID requests (AT commands, errors etc)
return true; // We have received a valid PID request we need to respond to
}
void ELMulator::sendELMResponse()
{
// Respond to any mode 01 request with a mock sensor value
if (isMode01(elmRequest))
{
uint8_t pidCode = getPidCode(elmRequest);
writePidResponse(elmRequest, responseBytes[pidCode], getMockSensorValue());
return;
}
// Not a mode 01 PID request. Report it as not supported (ie, "NO DATA");
else
{
writePidNotSupported();
}
}
void ELMulator::begin()
{
while (readELMRequest())
{
sendELMResponse();
}
}
uint8_t ELMulator::getPidCode(const String &request)
{
return _pidProcessor->getPidCodeFromRequest(request);
}
bool ELMulator::registerMode01Pid(uint32_t pid)
{
return _pidProcessor->registerMode01Pid(pid);
}
bool ELMulator::registerMode01MILResponse(const String &response)
{
return _pidProcessor->registerMode01MILResponse(response);
}
bool ELMulator::registerMode03Response(const String &response)
{
return _pidProcessor->registerMode03Response(response);
}
void ELMulator::writePidNotSupported()
{
_connection->writeEndNoData();
}
void ELMulator::writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value)
{
_pidProcessor->writePidResponse(requestPid, numberOfBytes, value);
}
void ELMulator::writeResponse(const String &response)
{
_connection->writeTo(response.c_str());
_connection->writeEnd();
}
bool ELMulator::processRequest(String &command)
{
// TODO: check for no 0X0D char in command, return ? and >
// TODO: receive only <cr>, repeat last command
// carriage return, means repeat last command
if (command == "\n")
{
command = _lastCommand;
}
// empty command, do not send to user
if (command.length() == 0)
{
return true;
}
// Check for AT command
if (_atProcessor->process(command))
{
_lastCommand = command;
return true;
}
// Check for a valid hex string, return error if false
if (!isValidHex(command.c_str()))
{
_connection->writeEndUnknown();
DEBUG("Invalid HEX command: " + command);
return true;
}
// Check for a valid PID request
if (_pidProcessor->process(command))
{
_lastCommand = command;
return true;
}
return false;
}
bool ELMulator::isValidHex(const char *pid)
{
return (pid[strspn(pid, "0123456789abcdefABCDEF")] == 0);
}
bool ELMulator::isMode01(const String &command)
{
return _pidProcessor->isMode01(command);
}
bool ELMulator::isMode22(const String &command)
{
return _pidProcessor->isMode22(command);
}
bool ELMulator::isMode03(const String &command)
{
return _pidProcessor->isMode03(command);
}
bool ELMulator::isMode01MIL(const String &command)
{
return _pidProcessor->isMode01MIL(command);
}
/**
* Fake some data
* start counter = 0
* every call to getFakeSensorData, will increment the counter to a max of 255 (0xff)
* When 0xff is reached the cycle is reverted
* and the counter will decrement by one until 0 is reached
*/
uint32_t ELMulator::getMockSensorValue()
{
if (isCycleUp)
{
cycle++;
if (cycle == 0xff)
{
isCycleUp = false;
}
}
else
{
cycle--;
if (cycle == 0)
{
isCycleUp = true;
}
}
return cycle;
}
@@ -1,138 +0,0 @@
#ifndef ELMulator_ELMulator_h
#define ELMulator_ELMulator_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
#include "ATCommands.h"
#include "PidProcessor.h"
#include "definitions.h"
class ELMulator
{
public:
/**
* When creating a new instance of ELMulator with an attached
* BT module, we need to set the Bluetooth serial parameters
*
* @param baudRate - rate for bluetooth serial port (normally 9600)
* @param rxPin - the Arduino RX pin for the Bluetooth module (serial port)
* @param txPin - the Arduino TX pin for the Bluetooth module (serial port)
* @return
*/
ELMulator(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
ELMulator();
~ELMulator();
void init(const String &deviceName, const bool registerPids = false);
/**
* Waits and Reads a request from OBD client software (example: torque)
*
* It will only return if the OBD client software
* request's one of the previous registred PID (see: registerMode01Pid())
*
* Any other request will be automaticly handle or it will be rejected.
*
* ELMulator lib will automaticly handle:
* - any AT command
* - PID's support request like 0100, 0120, 0140, 0160, ...
*
* @return the request PID (Vehicle speed ex: 010D )
*/
bool readELMRequest();
void sendELMResponse();
void begin();
/**
* Registry the PID's (sensors) your arduino will support.
* Currently ELMulator lib only supports MODE 01 PID's
*
* In OBDII MODE 01 PID's, are request
* to Show current data for a given sensor identified by a PID
*
* List of MODE 01 PID's: https://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01
*
* Example, for Vehicle speed, where OBDII PID code is 0D:
*
* registerMode01Pid(0x0D)
*
* @param pidHexId
* @return returns true if the entered ID is in the valide range [0x00, 0xFF]
*/
bool registerMode01Pid(uint32_t pidHexId);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
uint8_t getPidCodeOnly(uint16_t hexCommand);
/**
* Used to informe the OBD client that the PID,
* it has requested is not supported
*
* In normal conditions, you will only receive,
* request for the PID you have registred
* All other request is automaticly handle by GTTuboEcu lib
*/
void writePidNotSupported();
/**
* Respond to a given PID request from OBDII client (ex: torque)
*
* @param requestPid - the received pid
* @param numberOfBytes - number of bytes this PID response VALUE is supposed to have
* @param value - sensor value for this PID,
* lib will convert it to HEX
* with the appropriated number of HEX chars (numberOfChars)
*/
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
// Write the response back to the requestor without PID formatting, etc
// Just pass the response string on. Useful for testing with a specific response
// that has been pre-configured.
void writeResponse(const String &response);
bool isMode01(const String &command);
bool isMode03(const String &command);
bool isMode01MIL(const String &command);
bool isMode22(const String &command);
uint8_t getPidCode(const String &request);
void registerAllMode01Pids();
uint32_t getMockSensorValue();
String elmRequest;
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
ATCommands *_atProcessor;
PidProcessor *_pidProcessor;
String _lastCommand;
bool isCycleUp = true;
uint32_t cycle = 0;
bool processRequest(String &command);
bool isValidHex(const char *pid);
};
#endif
@@ -1,145 +0,0 @@
#include "OBDSerialComm.h"
#include "definitions.h"
OBDSerialComm::OBDSerialComm() {
}
OBDSerialComm::~OBDSerialComm() {
operator delete(serial);
}
void OBDSerialComm::init(const String& deviceName) {
Serial.println("Starting BT . . .");
serial = new BluetoothSerial();
//delay(2000);
serial->begin(deviceName, false);
setToDefaults();
}
void OBDSerialComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
serial->flush();
};
void OBDSerialComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDSerialComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDSerialComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDSerialComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDSerialComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDSerialComm::writeTo(char const *response) {
serial->print(response);
}
void OBDSerialComm::writeTo(uint8_t cChar) {
serial->print(cChar);
}
void OBDSerialComm::writeEndPidTo(char const *response) {
if (whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDSerialComm::readData(String& rxData) {
serial->flush(); // temp remove this
rxData = serial->readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable()) {
writeTo(rxData.c_str());
}
//return rxData;
}
void OBDSerialComm::setBaudRate(uint32_t rate) {
this->baudRate = rate;
}
long OBDSerialComm::getBaudRate() {
return this->baudRate;
}
bool OBDSerialComm::isEchoEnable() {
return this->echoEnable;
}
void OBDSerialComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDSerialComm::setStatus(STATUS status) {
this->status = status;
}
void OBDSerialComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDSerialComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDSerialComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDSerialComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDSerialComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,90 +0,0 @@
#ifndef ELMulator_OBDSerialComm_h
#define ELMulator_OBDSerialComm_h
#include <Arduino.h>
#include "definitions.h"
#include <BluetoothSerial.h>
class OBDSerialComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
// // Bluetooth is not built in, we are using a BT module via GPIO
// OBDSerialComm(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
OBDSerialComm();
~OBDSerialComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
uint32_t baudRate; // Serial Baud Rate
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void setBaudRate(uint32_t rate);
long getBaudRate();
void addSpacesToResponse(const char *response, char string[]);
#ifndef BLUETOOTH_BUILTIN
HardwareSerial *serial; // lib to communicate with bluetooth
#else
BluetoothSerial *serial;
#endif
};
#endif
@@ -1,151 +0,0 @@
#include "OBDWiFiComm.h"
#include "definitions.h"
WiFiServer server(35000);
IPAddress localIP = IPAddress(192, 168, 0, 10);
IPAddress gateway = IPAddress(192, 168, 0, 10);
IPAddress subnet = IPAddress(255,255,255,0);
OBDWiFiComm::OBDWiFiComm() {
}
OBDWiFiComm::~OBDWiFiComm() {
}
void OBDWiFiComm::init(const String& deviceName) {
Serial.println("Starting AP . . .");
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(deviceName);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
Serial.print("AP SSID: ");
Serial.println(deviceName);
server.begin();
setToDefaults();
}
void OBDWiFiComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
};
void OBDWiFiComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDWiFiComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDWiFiComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDWiFiComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDWiFiComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDWiFiComm::writeTo(char const *response) {
client.print(response);
}
void OBDWiFiComm::writeTo(uint8_t cChar) {
client.print(cChar);
}
void OBDWiFiComm::writeEndPidTo(char const *response) {
if (this->whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDWiFiComm::readData(String& rxData) {
if (!client.connected()) {
client = server.available();
}
if(client)
{
rxData
= client.readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable())
{
writeTo(rxData.c_str());
}
}
}
bool OBDWiFiComm::isEchoEnable() {
return this->echoEnable;
}
void OBDWiFiComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDWiFiComm::setStatus(STATUS status) {
this->status = status;
}
void OBDWiFiComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDWiFiComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDWiFiComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDWiFiComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDWiFiComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,78 +0,0 @@
#ifndef ELMulator_OBDWiFiComm_h
#define ELMulator_OBDWiFiComm_h
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include "definitions.h"
class OBDWiFiComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
OBDWiFiComm();
~OBDWiFiComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void addSpacesToResponse(const char *response, char string[]);
WiFiClient client;
};
#endif
@@ -1,195 +0,0 @@
#include "PidProcessor.h"
#if USE_WIFI
PidProcessor::PidProcessor(OBDWiFiComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#else
PidProcessor::PidProcessor(OBDSerialComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#endif
bool PidProcessor::process(String& command) {
bool processed = false;
if (command.length() > 4)
{
command = command.substring(0, 4); // remove num_responses value if present; lib only handles single responses
}
if (!isMode01(command) & !isMode22(command))
{
_connection->writeEndNoData();
return false;
}
uint16_t hexCommand = strtoul(command.c_str(), NULL, HEX);
uint8_t pid = getPidCodeFromHex(hexCommand);
if (isSupportedPidRequest(pid)) { //reqeust to return a list of valid PIDs we can respond to
processed = true;
uint32_t supportedPids = getSupportedPids(pid);
writePidResponse(command, 4, supportedPids);
}
return processed;
}
void PidProcessor::writePidResponse(const String& requestPid, uint8_t numberOfBytes, uint32_t value) {
uint8_t nHexChars = PID_N_BYTES * N_CHARS_IN_BYTE + numberOfBytes * N_CHARS_IN_BYTE ;
char responseArray[nHexChars + 1]; // one more for termination char
getFormattedResponse(responseArray, nHexChars, requestPid, value);
_connection->writeEndPidTo(responseArray);
}
/**
* adds a supported pid, so it can answer to pid support request, ex 0100, 0120, ...
*/
bool PidProcessor::registerMode01Pid(uint32_t pid) {
// pid must be off type 01
if (pid > 0x00 && pid < 0x0200) {
// remove PidMode, only use pid code
pid = getPidCodeFromHex(pid);
setPidBit(pid);
char buffer[4];
sprintf(buffer, "%02X", pid);
DEBUG("Registered PID: " + String(buffer ));
return true;
}
return false;
}
bool PidProcessor::isMode01(const String& command)
{
return command.startsWith("01") ? true : false;
}
bool PidProcessor::isMode01MIL(const String& command)
{
return command.startsWith("0101") ? true : false;
}
bool PidProcessor::isMode03(const String& command)
{
return command.startsWith("03") ? true : false;
}
bool PidProcessor::isMode22(const String& command)
{
return command.startsWith("22") ? true : false;
}
/**
* return true if:
* - 1000
* - 1020
* - 1040
* - 1060
* ....
*/
bool PidProcessor::isSupportedPidRequest(uint8_t pid) {
return pid == getPidIntervalId(pid);
}
/**
* returns the interval RANGEID for the given pid (ex: 00, 20, 40, ...)
*/
uint8_t PidProcessor::getPidIntervalId(uint8_t pidcode) {
return getPidIntervalIndex(pidcode) * PID_INTERVAL_OFFSET;
}
/**
* returns the interval INDEX for the given pid (0,1,2,3,4,...)
*/
uint8_t PidProcessor::getPidIntervalIndex(uint8_t pidcode) {
return pidcode / PID_INTERVAL_OFFSET;
}
/**
* Receives a mode pid ex 010C ,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromHex(uint16_t hexCommand) {
if (hexCommand >= 0x0100) {
hexCommand -= 0x100;
}
return (uint8_t) hexCommand; // return only the lower 8 bits (trim off num_messages or other extra bytes if present)
}
/**
* Receives a complete PID request,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromRequest(const String& request) {
uint16_t hexCommand = strtoul(request.c_str(), NULL, HEX);
return getPidCodeFromHex(hexCommand);
}
void PidProcessor::setPidBit(uint8_t pid) {
uint8_t arrayIndex = getPidIntervalIndex(pid);
if(isSupportedPidRequest(pid)) {
arrayIndex -= 1;
}
uint8_t bitPosition = getPidBitPosition(pid);
bitWrite(pidMode01Supported[arrayIndex], bitPosition, 1);
// mark available intervals
for (int i = 0; i < arrayIndex; i++) {
bitWrite(pidMode01Supported[i], 0, 1);
}
}
uint8_t PidProcessor::getPidBitPosition(uint8_t pid) {
uint8_t intervalId = getPidIntervalId(pid) ;
if(isSupportedPidRequest(pid)) {
intervalId -= PID_INTERVAL_OFFSET;
}
uint16_t pos = pid - intervalId;
pos = PID_INTERVAL_OFFSET - pos;
return pos;
}
uint32_t PidProcessor:: getSupportedPids(uint8_t pid) {
uint8_t index = getPidIntervalIndex(pid);
return pidMode01Supported[index];
}
void PidProcessor::getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String& pid, uint32_t value) {
uint8_t nValueChars = totalNumberOfChars - PID_N_BYTES * N_CHARS_IN_BYTE;
char cValue[3] = {0};
itoa(nValueChars, cValue, DEC);
String mask = "%s%0";
mask.concat(cValue);
mask.concat("lX\0");
String pidResponse = convertToPidResponse(pid);
sprintf(response, mask.c_str(), pidResponse.c_str(), value);
DEBUG("RX PID: " + pid + " - TX: " + String(response));
}
String PidProcessor::convertToPidResponse(String pid) {
String x = "4";
x.concat(pid.substring(1, pid.length()));
return x;
}
void PidProcessor::resetPidMode01Array() {
for(uint8_t i = 0; i < N_MODE01_INTERVALS; i++ ) {
pidMode01Supported[i] = 0x0;
}
}
@@ -1,62 +0,0 @@
#ifndef ELMulator_PIDPROCESSOR_H
#define ELMulator_PIDPROCESSOR_H
#include "definitions.h"
#include <Arduino.h>
#include <WString.h>
#include <Print.h>
#include "OBDWiFiComm.h"
#include "OBDSerialComm.h"
class PidProcessor
{
public:
PidProcessor(OBDWiFiComm *connection);
PidProcessor(OBDSerialComm *connection);
bool process(String &string);
bool registerMode01Pid(uint32_t pid);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
uint8_t getPidCodeFromHex(uint16_t hexCommand);
uint8_t getPidCodeFromRequest(const String &command);
bool isMode01(const String &command);
bool isMode01MIL(const String &command);
bool isMode03(const String &command);
bool isMode22(const String &command);
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
uint32_t pidMode01Supported[N_MODE01_INTERVALS];
bool isSupportedPidRequest(uint8_t pid);
uint32_t getSupportedPids(uint8_t pidcode);
uint8_t getPidIntervalId(uint8_t pidcode);
uint8_t getPidIntervalIndex(uint8_t pidcode);
void setPidBit(uint8_t pid);
uint8_t getPidBitPosition(uint8_t pidcode);
String convertToPidResponse(String pid);
void getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String &pid, uint32_t value);
void resetPidMode01Array();
};
#endif // ELMulator_PIDPROCESSOR_H
@@ -1,294 +0,0 @@
#ifndef ELMulator_DEFINITIONS_H
#define ELMulator_DEFINITIONS_H
#include <stdint.h>
// true == We are running on hardware that has builtin Bluetooth
// false == We are using hardware that has BT module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
#define USE_WIFI false
#define DO_DEBUG true
#define DEBUG(x) do {if (DO_DEBUG) { Serial.println(x); } } while (0)
#define xtoc(x) ((x < 10) ? ('0' + x) : ('A' - 10 + x))
#define getNumOfHexChars(nBytes) (nBytes * 2)
#define PID_N_BYTES 2
#define N_CHARS_IN_BYTE 2
// Device ID
#define ID "ELM327 / ELMulator V1.3.0"
#define DESC "ELMulator OBD2 Arduino library, based on ELM327 protocol"
#define PROTOCOL "6" // canbus 500k 11 bit protocol id for elm.
// Char representing end of serial string
#define SERIAL_END_CHAR 0x0D
#define SERIAL_READ_TIMEOUT 20000L
#define WIFI_END_CHAR 0x0A
const uint8_t maxPid = 0xFF;
const uint8_t N_MODE01_INTERVALS = 7;
const uint8_t PID_INTERVAL_OFFSET = 0x20;
const uint8_t MAX_REQUEST_SIZE = 40;
//-------------------------------------------------------------------------------------//
// Protocol IDs
//-------------------------------------------------------------------------------------//
const char AUTOMATIC = '0';
const char SAE_J1850_PWM_41_KBAUD = '1';
const char SAE_J1850_PWM_10_KBAUD = '2';
const char ISO_9141_5_BAUD_INIT = '3';
const char ISO_14230_5_BAUD_INIT = '4';
const char ISO_14230_FAST_INIT = '5';
const char ISO_15765_11_BIT_500_KBAUD = '6';
const char ISO_15765_29_BIT_500_KBAUD = '7';
const char ISO_15765_11_BIT_250_KBAUD = '8';
const char ISO_15765_29_BIT_250_KBAUD = '9';
const char SAE_J1939_29_BIT_250_KBAUD = 'A';
const char USER_1_CAN = 'B';
const char USER_2_CAN = 'C';
//-------------------------------------------------------------------------------------//
// PIDs (https://en.wikipedia.org/wiki/OBD-II_PIDs)
//-------------------------------------------------------------------------------------//
const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded
const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded
const uint8_t FREEZE_DTC = 2; // 0x02 -
const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded
const uint8_t ENGINE_LOAD = 4; // 0x04 - %
const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - %
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - %
const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa
const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa
const uint8_t ENGINE_RPM = 12; // 0x0C - rpm
const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h
const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC
const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C
const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s
const uint8_t THROTTLE_POSITION = 17; // 0x11 - %
const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded
const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V %
const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V %
const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V %
const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V %
const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V %
const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V %
const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V %
const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V %
const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded
const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded
const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec
const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded
const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km
const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa
const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa
const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V
const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V
const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V
const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V
const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V
const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V
const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V
const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V
const uint8_t COMMANDED_EGR = 44; // 0x2C - %
const uint8_t EGR_ERROR = 45; // 0x2D - %
const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - %
const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - %
const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count
const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km
const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa
const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa
const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA
const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA
const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA
const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA
const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA
const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA
const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA
const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C
const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded
const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded
const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V
const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - %
const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio
const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - %
const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C
const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - %
const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - %
const uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - %
const uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - %
const uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - %
const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - %
const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min
const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min
const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa
const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s
const uint8_t FUEL_TYPE = 81; // 0x51 - ref table
const uint8_t ETHANOL_FUEL_PERCENT = 82; // 0x52 - %
const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa
const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - %
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - %
const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa
const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - %
const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - %
const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C
const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - °
const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h
const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded
const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded
const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - %
const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - %
const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm
const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - %
const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded
const uint8_t ODOMETER = 166; // 0xA6 - km * 10
const uint8_t SERVICE_01 = 1;
const uint8_t SERVICE_02 = 2;
const uint8_t SERVICE_03 = 3;
//-------------------------------------------------------------------------------------//
// AT commands (https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf)
//-------------------------------------------------------------------------------------//
const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General
const char * const DISP_DEVICE_ID = "AT @2"; // General
const char * const STORE_DEVICE_ID = "AT @3 %s"; // General
const char * const REPEAT_LAST_COMMAND = "AT \r"; // General
const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General
const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD
const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD
const char * const DUMP_BUFFER = "AT BD"; // OBD
const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD
const char * const TRY_BAUD_DIVISOR = "AT BRD %02d"; // General
const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %02d"; // General
const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN
const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN
const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN
const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %02d"; // CAN
const char * const SET_ID_FILTER = "AT CF %s"; // CAN
const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN
const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN
const char * const SET_ID_MASK = "AT CM %s"; // CAN
const char * const SET_CAN_PRIORITY = "AT CP %02d"; // CAN
const char * const SHOW_CAN_STATUS = "AT CS"; // CAN
const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN
const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN
const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %04d"; // Volts
const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts
const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General
const char * const DISP_DLC_OFF = "AT D0"; // CAN
const char * const DISP_DLC_ON = "AT D1"; // CAN
const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939
const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD
const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD
const char * const ECHO_OFF = "AT E0"; // General
const char * const ECHO_ON = "AT E1"; // General
const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN
const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN
const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %c"; // CAN
const char * const FORGE_EVENTS = "AT FE"; // General
const char * const PERFORM_FAST_INIT = "AT FI"; // ISO
const char * const HEADERS_OFF = "AT H0"; // OBD
const char * const HEADERS_ON = "AT H1"; // OBD
const char * const DISP_ID = "AT I"; // General
const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO
const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO
const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO
const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850
const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850
const char * const IFRS_OFF = "AT IFR0"; // J1850
const char * const IFRS_AUTO = "AT IFR1"; // J1850
const char * const IFRS_ON = "AT IFR2"; // J1850
const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other
const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %02d"; // ISO
const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850
const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850
const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850
const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850
const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850
const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850
const char * const DISP_KEY_WORDS = "AT KW"; // ISO
const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO
const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO
const char * const LINEFEEDS_OFF = "AT L0"; // General
const char * const LINEFEEDS_ON = "AT L1"; // General
const char * const LOW_POWER_MODE = "AT LP"; // General
const char * const MEMORY_OFF = "AT M0"; // General
const char * const MEMORY_ON = "AT M1"; // General
const char * const MONITOR_ALL = "AT MA"; // OBD
const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939
const char * const MONITOR_FOR_RECEIVER = "AT MR %02d"; // OBD
const char * const MONITOR_FOR_TRANSMITTER = "AT MT %02d"; // OBD
const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD
const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD
const char * const PROTOCOL_CLOSE = "AT PC"; // OBD
const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF";// PPs
const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs
const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF";// PPs
const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs
const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs
const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs
const char * const RESPONSES_OFF = "AT R0"; // OBD
const char * const RESPONSES_ON = "AT R1"; // OBD
const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %02d"; // OBD
const char * const READ_STORED_DATA = "AT RD"; // General
const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN
const char * const READ_VOLTAGE = "AT RV"; // Volts
const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD
const char * const PRINTING_SPACES_ON = "AT S1"; // OBD
const char * const STORE_DATA_BYTE = "AT SD "; // General
const char * const SET_HEADER = "AT SH %s"; // OBD
const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO
const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD
const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD
const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD
const char * const SET_REC_ADDRESS = "AT SR %02d"; // OBD
const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD
const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %02d"; // OBD
const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %02d"; // ISO
const char * const SET_TESTER_ADDRESS_TO = "AT TA %02d"; // OBD
const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD
const char * const TRY_PROT_H = "AT TP %c"; // OBD
const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN
const char * const VARIABLE_DLC_ON = "AT V1"; // CAN
const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO
const char * const WARM_START = "AT WS"; // General
const char * const RESET_ALL = "AT Z"; // General
const uint8_t responseBytes[0xA9] =
{
4, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2,
4, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2,
4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1,
4, 1, 1, 2, 5, 2, 5, 3, 3, 7, 5, 5, 5, 11, 9, 3, 10, 6, 5, 5, 5, 7, 7, 5, 9, 9, 7, 7, 9, 1, 1, 13,
4, 41, 41, 9, 1, 10, 5, 5, 13, 41, 41, 7, 17, 1, 1, 7, 3, 5, 2, 3, 12, 9, 9, 6, 4, 17, 4, 2, 9
};
#endif //ELMulator_DEFINITIONS_H
@@ -0,0 +1,27 @@
; ELMulator — fake ELM327 OBD-II adapter for testing without a car.
; Runs on a CLASSIC ESP32 (Bluetooth Classic SPP). Library lives in lib/ELMulator.
; One env per example: pio run -e simple | custom | wifi
[platformio]
default_envs = simple
[env]
platform = espressif32@6.10.0
board = esp32dev
framework = arduino
board_build.partitions = huge_app.csv
; upload_port = COMxx ; the classic ESP32's port (COM21 is the JC3636W518)
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
; all Mode 01 PIDs answered with mock data, BT name "ELMULATOR"
[env:simple]
build_src_filter = +<simple/>
; custom PID handler + DTC response, BT name "ELMULATOR"
[env:custom]
build_src_filter = +<custom/>
; custom handler, BT name "OBDII" (matches what elm327_wifi_bridge connects to)
[env:wifi]
build_src_filter = +<wifi/>
@@ -1,162 +0,0 @@
#include "ATCommands.h"
#include "definitions.h"
#if USE_WIFI
ATCommands::ATCommands(OBDWiFiComm *connection) {
this->connection = connection;
}
#else
ATCommands::ATCommands(OBDSerialComm *connection) {
this->connection = connection;
}
#endif
ATCommands::~ATCommands() {
operator delete(this->connection);
}
bool ATCommands::process(const String& command) {
bool processed = false;
if (isATCommand(command)) {
processed = true;
processCommand(command);
DEBUG("RX AT: " + command);
}
return processed;
}
void ATCommands::processCommand(const String& command) {
// if space is enabled (ex: AT H[charPosition])
uint8_t offset = 2;
if (command.charAt(2) == 0x20) {
offset = 3;
}
// refer to ELM327 specs
String specificCommand = command.substring(offset, command.length());
if (specificCommand.startsWith("D",offset)) {
ATCommands::ATD();
} else if (specificCommand.startsWith("Z")) {
ATCommands::ATZ();
} else if (specificCommand.startsWith("I")) {
ATCommands::ATI();
} else if (specificCommand.startsWith("E")) {
ATCommands::ATEx(specificCommand);
} else if (specificCommand.startsWith("L")) {
ATCommands::ATLx(specificCommand);
} else if (specificCommand.startsWith("M")) {
ATCommands::ATMx(specificCommand);
} else if (specificCommand.startsWith("S")) {
if (specificCommand.startsWith("SP")) {
ATCommands::ATSPx(specificCommand);
} else {
ATCommands::ATSx(specificCommand);
}
} else if (specificCommand.startsWith("H")) {
ATCommands::ATHx(specificCommand);
} else if (specificCommand.startsWith("AT")) {
ATCommands::ATATx(specificCommand);
} else if (specificCommand.startsWith("DPN")) {
ATCommands::ATDPN();
} else if (specificCommand.startsWith("DESC") || specificCommand.startsWith("@1")) {
ATCommands::ATDESC();
} else if (specificCommand.startsWith("PC")) {
ATCommands::ATPC();
} else if (specificCommand.startsWith("RV")){
ATCommands::ATRV();
} else {
// lets assume we process any at command
connection->writeEndOK();
}
}
// set all to defaults
void ATCommands::ATD() {
connection->setToDefaults();
connection->writeEndOK();
}
// reset all
void ATCommands::ATZ() {
connection->setToDefaults();
connection->writeTo(ID);
connection->writeEndOK();
}
// Print the version ID
void ATCommands::ATI() {
connection->writeTo(ID);
connection->writeEndOK();
}
// send description
void ATCommands::ATDESC() {
connection->writeTo(DESC);
connection->writeEndOK();
}
// set echoEnable 0=off 1=on
void ATCommands::ATEx(String& cmd) {
connection->setEcho(cmd.equals("E0") ? false : true);
connection->writeEndOK();
}
// set memory off=0 on=1
void ATCommands::ATMx(String& cmd) {
connection->setMemory(cmd.equals("M0") ? false : true);
connection->writeEndOK();
}
// line feeds off=0 on=1
void ATCommands::ATLx(String& cmd) {
connection->setLineFeeds(cmd.equals("L0") ? false : true);
connection->writeEndOK();
}
// ATSx printing spaces off=0 on=1
void ATCommands::ATSx(String& cmd) {
connection->setWhiteSpaces(cmd.equals("S0") ? false : true);
connection->writeEndOK();
}
// Headers off=0 on=1
void ATCommands::ATHx(String& cmd) {
connection->setHeaders(cmd.equals("H0") ? false : true);
connection->writeEndOK();
}
// ATSPx Define protocol 0=auto
void ATCommands::ATSPx(String& cmd) {
connection->writeEndOK();
}
// set protocol
void ATCommands::ATDPN() {
connection->writeTo(PROTOCOL);
connection->writeEndOK();
}
// AT AT2 adaptative time control
void ATCommands::ATATx(String& cmd) {
connection->writeEndOK();
}
// Terminates current diagnostic session. Protocol close
void ATCommands::ATPC() {
connection->writeEndOK();
}
void ATCommands::ATRV() {
connection->writeTo("AT RV\r13.5V");
connection->writeEnd();
}
// return true ir connectionand is AT
bool ATCommands::isATCommand(const String& command) {
toUpperCase(command.charAt(0));
return command.startsWith("AT") ? true : false;
}
@@ -1,65 +0,0 @@
#ifndef ELMulator_ATCommands_h
#define ELMulator_ATCommands_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
class ATCommands
{
public:
#if USE_WIFI
ATCommands(OBDWiFiComm *connection);
#else
ATCommands(OBDSerialComm *connection);
#endif
~ATCommands();
bool process(const String &string);
private:
// Variables
#if USE_WIFI
OBDWiFiComm *connection;
#else
OBDSerialComm *connection;
#endif
void ATD();
void ATZ();
void ATI();
void ATEx(String &x);
void ATMx(String &x);
void ATLx(String &x);
void ATSx(String &x);
void ATHx(String &x);
void ATSPx(String &x);
void ATATx(String &cmd);
void ATPC();
void ATDPN();
void ATDESC();
void ATRV();
void processCommand(const String &command);
bool isATCommand(const String &command);
};
#endif
@@ -1,218 +0,0 @@
#include "ELMulator.h"
#if USE_WIFI
ELMulator::ELMulator()
{
_connection = new OBDWiFiComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#else
ELMulator::ELMulator()
{
_connection = new OBDSerialComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#endif
ELMulator::~ELMulator() {}
void ELMulator::init(const String &deviceName, bool registerPids)
{
_connection->init(deviceName);
if (registerPids)
{
registerAllMode01Pids();
}
}
void ELMulator::registerAllMode01Pids()
{
// Set up PIDs 0x00 to 0x65
// Will be provided a response with mock data value if no special handling is performed.
// Requests for PID > 0x65 are not currently supported - will return "NO DATA"
for (int i = 0; i < 0x66; i++)
{
_pidProcessor->registerMode01Pid(i);
}
}
bool ELMulator::readELMRequest()
{
do
{
elmRequest.clear(); // clear buffer from previous requests
_connection->readData(elmRequest);
elmRequest.toUpperCase();
// TODO ignore spaces, and all control chars (tab, etc)
// TODO accept single carriage return as repeat last command at or pid
// ignore null i.e 00
} while (processRequest(elmRequest)); // processRequest handles all non PID requests (AT commands, errors etc)
return true; // We have received a valid PID request we need to respond to
}
void ELMulator::sendELMResponse()
{
// Respond to any mode 01 request with a mock sensor value
if (isMode01(elmRequest))
{
uint8_t pidCode = getPidCode(elmRequest);
writePidResponse(elmRequest, responseBytes[pidCode], getMockSensorValue());
return;
}
// Not a mode 01 PID request. Report it as not supported (ie, "NO DATA");
else
{
writePidNotSupported();
}
}
void ELMulator::begin()
{
while (readELMRequest())
{
sendELMResponse();
}
}
uint8_t ELMulator::getPidCode(const String &request)
{
return _pidProcessor->getPidCodeFromRequest(request);
}
bool ELMulator::registerMode01Pid(uint32_t pid)
{
return _pidProcessor->registerMode01Pid(pid);
}
bool ELMulator::registerMode01MILResponse(const String &response)
{
return _pidProcessor->registerMode01MILResponse(response);
}
bool ELMulator::registerMode03Response(const String &response)
{
return _pidProcessor->registerMode03Response(response);
}
void ELMulator::writePidNotSupported()
{
_connection->writeEndNoData();
}
void ELMulator::writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value)
{
_pidProcessor->writePidResponse(requestPid, numberOfBytes, value);
}
void ELMulator::writeResponse(const String &response)
{
_connection->writeTo(response.c_str());
_connection->writeEnd();
}
bool ELMulator::processRequest(String &command)
{
// TODO: check for no 0X0D char in command, return ? and >
// TODO: receive only <cr>, repeat last command
// carriage return, means repeat last command
if (command == "\n")
{
command = _lastCommand;
}
// empty command, do not send to user
if (command.length() == 0)
{
return true;
}
// Check for AT command
if (_atProcessor->process(command))
{
_lastCommand = command;
return true;
}
// Check for a valid hex string, return error if false
if (!isValidHex(command.c_str()))
{
_connection->writeEndUnknown();
DEBUG("Invalid HEX command: " + command);
return true;
}
// Check for a valid PID request
if (_pidProcessor->process(command))
{
_lastCommand = command;
return true;
}
return false;
}
bool ELMulator::isValidHex(const char *pid)
{
return (pid[strspn(pid, "0123456789abcdefABCDEF")] == 0);
}
bool ELMulator::isMode01(const String &command)
{
return _pidProcessor->isMode01(command);
}
bool ELMulator::isMode22(const String &command)
{
return _pidProcessor->isMode22(command);
}
bool ELMulator::isMode03(const String &command)
{
return _pidProcessor->isMode03(command);
}
bool ELMulator::isMode01MIL(const String &command)
{
return _pidProcessor->isMode01MIL(command);
}
/**
* Fake some data
* start counter = 0
* every call to getFakeSensorData, will increment the counter to a max of 255 (0xff)
* When 0xff is reached the cycle is reverted
* and the counter will decrement by one until 0 is reached
*/
uint32_t ELMulator::getMockSensorValue()
{
if (isCycleUp)
{
cycle++;
if (cycle == 0xff)
{
isCycleUp = false;
}
}
else
{
cycle--;
if (cycle == 0)
{
isCycleUp = true;
}
}
return cycle;
}
@@ -1,138 +0,0 @@
#ifndef ELMulator_ELMulator_h
#define ELMulator_ELMulator_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
#include "ATCommands.h"
#include "PidProcessor.h"
#include "definitions.h"
class ELMulator
{
public:
/**
* When creating a new instance of ELMulator with an attached
* BT module, we need to set the Bluetooth serial parameters
*
* @param baudRate - rate for bluetooth serial port (normally 9600)
* @param rxPin - the Arduino RX pin for the Bluetooth module (serial port)
* @param txPin - the Arduino TX pin for the Bluetooth module (serial port)
* @return
*/
ELMulator(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
ELMulator();
~ELMulator();
void init(const String &deviceName, const bool registerPids = false);
/**
* Waits and Reads a request from OBD client software (example: torque)
*
* It will only return if the OBD client software
* request's one of the previous registred PID (see: registerMode01Pid())
*
* Any other request will be automaticly handle or it will be rejected.
*
* ELMulator lib will automaticly handle:
* - any AT command
* - PID's support request like 0100, 0120, 0140, 0160, ...
*
* @return the request PID (Vehicle speed ex: 010D )
*/
bool readELMRequest();
void sendELMResponse();
void begin();
/**
* Registry the PID's (sensors) your arduino will support.
* Currently ELMulator lib only supports MODE 01 PID's
*
* In OBDII MODE 01 PID's, are request
* to Show current data for a given sensor identified by a PID
*
* List of MODE 01 PID's: https://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01
*
* Example, for Vehicle speed, where OBDII PID code is 0D:
*
* registerMode01Pid(0x0D)
*
* @param pidHexId
* @return returns true if the entered ID is in the valide range [0x00, 0xFF]
*/
bool registerMode01Pid(uint32_t pidHexId);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
uint8_t getPidCodeOnly(uint16_t hexCommand);
/**
* Used to informe the OBD client that the PID,
* it has requested is not supported
*
* In normal conditions, you will only receive,
* request for the PID you have registred
* All other request is automaticly handle by GTTuboEcu lib
*/
void writePidNotSupported();
/**
* Respond to a given PID request from OBDII client (ex: torque)
*
* @param requestPid - the received pid
* @param numberOfBytes - number of bytes this PID response VALUE is supposed to have
* @param value - sensor value for this PID,
* lib will convert it to HEX
* with the appropriated number of HEX chars (numberOfChars)
*/
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
// Write the response back to the requestor without PID formatting, etc
// Just pass the response string on. Useful for testing with a specific response
// that has been pre-configured.
void writeResponse(const String &response);
bool isMode01(const String &command);
bool isMode03(const String &command);
bool isMode01MIL(const String &command);
bool isMode22(const String &command);
uint8_t getPidCode(const String &request);
void registerAllMode01Pids();
uint32_t getMockSensorValue();
String elmRequest;
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
ATCommands *_atProcessor;
PidProcessor *_pidProcessor;
String _lastCommand;
bool isCycleUp = true;
uint32_t cycle = 0;
bool processRequest(String &command);
bool isValidHex(const char *pid);
};
#endif
@@ -1,145 +0,0 @@
#include "OBDSerialComm.h"
#include "definitions.h"
OBDSerialComm::OBDSerialComm() {
}
OBDSerialComm::~OBDSerialComm() {
operator delete(serial);
}
void OBDSerialComm::init(const String& deviceName) {
Serial.println("Starting BT . . .");
serial = new BluetoothSerial();
//delay(2000);
serial->begin(deviceName, false);
setToDefaults();
}
void OBDSerialComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
serial->flush();
};
void OBDSerialComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDSerialComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDSerialComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDSerialComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDSerialComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDSerialComm::writeTo(char const *response) {
serial->print(response);
}
void OBDSerialComm::writeTo(uint8_t cChar) {
serial->print(cChar);
}
void OBDSerialComm::writeEndPidTo(char const *response) {
if (whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDSerialComm::readData(String& rxData) {
serial->flush(); // temp remove this
rxData = serial->readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable()) {
writeTo(rxData.c_str());
}
//return rxData;
}
void OBDSerialComm::setBaudRate(uint32_t rate) {
this->baudRate = rate;
}
long OBDSerialComm::getBaudRate() {
return this->baudRate;
}
bool OBDSerialComm::isEchoEnable() {
return this->echoEnable;
}
void OBDSerialComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDSerialComm::setStatus(STATUS status) {
this->status = status;
}
void OBDSerialComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDSerialComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDSerialComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDSerialComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDSerialComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,90 +0,0 @@
#ifndef ELMulator_OBDSerialComm_h
#define ELMulator_OBDSerialComm_h
#include <Arduino.h>
#include "definitions.h"
#include <BluetoothSerial.h>
class OBDSerialComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
// // Bluetooth is not built in, we are using a BT module via GPIO
// OBDSerialComm(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
OBDSerialComm();
~OBDSerialComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
uint32_t baudRate; // Serial Baud Rate
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void setBaudRate(uint32_t rate);
long getBaudRate();
void addSpacesToResponse(const char *response, char string[]);
#ifndef BLUETOOTH_BUILTIN
HardwareSerial *serial; // lib to communicate with bluetooth
#else
BluetoothSerial *serial;
#endif
};
#endif
@@ -1,151 +0,0 @@
#include "OBDWiFiComm.h"
#include "definitions.h"
WiFiServer server(35000);
IPAddress localIP = IPAddress(192, 168, 0, 10);
IPAddress gateway = IPAddress(192, 168, 0, 10);
IPAddress subnet = IPAddress(255,255,255,0);
OBDWiFiComm::OBDWiFiComm() {
}
OBDWiFiComm::~OBDWiFiComm() {
}
void OBDWiFiComm::init(const String& deviceName) {
Serial.println("Starting AP . . .");
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(deviceName);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
Serial.print("AP SSID: ");
Serial.println(deviceName);
server.begin();
setToDefaults();
}
void OBDWiFiComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
};
void OBDWiFiComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDWiFiComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDWiFiComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDWiFiComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDWiFiComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDWiFiComm::writeTo(char const *response) {
client.print(response);
}
void OBDWiFiComm::writeTo(uint8_t cChar) {
client.print(cChar);
}
void OBDWiFiComm::writeEndPidTo(char const *response) {
if (this->whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDWiFiComm::readData(String& rxData) {
if (!client.connected()) {
client = server.available();
}
if(client)
{
rxData
= client.readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable())
{
writeTo(rxData.c_str());
}
}
}
bool OBDWiFiComm::isEchoEnable() {
return this->echoEnable;
}
void OBDWiFiComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDWiFiComm::setStatus(STATUS status) {
this->status = status;
}
void OBDWiFiComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDWiFiComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDWiFiComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDWiFiComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDWiFiComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,78 +0,0 @@
#ifndef ELMulator_OBDWiFiComm_h
#define ELMulator_OBDWiFiComm_h
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include "definitions.h"
class OBDWiFiComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
OBDWiFiComm();
~OBDWiFiComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void addSpacesToResponse(const char *response, char string[]);
WiFiClient client;
};
#endif
@@ -1,195 +0,0 @@
#include "PidProcessor.h"
#if USE_WIFI
PidProcessor::PidProcessor(OBDWiFiComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#else
PidProcessor::PidProcessor(OBDSerialComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#endif
bool PidProcessor::process(String& command) {
bool processed = false;
if (command.length() > 4)
{
command = command.substring(0, 4); // remove num_responses value if present; lib only handles single responses
}
if (!isMode01(command) & !isMode22(command))
{
_connection->writeEndNoData();
return false;
}
uint16_t hexCommand = strtoul(command.c_str(), NULL, HEX);
uint8_t pid = getPidCodeFromHex(hexCommand);
if (isSupportedPidRequest(pid)) { //reqeust to return a list of valid PIDs we can respond to
processed = true;
uint32_t supportedPids = getSupportedPids(pid);
writePidResponse(command, 4, supportedPids);
}
return processed;
}
void PidProcessor::writePidResponse(const String& requestPid, uint8_t numberOfBytes, uint32_t value) {
uint8_t nHexChars = PID_N_BYTES * N_CHARS_IN_BYTE + numberOfBytes * N_CHARS_IN_BYTE ;
char responseArray[nHexChars + 1]; // one more for termination char
getFormattedResponse(responseArray, nHexChars, requestPid, value);
_connection->writeEndPidTo(responseArray);
}
/**
* adds a supported pid, so it can answer to pid support request, ex 0100, 0120, ...
*/
bool PidProcessor::registerMode01Pid(uint32_t pid) {
// pid must be off type 01
if (pid > 0x00 && pid < 0x0200) {
// remove PidMode, only use pid code
pid = getPidCodeFromHex(pid);
setPidBit(pid);
char buffer[4];
sprintf(buffer, "%02X", pid);
DEBUG("Registered PID: " + String(buffer ));
return true;
}
return false;
}
bool PidProcessor::isMode01(const String& command)
{
return command.startsWith("01") ? true : false;
}
bool PidProcessor::isMode01MIL(const String& command)
{
return command.startsWith("0101") ? true : false;
}
bool PidProcessor::isMode03(const String& command)
{
return command.startsWith("03") ? true : false;
}
bool PidProcessor::isMode22(const String& command)
{
return command.startsWith("22") ? true : false;
}
/**
* return true if:
* - 1000
* - 1020
* - 1040
* - 1060
* ....
*/
bool PidProcessor::isSupportedPidRequest(uint8_t pid) {
return pid == getPidIntervalId(pid);
}
/**
* returns the interval RANGEID for the given pid (ex: 00, 20, 40, ...)
*/
uint8_t PidProcessor::getPidIntervalId(uint8_t pidcode) {
return getPidIntervalIndex(pidcode) * PID_INTERVAL_OFFSET;
}
/**
* returns the interval INDEX for the given pid (0,1,2,3,4,...)
*/
uint8_t PidProcessor::getPidIntervalIndex(uint8_t pidcode) {
return pidcode / PID_INTERVAL_OFFSET;
}
/**
* Receives a mode pid ex 010C ,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromHex(uint16_t hexCommand) {
if (hexCommand >= 0x0100) {
hexCommand -= 0x100;
}
return (uint8_t) hexCommand; // return only the lower 8 bits (trim off num_messages or other extra bytes if present)
}
/**
* Receives a complete PID request,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromRequest(const String& request) {
uint16_t hexCommand = strtoul(request.c_str(), NULL, HEX);
return getPidCodeFromHex(hexCommand);
}
void PidProcessor::setPidBit(uint8_t pid) {
uint8_t arrayIndex = getPidIntervalIndex(pid);
if(isSupportedPidRequest(pid)) {
arrayIndex -= 1;
}
uint8_t bitPosition = getPidBitPosition(pid);
bitWrite(pidMode01Supported[arrayIndex], bitPosition, 1);
// mark available intervals
for (int i = 0; i < arrayIndex; i++) {
bitWrite(pidMode01Supported[i], 0, 1);
}
}
uint8_t PidProcessor::getPidBitPosition(uint8_t pid) {
uint8_t intervalId = getPidIntervalId(pid) ;
if(isSupportedPidRequest(pid)) {
intervalId -= PID_INTERVAL_OFFSET;
}
uint16_t pos = pid - intervalId;
pos = PID_INTERVAL_OFFSET - pos;
return pos;
}
uint32_t PidProcessor:: getSupportedPids(uint8_t pid) {
uint8_t index = getPidIntervalIndex(pid);
return pidMode01Supported[index];
}
void PidProcessor::getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String& pid, uint32_t value) {
uint8_t nValueChars = totalNumberOfChars - PID_N_BYTES * N_CHARS_IN_BYTE;
char cValue[3] = {0};
itoa(nValueChars, cValue, DEC);
String mask = "%s%0";
mask.concat(cValue);
mask.concat("lX\0");
String pidResponse = convertToPidResponse(pid);
sprintf(response, mask.c_str(), pidResponse.c_str(), value);
DEBUG("RX PID: " + pid + " - TX: " + String(response));
}
String PidProcessor::convertToPidResponse(String pid) {
String x = "4";
x.concat(pid.substring(1, pid.length()));
return x;
}
void PidProcessor::resetPidMode01Array() {
for(uint8_t i = 0; i < N_MODE01_INTERVALS; i++ ) {
pidMode01Supported[i] = 0x0;
}
}
@@ -1,62 +0,0 @@
#ifndef ELMulator_PIDPROCESSOR_H
#define ELMulator_PIDPROCESSOR_H
#include "definitions.h"
#include <Arduino.h>
#include <WString.h>
#include <Print.h>
#include "OBDWiFiComm.h"
#include "OBDSerialComm.h"
class PidProcessor
{
public:
PidProcessor(OBDWiFiComm *connection);
PidProcessor(OBDSerialComm *connection);
bool process(String &string);
bool registerMode01Pid(uint32_t pid);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
uint8_t getPidCodeFromHex(uint16_t hexCommand);
uint8_t getPidCodeFromRequest(const String &command);
bool isMode01(const String &command);
bool isMode01MIL(const String &command);
bool isMode03(const String &command);
bool isMode22(const String &command);
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
uint32_t pidMode01Supported[N_MODE01_INTERVALS];
bool isSupportedPidRequest(uint8_t pid);
uint32_t getSupportedPids(uint8_t pidcode);
uint8_t getPidIntervalId(uint8_t pidcode);
uint8_t getPidIntervalIndex(uint8_t pidcode);
void setPidBit(uint8_t pid);
uint8_t getPidBitPosition(uint8_t pidcode);
String convertToPidResponse(String pid);
void getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String &pid, uint32_t value);
void resetPidMode01Array();
};
#endif // ELMulator_PIDPROCESSOR_H
@@ -1,294 +0,0 @@
#ifndef ELMulator_DEFINITIONS_H
#define ELMulator_DEFINITIONS_H
#include <stdint.h>
// true == We are running on hardware that has builtin Bluetooth
// false == We are using hardware that has BT module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
#define USE_WIFI false
#define DO_DEBUG true
#define DEBUG(x) do {if (DO_DEBUG) { Serial.println(x); } } while (0)
#define xtoc(x) ((x < 10) ? ('0' + x) : ('A' - 10 + x))
#define getNumOfHexChars(nBytes) (nBytes * 2)
#define PID_N_BYTES 2
#define N_CHARS_IN_BYTE 2
// Device ID
#define ID "ELM327 / ELMulator V1.3.0"
#define DESC "ELMulator OBD2 Arduino library, based on ELM327 protocol"
#define PROTOCOL "6" // canbus 500k 11 bit protocol id for elm.
// Char representing end of serial string
#define SERIAL_END_CHAR 0x0D
#define SERIAL_READ_TIMEOUT 20000L
#define WIFI_END_CHAR 0x0A
const uint8_t maxPid = 0xFF;
const uint8_t N_MODE01_INTERVALS = 7;
const uint8_t PID_INTERVAL_OFFSET = 0x20;
const uint8_t MAX_REQUEST_SIZE = 40;
//-------------------------------------------------------------------------------------//
// Protocol IDs
//-------------------------------------------------------------------------------------//
const char AUTOMATIC = '0';
const char SAE_J1850_PWM_41_KBAUD = '1';
const char SAE_J1850_PWM_10_KBAUD = '2';
const char ISO_9141_5_BAUD_INIT = '3';
const char ISO_14230_5_BAUD_INIT = '4';
const char ISO_14230_FAST_INIT = '5';
const char ISO_15765_11_BIT_500_KBAUD = '6';
const char ISO_15765_29_BIT_500_KBAUD = '7';
const char ISO_15765_11_BIT_250_KBAUD = '8';
const char ISO_15765_29_BIT_250_KBAUD = '9';
const char SAE_J1939_29_BIT_250_KBAUD = 'A';
const char USER_1_CAN = 'B';
const char USER_2_CAN = 'C';
//-------------------------------------------------------------------------------------//
// PIDs (https://en.wikipedia.org/wiki/OBD-II_PIDs)
//-------------------------------------------------------------------------------------//
const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded
const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded
const uint8_t FREEZE_DTC = 2; // 0x02 -
const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded
const uint8_t ENGINE_LOAD = 4; // 0x04 - %
const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - %
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - %
const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa
const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa
const uint8_t ENGINE_RPM = 12; // 0x0C - rpm
const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h
const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC
const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C
const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s
const uint8_t THROTTLE_POSITION = 17; // 0x11 - %
const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded
const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V %
const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V %
const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V %
const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V %
const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V %
const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V %
const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V %
const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V %
const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded
const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded
const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec
const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded
const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km
const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa
const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa
const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V
const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V
const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V
const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V
const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V
const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V
const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V
const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V
const uint8_t COMMANDED_EGR = 44; // 0x2C - %
const uint8_t EGR_ERROR = 45; // 0x2D - %
const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - %
const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - %
const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count
const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km
const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa
const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa
const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA
const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA
const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA
const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA
const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA
const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA
const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA
const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C
const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded
const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded
const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V
const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - %
const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio
const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - %
const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C
const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - %
const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - %
const uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - %
const uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - %
const uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - %
const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - %
const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min
const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min
const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa
const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s
const uint8_t FUEL_TYPE = 81; // 0x51 - ref table
const uint8_t ETHANOL_FUEL_PERCENT = 82; // 0x52 - %
const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa
const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - %
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - %
const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa
const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - %
const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - %
const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C
const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - °
const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h
const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded
const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded
const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - %
const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - %
const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm
const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - %
const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded
const uint8_t ODOMETER = 166; // 0xA6 - km * 10
const uint8_t SERVICE_01 = 1;
const uint8_t SERVICE_02 = 2;
const uint8_t SERVICE_03 = 3;
//-------------------------------------------------------------------------------------//
// AT commands (https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf)
//-------------------------------------------------------------------------------------//
const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General
const char * const DISP_DEVICE_ID = "AT @2"; // General
const char * const STORE_DEVICE_ID = "AT @3 %s"; // General
const char * const REPEAT_LAST_COMMAND = "AT \r"; // General
const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General
const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD
const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD
const char * const DUMP_BUFFER = "AT BD"; // OBD
const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD
const char * const TRY_BAUD_DIVISOR = "AT BRD %02d"; // General
const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %02d"; // General
const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN
const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN
const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN
const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %02d"; // CAN
const char * const SET_ID_FILTER = "AT CF %s"; // CAN
const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN
const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN
const char * const SET_ID_MASK = "AT CM %s"; // CAN
const char * const SET_CAN_PRIORITY = "AT CP %02d"; // CAN
const char * const SHOW_CAN_STATUS = "AT CS"; // CAN
const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN
const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN
const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %04d"; // Volts
const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts
const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General
const char * const DISP_DLC_OFF = "AT D0"; // CAN
const char * const DISP_DLC_ON = "AT D1"; // CAN
const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939
const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD
const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD
const char * const ECHO_OFF = "AT E0"; // General
const char * const ECHO_ON = "AT E1"; // General
const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN
const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN
const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %c"; // CAN
const char * const FORGE_EVENTS = "AT FE"; // General
const char * const PERFORM_FAST_INIT = "AT FI"; // ISO
const char * const HEADERS_OFF = "AT H0"; // OBD
const char * const HEADERS_ON = "AT H1"; // OBD
const char * const DISP_ID = "AT I"; // General
const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO
const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO
const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO
const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850
const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850
const char * const IFRS_OFF = "AT IFR0"; // J1850
const char * const IFRS_AUTO = "AT IFR1"; // J1850
const char * const IFRS_ON = "AT IFR2"; // J1850
const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other
const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %02d"; // ISO
const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850
const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850
const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850
const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850
const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850
const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850
const char * const DISP_KEY_WORDS = "AT KW"; // ISO
const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO
const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO
const char * const LINEFEEDS_OFF = "AT L0"; // General
const char * const LINEFEEDS_ON = "AT L1"; // General
const char * const LOW_POWER_MODE = "AT LP"; // General
const char * const MEMORY_OFF = "AT M0"; // General
const char * const MEMORY_ON = "AT M1"; // General
const char * const MONITOR_ALL = "AT MA"; // OBD
const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939
const char * const MONITOR_FOR_RECEIVER = "AT MR %02d"; // OBD
const char * const MONITOR_FOR_TRANSMITTER = "AT MT %02d"; // OBD
const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD
const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD
const char * const PROTOCOL_CLOSE = "AT PC"; // OBD
const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF";// PPs
const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs
const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF";// PPs
const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs
const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs
const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs
const char * const RESPONSES_OFF = "AT R0"; // OBD
const char * const RESPONSES_ON = "AT R1"; // OBD
const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %02d"; // OBD
const char * const READ_STORED_DATA = "AT RD"; // General
const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN
const char * const READ_VOLTAGE = "AT RV"; // Volts
const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD
const char * const PRINTING_SPACES_ON = "AT S1"; // OBD
const char * const STORE_DATA_BYTE = "AT SD "; // General
const char * const SET_HEADER = "AT SH %s"; // OBD
const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO
const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD
const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD
const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD
const char * const SET_REC_ADDRESS = "AT SR %02d"; // OBD
const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD
const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %02d"; // OBD
const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %02d"; // ISO
const char * const SET_TESTER_ADDRESS_TO = "AT TA %02d"; // OBD
const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD
const char * const TRY_PROT_H = "AT TP %c"; // OBD
const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN
const char * const VARIABLE_DLC_ON = "AT V1"; // CAN
const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO
const char * const WARM_START = "AT WS"; // General
const char * const RESET_ALL = "AT Z"; // General
const uint8_t responseBytes[0xA9] =
{
4, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2,
4, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2,
4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1,
4, 1, 1, 2, 5, 2, 5, 3, 3, 7, 5, 5, 5, 11, 9, 3, 10, 6, 5, 5, 5, 7, 7, 5, 9, 9, 7, 7, 9, 1, 1, 13,
4, 41, 41, 9, 1, 10, 5, 5, 13, 41, 41, 7, 17, 1, 1, 7, 3, 5, 2, 3, 12, 9, 9, 6, 4, 17, 4, 2, 9
};
#endif //ELMulator_DEFINITIONS_H
@@ -0,0 +1,25 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
board_build.partitions = huge_app.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; vendor-bundled libraries (not needed by this sketch, kept for consistency)
lib_extra_dirs = ../library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,75 +1,76 @@
#include "pincfg.h"
#include "SD_MMC.h"
// #include "FS.h"
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void setup() {
delay(200);
Serial.begin(115200);
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
Serial.print("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
listDir(SD_MMC, "/", 0);
}
void loop() {
}
#include <Arduino.h>
#include "pincfg.h"
#include "SD_MMC.h"
// #include "FS.h"
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void setup() {
delay(200);
Serial.begin(115200);
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
Serial.print("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
listDir(SD_MMC, "/", 0);
}
void loop() {
}
+11
View File
@@ -40,6 +40,17 @@ Free : GPIO38, GPIO39 (on CN2 header)
USB : D-=19 D+=20
```
## PlatformIO setup (preferred build system, since 2026-10-23)
- All projects are **PlatformIO projects** (`platformio.ini`, `src/main.cpp`, `include/`). They no longer open in the Arduino IDE.
- PlatformIO IDE extension is installed in VS Code. CLI: `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` (Core 6.2.0, not on PATH).
- **JC3636W518 (ESP32-S3)**: platform `https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip` (pioarduino = Arduino core 3.0.5; the official PIO espressif32 only has core 2.x, which ESP32_Display_Panel 0.1.4 can't use).
Board `esp32-s3-devkitc-1` + `board_build.arduino.memory_type = qio_opi`, `flash_mode = qio`, `board_upload.flash_size = 16MB`, `partitions = huge_app.csv`, `-DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1`, upload/monitor COM21 @115200.
Libraries: `lib_extra_dirs` → vendor `2-Program Example/Demo_Arduino/library`.
**LVGL config:** `-DLV_CONF_INCLUDE_SIMPLE -Iinclude` — the `-Iinclude` is required, otherwise the LVGL library compiles without the project's `lv_conf.h` (symptom: `undefined reference to lv_gif_create`).
- **Classic ESP32** projects (bridge, ELMulator): `espressif32@6.10.0` (core 2.0.17), board `esp32dev`, `huge_app.csv`.
- Template for a new JC3636W518 project: copy `10-OBD-Display-2024/display_speed_wifi/platformio.ini` (+ `include/pincfg.h`, `include/scr_st77916.h`, `include/lv_conf.h`).
- When converting `.ino` → `main.cpp`: add `#include <Arduino.h>` and prototypes for functions used before their definition; don't `#include` `.c` files that are also in `src/`.
## Software stack used by the vendor demo
- **Arduino IDE** + **arduino-esp32 core ≥ 3.0.1** (core **3.0.5 already installed** on this PC under `%LOCALAPPDATA%\Arduino15`).
- Libraries (bundled in `2-Program Example/Demo_Arduino/library/`):