Remove 2024 BT-Classic bridge, ELMulator and WiFi speed sketch

The ELM327 is read directly over BLE now. Docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patriandClaude Opus 5.5 committed 2026-10-23 16:38:15 +02:00
1 parent 0bd6aef6b5
commit 9e9c0fac68
30 files changed
+19 -3348

No files matched your search

+15 -32
View File
@@ -1,42 +1,25 @@
# OBD Display (2024 experiments)
Earlier attempt to show car speed from an ELM327 OBD-II adapter on the JC3636W518.
Because the ESP32-S3 has no Bluetooth Classic, the design used a second (classic) ESP32 as a bridge:
Leftovers of the earlier attempt to show car speed from an ELM327 on the JC3636W518.
The 2024 design needed a second (classic) ESP32 as a Bluetooth-Classic → WiFi bridge; that bridge, the ELMulator
and the WiFi speed sketch were removed on 2026-10-23 (still in git history) — the ELM327 is now read directly over BLE,
see `../12-OBD-Speed`.
```
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 and converted to PlatformIO. History is in git.
## 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 (`src/loligod.c`, 2.2 MB GIF → 3.4 MB firmware, uses `default_16MB.csv` with 6.25 MB app slots). **LVGL 9.6.0**, plays at full GIF speed (~23.4 fps), prints FPS once per second. GIF starts without SD card. WiFi code commented out. | `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, not converted | `ST77916_LVGL_DEMO/9.1/` |
## Projects (PlatformIO, LVGL 9.6.0, shared library in `../shared`)
| Folder | What it does |
|---|---|
| `display_gif_embedded/` | GIF compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.5 MB firmware, `default_16MB.csv`). Plays at full GIF speed (~23.8 fps, 53 % CPU). GIF starts without SD card. |
| `display_gif_sd/` | `wallpaper.gif` from the SD card, loaded into PSRAM once and played from memory (~15 fps for the current file). |
| `assets/gifs/` | GIF files (`dump/` = unused collection) |
| `_archive/lvgl9_broken/` | Unfinished 2024 LVGL 9 port, does not compile |
## 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 run -d display_gif_embedded -t upload # flash (COM21)
pio device monitor -d display_gif_embedded # serial: FPS / refresh / other / CPU once per second
```
(`pio` = `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` if not on PATH.)
## Configuration
- **JC3636W518 projects** (`display_*`): **LVGL 9.6.0** and the shared board library/config in `../shared` (see `shared/README.md`). Each `platformio.ini` only extends `shared/jc3636w518.ini`; `display_gif_embedded` additionally uses `default_16MB.csv`.
All three print `FPS / refresh / other / CPU` once per second on serial (115200).
- **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_STDIO` on `/sdcard/` (the SD_MMC mount). Not yet verified with LVGL 9 on hardware (SD card was stuck during the port).
- `display_speed_wifi` polls `/speed` with a blocking HTTP request on every loop pass (unchanged from 2024) — slows the UI when WiFi is connected.
- `display_gif_embedded/src/montserrat_100.c` (100 px digits font, 1 bpp) is present but not used.
- `display_gif_sd` returns early in `setup()` if the SD card fails to mount (black screen).
@@ -1,10 +0,0 @@
; GIF from SD + speed polled over WiFi from elm327_wifi_bridge — JC3636W518, common settings in shared/jc3636w518.ini
[platformio]
extra_configs = ../../shared/jc3636w518.ini
[shared]
dir = ../../shared
[env:jc3636w518]
extends = jc3636w518
@@ -1,180 +0,0 @@
#include <Arduino.h>
#include "jc3636w518.h"
#include "perf_monitor.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_screen_active());
// shown on a plain black screen: no transparency needed, so decode straight into the display's
// color format (no alpha blending, no conversion) to save CPU
lv_gif_set_color_format(img, lv_display_get_color_format(lv_display_get_default()));
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_screen_active());
// 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);
lv_display_t* disp = scr_lvgl_init();
perf_monitor_begin(disp);
//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_screen_active();
// 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() {
perf_timer_handler();
vTaskDelay(1);
perf_monitor_print();
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;
}
@@ -1,703 +0,0 @@
/*******************************************************************************
* Size: 100 px
* Bpp: 1
* Opts: --bpp 1 --size 100 --no-compress --font Montserrat-Regular.ttf --symbols 1234567890 --format lvgl -o montserrat_100.c
******************************************************************************/
#include "lvgl.h"
#ifndef MONTSERRAT_100
#define MONTSERRAT_100 1
#endif
#if MONTSERRAT_100
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+0030 "0" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x3, 0xff, 0xff,
0xff, 0xc0, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff,
0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x3f, 0xfc, 0x0, 0x3f, 0xfc, 0x0,
0x0, 0x7f, 0xe0, 0x0, 0x7, 0xfe, 0x0, 0x0,
0xff, 0xc0, 0x0, 0x3, 0xff, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0xff, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x1f, 0xc0,
0x0, 0x0, 0x0, 0x3, 0xf8, 0x1f, 0xc0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x1, 0xfc, 0x3f, 0x80, 0x0, 0x0, 0x0,
0x1, 0xfc, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0,
0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x3f, 0x80, 0x0,
0x0, 0x0, 0x1, 0xfc, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x3, 0xf8, 0x1f, 0xc0, 0x0, 0x0, 0x0,
0x3, 0xf8, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x7,
0xf8, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x7,
0xf8, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x7, 0xf8,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x3, 0xfc, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x1, 0xfe, 0x0, 0x0,
0x0, 0x7f, 0x80, 0x1, 0xff, 0x0, 0x0, 0x0,
0xff, 0x0, 0x0, 0xff, 0xc0, 0x0, 0x3, 0xff,
0x0, 0x0, 0x7f, 0xe0, 0x0, 0x7, 0xfe, 0x0,
0x0, 0x3f, 0xfc, 0x0, 0x3f, 0xfc, 0x0, 0x0,
0x1f, 0xff, 0xff, 0xff, 0xf8, 0x0, 0x0, 0xf,
0xff, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xff, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xfc,
0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0,
/* U+0031 "1" */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f,
/* U+0032 "2" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x3f, 0xff,
0xff, 0xc0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0,
0xf, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x1, 0xff, 0xf8, 0x0,
0x7, 0xff, 0x80, 0x7f, 0xf8, 0x0, 0x0, 0x3f,
0xf8, 0x1f, 0xfc, 0x0, 0x0, 0x1, 0xff, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x1f, 0xf0, 0x1f, 0x80,
0x0, 0x0, 0x1, 0xfe, 0x1, 0xe0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x18, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x80,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x80, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf8,
/* U+0033 "3" */
0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff,
0xff, 0xff, 0xfc, 0xf, 0xff, 0xff, 0xff, 0xff,
0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0,
0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xff, 0xc0, 0x0, 0x0, 0x0, 0xf, 0xff,
0xff, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x80, 0x0,
0x0, 0x7, 0xff, 0xff, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x7, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x10, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc3, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf8, 0xf0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x1f, 0x80, 0x0, 0x0, 0x0, 0x7f, 0xc7, 0xf8,
0x0, 0x0, 0x0, 0x1f, 0xf1, 0xff, 0xc0, 0x0,
0x0, 0x7, 0xfe, 0x1f, 0xff, 0x0, 0x0, 0x3,
0xff, 0x81, 0xff, 0xfc, 0x0, 0x1, 0xff, 0xe0,
0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0, 0xff,
0xff, 0xff, 0xff, 0xfe, 0x0, 0x7, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff,
0x80, 0x0, 0x0, 0xff, 0xff, 0xff, 0xc0, 0x0,
0x0, 0x1, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x38, 0x0, 0x0, 0x0,
/* U+0034 "4" */
0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0x0, 0x3, 0xf8, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x1f, 0xf0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0,
0x3f, 0x80, 0x0, 0x0, 0xff, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x1f, 0xc0,
0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x7f, 0x80, 0x0, 0x0, 0x7, 0xf0, 0x0,
0x7, 0xfc, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0,
0x3f, 0xc0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x3,
0xfc, 0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0,
/* U+0035 "5" */
0x0, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x3f,
0xff, 0xff, 0xff, 0xff, 0x0, 0xf, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x3, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0,
0x3f, 0xff, 0xff, 0xff, 0xff, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x1, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xfe, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xff, 0xfe,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0,
0x7, 0xff, 0xff, 0xff, 0xff, 0x0, 0x1, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x7f, 0xff, 0xff,
0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff,
0xc0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x10, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x86, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc3, 0xc0, 0x0, 0x0, 0x0, 0xf,
0xf1, 0xf8, 0x0, 0x0, 0x0, 0x7, 0xfc, 0x7f,
0x80, 0x0, 0x0, 0x3, 0xfe, 0x3f, 0xf8, 0x0,
0x0, 0x1, 0xff, 0xf, 0xff, 0x80, 0x0, 0x1,
0xff, 0xc1, 0xff, 0xfe, 0x0, 0x1, 0xff, 0xe0,
0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x3f, 0xff, 0xff,
0xff, 0xf8, 0x0, 0x1, 0xff, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xf8, 0x0, 0x0,
0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x1c, 0x0, 0x0, 0x0,
/* U+0036 "6" */
0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0,
0x0, 0xf, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x3,
0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x7f, 0xff,
0xff, 0xfe, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff,
0xf8, 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0x80,
0x0, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x3,
0xff, 0xe0, 0x0, 0xf, 0xc0, 0x0, 0x3f, 0xfc,
0x0, 0x0, 0xe, 0x0, 0x3, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x0, 0x0, 0xff, 0xf0, 0x0, 0x3, 0xf8,
0x0, 0x3f, 0xff, 0xf8, 0x0, 0x1f, 0xc0, 0xf,
0xff, 0xff, 0xf0, 0x0, 0xfe, 0x1, 0xff, 0xff,
0xff, 0xe0, 0x7, 0xf0, 0x1f, 0xff, 0xff, 0xff,
0xc0, 0x3f, 0x83, 0xff, 0xff, 0xff, 0xff, 0x1,
0xfc, 0x3f, 0xfc, 0x0, 0xff, 0xfc, 0xf, 0xe3,
0xff, 0x0, 0x0, 0x7f, 0xf0, 0x7f, 0x3f, 0xc0,
0x0, 0x0, 0xff, 0xc3, 0xf9, 0xfc, 0x0, 0x0,
0x3, 0xff, 0x1f, 0xdf, 0xc0, 0x0, 0x0, 0x7,
0xf8, 0xfe, 0xfc, 0x0, 0x0, 0x0, 0x1f, 0xe7,
0xff, 0xc0, 0x0, 0x0, 0x0, 0xff, 0x3f, 0xfc,
0x0, 0x0, 0x0, 0x3, 0xfd, 0xff, 0xc0, 0x0,
0x0, 0x0, 0xf, 0xef, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfd, 0xff, 0x0, 0x0, 0x0, 0x0, 0xf, 0xef,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x7f, 0xc0,
0x0, 0x0, 0x0, 0x3, 0xfb, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xcf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7,
0xf3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x8f,
0xe0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xe3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0xfe, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7f,
0x81, 0xfc, 0x0, 0x0, 0x0, 0x3, 0xf8, 0xf,
0xf0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x3f, 0xc0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0xff, 0x0, 0x0,
0x0, 0x3f, 0xe0, 0x3, 0xfe, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x1f, 0xf8, 0x0, 0x0, 0x7f, 0xe0,
0x0, 0x7f, 0xf8, 0x0, 0x1f, 0xfe, 0x0, 0x0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xfe, 0x0, 0x0, 0xf, 0xff, 0xff,
0xff, 0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xfc,
0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x80, 0x0,
0x0, 0x0, 0x3f, 0xff, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x80, 0x0, 0x0,
/* U+0037 "7" */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xef, 0xe0, 0x0,
0x0, 0x0, 0x1, 0xfc, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xcf, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f,
0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0xff, 0xf, 0xe0, 0x0, 0x0,
0x0, 0xf, 0xf0, 0xfe, 0x0, 0x0, 0x0, 0x1,
0xfe, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0xfe, 0x0, 0x0, 0x0, 0x3, 0xfc, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0xfe, 0x0, 0x0,
0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x80,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
/* U+0038 "8" */
0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x1f,
0xff, 0xff, 0xf0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xff, 0xf0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff,
0xf0, 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xe0,
0x0, 0xf, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0,
0x7f, 0xfc, 0x0, 0x7, 0xff, 0xc0, 0x3, 0xff,
0x80, 0x0, 0x3, 0xff, 0x80, 0xf, 0xf8, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0x0, 0x0,
0x7, 0xfc, 0x3, 0xfe, 0x0, 0x0, 0x0, 0xf,
0xf0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0x3f, 0x80, 0x0, 0x0, 0x0, 0x3f, 0x81, 0xfe,
0x0, 0x0, 0x0, 0x0, 0xff, 0x7, 0xf0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x7, 0xf0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xc1, 0xfe, 0x0, 0x0,
0x0, 0x0, 0xff, 0x3, 0xf8, 0x0, 0x0, 0x0,
0x3, 0xf8, 0xf, 0xe0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x7f, 0x80,
0x7f, 0x80, 0x0, 0x0, 0x3, 0xfc, 0x1, 0xff,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x3, 0xfe, 0x0,
0x0, 0x0, 0xff, 0x80, 0x7, 0xfe, 0x0, 0x0,
0xf, 0xfc, 0x0, 0xf, 0xff, 0x0, 0x1, 0xff,
0xe0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x0,
0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0,
0x1f, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff,
0xff, 0xff, 0xfc, 0x0, 0x0, 0xf, 0xff, 0xff,
0xff, 0xfc, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
0xfc, 0x0, 0xf, 0xff, 0xe0, 0x1, 0xff, 0xf8,
0x0, 0x7f, 0xf8, 0x0, 0x0, 0x7f, 0xf8, 0x3,
0xff, 0x0, 0x0, 0x0, 0x7f, 0xf0, 0x1f, 0xf8,
0x0, 0x0, 0x0, 0x7f, 0xc0, 0x7f, 0x80, 0x0,
0x0, 0x0, 0x7f, 0x83, 0xfc, 0x0, 0x0, 0x0,
0x0, 0xff, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfc, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7, 0xf9,
0xfc, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xbf, 0x80, 0x0,
0x0, 0x0, 0x0, 0xff, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x1, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xe0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xff, 0xc0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf9, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe7,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f, 0xf0,
0x0, 0x0, 0x0, 0x3, 0xfc, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x1f, 0xf0, 0x7f, 0xc0, 0x0, 0x0,
0x0, 0xff, 0x80, 0xff, 0xc0, 0x0, 0x0, 0xf,
0xfc, 0x3, 0xff, 0xc0, 0x0, 0x0, 0xff, 0xf0,
0x7, 0xff, 0xc0, 0x0, 0x1f, 0xff, 0x80, 0xf,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0xf, 0xff,
0xff, 0xff, 0xff, 0xc0, 0x0, 0x1f, 0xff, 0xff,
0xff, 0xfe, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff,
0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xfc, 0x0,
0x0, 0x0, 0x7, 0xff, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0x1c, 0x0, 0x0, 0x0,
/* U+0039 "9" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xff, 0xe0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xff,
0x80, 0x0, 0x0, 0x7f, 0xff, 0xff, 0xfe, 0x0,
0x0, 0xf, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x1,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x0, 0x7f, 0xf8,
0x0, 0x1f, 0xfe, 0x0, 0x7, 0xfe, 0x0, 0x0,
0x3f, 0xf0, 0x0, 0xff, 0x80, 0x0, 0x0, 0xff,
0x80, 0x1f, 0xf0, 0x0, 0x0, 0x7, 0xf8, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x3f, 0xc0,
0x0, 0x0, 0x1, 0xfe, 0x3, 0xf8, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0xff, 0x7, 0xf0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f, 0xf0,
0x0, 0x0, 0x0, 0x3, 0xf8, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x3f, 0x8f, 0xe0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xcf, 0xe0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xef, 0xe0, 0x0,
0x0, 0x0, 0x3, 0xfe, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xef, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0xff, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe7,
0xf0, 0x0, 0x0, 0x0, 0x7, 0xff, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x7f, 0xf7, 0xf8, 0x0, 0x0,
0x0, 0xf, 0xff, 0x3f, 0xc0, 0x0, 0x0, 0x1,
0xff, 0xf3, 0xfc, 0x0, 0x0, 0x0, 0x1f, 0xff,
0x1f, 0xe0, 0x0, 0x0, 0x3, 0xf7, 0xf1, 0xff,
0x80, 0x0, 0x0, 0xfe, 0x7f, 0xf, 0xfc, 0x0,
0x0, 0x1f, 0xe7, 0xf0, 0x7f, 0xf0, 0x0, 0x7,
0xfc, 0x7f, 0x3, 0xff, 0xe0, 0x3, 0xff, 0x87,
0xf0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0x0,
0xff, 0xff, 0xff, 0xfe, 0x7, 0xf0, 0x3, 0xff,
0xff, 0xff, 0x80, 0x7f, 0x0, 0x1f, 0xff, 0xff,
0xe0, 0x7, 0xf0, 0x0, 0x3f, 0xff, 0xf8, 0x0,
0x7f, 0x0, 0x0, 0x3f, 0xf8, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7e, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x1, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xe0, 0x0, 0x38, 0x0, 0x0, 0x1f, 0xfc, 0x0,
0x3, 0xf0, 0x0, 0xf, 0xff, 0x80, 0x0, 0x7f,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x7, 0xff, 0xff,
0xff, 0xfc, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
0x80, 0x0, 0x7, 0xff, 0xff, 0xff, 0xe0, 0x0,
0x0, 0x1f, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0,
0x1f, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x1,
0x80, 0x0, 0x0, 0x0
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 1059, .box_w = 56, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 504, .adv_w = 578, .box_w = 24, .box_h = 70, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 714, .adv_w = 909, .box_w = 51, .box_h = 71, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1167, .adv_w = 902, .box_w = 51, .box_h = 71, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 1620, .adv_w = 1058, .box_w = 61, .box_h = 70, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 2154, .adv_w = 906, .box_w = 50, .box_h = 71, .ofs_x = 2, .ofs_y = -1},
{.bitmap_index = 2598, .adv_w = 974, .box_w = 53, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 3075, .adv_w = 942, .box_w = 52, .box_h = 70, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 3530, .adv_w = 1021, .box_w = 54, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 4016, .adv_w = 974, .box_w = 52, .box_h = 72, .ofs_x = 3, .ofs_y = -1}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 48, .range_length = 10, .glyph_id_start = 1,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.bpp = 1,
.kern_classes = 0,
.bitmap_format = 0,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t montserrat_100 = {
#else
lv_font_t montserrat_100 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 72, /*The maximum line height required by the font*/
.base_line = 1, /*Baseline measured from the bottom of the line*/
// #if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
// .subpx = LV_FONT_SUBPX_NONE,
// #endif
// #if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
// .underline_position = -10,
// .underline_thickness = 5,
// #endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
// #if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
// .fallback = NULL,
// #endif
.user_data = NULL,
};
#endif /*#if MONTSERRAT_100*/
@@ -1,18 +0,0 @@
; 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,96 +0,0 @@
#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();
}
}
-7
View File
@@ -1,7 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode/extensions.json
-260
View File
@@ -1,260 +0,0 @@
# ELMulator
## ODBII & ELM327 Emulation library
This is a fork of the [GTTurboEcu library by TheBigBadWolfClub](https://github.com/TheBigBadWolfClub/GTTurboEcu) which is no longer being maintained.
I have updated the library with the following changes:
- included fixes submitted to the upstream fork but never merged
- updated the library to work with Bluetooth serial devices (like esp32)
- added support for connecting via WiFi (default port 30000)
- added additional functions to the emulator
- optimized the use of String class for lower memory requirements
- fixed a few bugs from earlier version
- refactored many methods to simplify use of the library
- made it easier to add your own new functions with sample/test data
- implemented a simple emulation mode that can be easily configured with just a few lines of code
- added new example programs to demonstrate use of the library
This library allows you to program a microcontroller device to emulate an ELM327 OBDII adapter connected to an ECU. The emulator can work with any of the huge number of ODBII apps that are compatible with the ELM327-standard adapters, like Torque or CarScanner.
There are two main use cases for this library:
1. It is ideal for use in developing OBDII applications using the [ELMduino](https://github.com/PowerBroker2/ELMduino) library. You can build the example program, upload it to a $10 ESP32 device with builtin Bluetooth and use it for your testing and development, avoiding the need to be communicating with an actual ELM327 device plugged into a vehicle. (Doing app development sitting on a folding chair in the garage gets old pretty quickly...).
2. This library can be used to as the building block for creating your own ELM327 compatible device that can read values from various custom sensors and allow you to monitor
those sensors with your own software or one of the many OBDII client applcations that are compatible with the ELM327 specification. No connection to an actual OBDII port is needed.
## Platforms
ELMulator is built and tested using the Arduino framework on a variety of ESP32 dev boards. The ESP32 platform is ideal as it has Bluetooth, Wifi and serial capability included and is very inexpensive. The library should work with any Arduino compatible device with the use of additional hardware like the [HC05 Bluetooth Module](https://components101.com/wireless/hc-05-bluetooth-module) to provide Bluetooth capability.
## Using ELMulator
In order to take best advantage of this library, you should be familiar with two specifications:
- The [OBDII protocol](https://en.wikipedia.org/wiki/OBD-II_PIDs)
- The [ELM327 datasheet](https://www.elmelectronics.com/DSheets/ELM327DSH.pdf)
You need to understand what a PID is and how it is defined (mode, pid, number of bytes, conversion formulas ...) and how to send/receive PID info from the ELM327 device.
### Easily create an ELM327 emulator with just a few lines of code:
```C
#include "ESP32_Simple_ELMulator.h"
/**
* This example demonstrates a very simple implementation of ELMulator.
* It handles all AT Commands, SUPPORTED_PIDS queries, etc. and rejectss unsupported requests.
* All Mode 01 PID requests are provided a response of the correct data size with mock data values.
* Typical use case is for testing custom applications to work with ELM327 devices.
*/
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName, true); // Initialize with support for all PIDs 0x00 to 0x65, providing mock data
ELMulator.begin(); // ELMulator will run continuously, listening for and respondng to requests
}
void loop() {}
```
That's it. Complile and install the program on an inexpensive ESP32 dev board and you can be running in a couple of minutes. You can connect your app or device to the Bluetooth device "ELMULATOR" and query it as though it were an ELM327 adapter connected to a vehicle. Very handy for testing, without the need to install and configure a physical adapter attached to an actual vehicle.
### Custom ELM327-compatible programs/devices
You can also use ELMulator to provide custom responses to specific queries or as the basis for your own device that reads physical sensors and provides sensor data via the OBDII / ELM327 protocols. This example code shows responding to PID requests with hardcoded values, custom sensor data and mock sensor values to demonstrate some of the possibilities for creating your ELM327 compatible application or device.
```C
#include "ESP32_Custom_ELMulator.h"
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
```
## Using ELMulator with an addon Bluetooth module (GPIO)
By default, ELMulator will work on an EPS32 with builtin Bluetooth, but you can configure another device type for use with a bluetooth module like the [HC05 Bluetooth Module](https://components101.com/wireless/hc-05-bluetooth-module) connected via GPIO and using SoftwareSerial.h library. To do so, the following two changes need to be made:
1. Update definitions.h file and undefine or set to false "BLUETOOTH_BUILTIN":
```
// true == We are using hardware that has builtin Bluetooth
// false == We are using hardware with a Bluetooth module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
```
2. Provide the serial baud rate, Rx and Tx pin numbers when creating your ELMulator instance:
```
/**
* 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);
```
Other than that, use of the ELMulator library is the same as when using builtin Bluetooth.
## License
The MIT License (MIT)
Copyright (c) 2023 Jim Whitelaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Whitespace-only changes.
@@ -1,9 +0,0 @@
name=ELMulator
version=1.0.0
author=jimwhitelaw
maintainer=jimwhitelaw@gmail.com
sentence=Arduino ODBII ELM327 Emulator - Allows Arduino to act as a ECU and a OBD2 ELM327 device and response to PID requests, from any given ODBII app (ex: android::torque).
paragraph=Using Arduino and Bluetooth shield to act as an ELM237. Connect sensors to arduino define their PID code and it's possible to see them in an app like Torque.
category=Communication
url=https://github.com/jimwhitelaw/ELMulator
architectures=*
@@ -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
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2023 Jim Whitelaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -1,27 +0,0 @@
; 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,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,172 +0,0 @@
#include "ESP32_Custom_ELMulator.h"
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
const String ethPercentResponse = "620052C6";
const String dpfCloggingResponse = "6218E4C6";
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode22(request)) // Mode 0x22 (extended info) service - Mfg-dependent and not defined in OBDII spec
{
Serial.print("Mode 22 PID: "); Serial.println(request.substring(2));
if (request.substring(2).compareTo("52") == 0) // Ethanol percent
{
if (ethPercentResponse.length())
{
ELMulator.writeResponse(ethPercentResponse);
return;
}
else
{
DEBUG("ETH % response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
if (request.substring(2).compareTo("18E4") == 0) // DPF Clogging A-R Giulia
{
if (dpfCloggingResponse.length())
{
ELMulator.writeResponse(dpfCloggingResponse);
return;
}
else
{
DEBUG("DPF Clogging response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
ELMulator.writePidNotSupported();
return;
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
@@ -1,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,21 +0,0 @@
#include "ESP32_Simple_ELMulator.h"
/**
* This example demonstrates a very simple implementation of ELMulator.
* It handles all AT Commands, SUPPORTED_PIDS queries, etc. and rejectss unsupported requests.
* All Mode 01 PID requests are provided a response of the correct data size with mock data values.
* Typical use case is for testing custom applications to work with ELM327 devices.
*/
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName, true); // Initialize with support for all PIDs 0x00 to 0x65, providing mock data
ELMulator.begin(); // ELMulator will run continuously, listening for and respondng to requests
}
void loop() {}
@@ -1,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,172 +0,0 @@
#include "ESP32_WiFi_Custom_ELMulator.h"
const String deviceName = "OBDII";
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
const String ethPercentResponse = "620052C6";
const String dpfCloggingResponse = "6218E4C6";
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode22(request)) // Mode 0x22 (extended info) service - Mfg-dependent and not defined in OBDII spec
{
Serial.print("Mode 22 PID: "); Serial.println(request.substring(2));
if (request.substring(2).compareTo("52") == 0) // Ethanol percent
{
if (ethPercentResponse.length())
{
ELMulator.writeResponse(ethPercentResponse);
return;
}
else
{
DEBUG("ETH % response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
if (request.substring(2).compareTo("18E4") == 0) // DPF Clogging A-R Giulia
{
if (dpfCloggingResponse.length())
{
ELMulator.writeResponse(dpfCloggingResponse);
return;
}
else
{
DEBUG("DPF Clogging response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
ELMulator.writePidNotSupported();
return;
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
+4 -4
View File
@@ -48,7 +48,7 @@ USB : D-=19 D+=20
**All of this lives in `shared/jc3636w518.ini`**; a project's `platformio.ini` only does `extra_configs` + `[shared] dir` + `extends = jc3636w518` (see `shared/README.md`).
Libraries: `lib_deps = lvgl/lvgl@9.6.0`, `lib_extra_dirs` → `shared/lib` (board library) + vendor `2-Program Example/Demo_Arduino/library` (ESP32_Display_Panel, ESP32_IO_Expander; its lvgl 8 is not used).
**LVGL config:** `-DLV_CONF_INCLUDE_SIMPLE -I${shared.dir}/include` — the `-I` is required, otherwise the LVGL library compiles without `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`.
- (Classic ESP32 projects — the 2024 BT-Classic→WiFi bridge and ELMulator — were removed 2026-10-23; they used `espressif32@6.10.0`, board `esp32dev`. In git history.)
- **New JC3636W518 project:** follow `shared/README.md` (3-line platformio.ini + `#include "jc3636w518.h"`).
- Editing files with scripts: keep each file's line endings (most are LF, some `main.cpp` are CRLF) — Python `open(..., newline="")`.
- 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/`.
@@ -80,7 +80,7 @@ Display color format is `RGB565_SWAPPED` (ST77916 byte order, set in `lv_conf.h`
| `2-Program Example/Demo_Arduino` | **Vendor only:** `library/` (3 libs), `lv_conf.h` (vendor original), `sd/` (SD listing example; its lv_conf.h has FATFS edits), `docs/` (setup screenshots + compile instructions). The vendor's original LVGL demo sketch no longer exists unmodified. |
| `shared` | **Shared code for all JC3636W518 projects:** `jc3636w518.ini` (PlatformIO base config), `include/lv_conf.h` (LVGL 9.6), `lib/JC3636W518` (board library). See its `README.md` |
| `11-BLE-Scanner` | **BLE scanner with on-screen UI** (NimBLE-Arduino 2.5.1): live device list, tap to list services/characteristics, ELM327 test (ATZ/ATE0/ATI/ATRV/0100). Results saved to LittleFS `/ble_log.txt`; serial `d` dump, `c` clear, `s` device list. See its `README.md` |
| `10-OBD-Display-2024` | **Earlier OBD speed-display project**, split into separate sketches — see its `README.md`: `display_speed_wifi`, `display_gif_sd`, `display_gif_embedded`, `elm327_wifi_bridge` (classic ESP32), `elmulator`, `assets/gifs`, `_archive/lvgl9_broken` |
| `10-OBD-Display-2024` | **Leftovers of the 2024 OBD project:** `display_gif_embedded`, `display_gif_sd`, `assets/gifs`, `_archive/lvgl9_broken` (bridge/ELMulator/WiFi speed sketch removed 2026-10-23) |
| `3-Specification` | Spec sheet (factory firmware features, interface photo) |
| `4-Chip manual` | ESP32-S3R8 datasheet, PCM5100A datasheet |
| `5-Dimension` | Mechanical drawings (JPG + DWG) |
@@ -113,11 +113,11 @@ AliExpress "ELM327 Mini V2.1 Bluetooth OBD **Dual Mode** 5.1" — dual mode = Bl
- **LVGL 9.6.0 port** (`display_gif_embedded`, 2026-10-23): **23.4 fps** (= the GIF's own 23.7 fps; decode ~12 ms, refresh ~24 ms) vs 20.6 fps tuned LVGL 8.3. LVGL 9.6 replaced gifdec with bitbank2 AnimatedGIF (2× faster decode, correct transparency — no local patch needed).
Port notes: `lib_deps = lvgl/lvgl@9.6.0` (takes priority over the vendor lvgl 8 in `lib_extra_dirs`); `lv_conf.h` generated from the 9.6 template (CLIB malloc, refresh 15 ms, GIF on, examples off); in 9.6 public headers live in `include/lvgl/` and `LV_COLOR_DEPTH` is deprecated → `LV_COLOR_FORMAT_DEFAULT` (RGB565); no LV_COLOR_16_SWAP → `lv_draw_rgb565_swap()` in the flush cb; no LV_TICK_CUSTOM → `lv_tick_set_cb(millis)`; no `monitor_cb` → display events `LV_EVENT_REFR_START/RENDER_START/REFR_READY`; images are `lv_image_dsc_t` with `LV_IMAGE_HEADER_MAGIC`. `lv_gif_set_color_format()` switches the GIF buffer format (used, see below).
- **LVGL 9 tuning** (display_gif_embedded, CPU at full 23.8 fps): start 86 % → GIF in display color format 66 % → render `RGB565_SWAPPED` (no swap pass) 60 % → QSPI 80 MHz 55 % → IRAM for hot LVGL code 53 %. No effect: `LV_GIF_CACHE_DECODE_DATA` (not used anywhere in 9.6), 120-row buffers. AnimatedGIF "turbo" mode can't be used: it only outputs in COOKED draw mode, LVGL uses RAW.
- **All JC3636W518 LVGL projects now use LVGL 9.6.0 + the shared library** (display_gif_embedded, display_gif_sd, display_speed_wifi). SD drive `A:` = `LV_USE_FS_STDIO` on `/sdcard/`.
- **All JC3636W518 LVGL projects now use LVGL 9.6.0 + the shared library** (display_gif_embedded, display_gif_sd, 11-BLE-Scanner, 12-OBD-Speed). SD drive `A:` = `LV_USE_FS_STDIO` on `/sdcard/`.
- 80 MHz QSPI and RGB565_SWAPPED rendering: **visual check by the user still pending** (colors / glitches).
- Display is round: corners of the 360×360 framebuffer are not visible — design UI within a 360 px circle.
- Compiling a 13.9 MB GIF into the firmware will not fit "Huge APP" (3 MB); large media should live on SD (or in a custom partition).
- `display_gif_sd` / `display_speed_wifi` `setup()` return early if the SD card fails to mount (black screen); `display_gif_embedded` starts its GIF first.
- `display_gif_sd` `setup()` returns early if the SD card fails to mount (black screen); `display_gif_embedded` starts its GIF first.
- Draw buffer is in internal RAM; PSRAM is available for larger buffers / images.
- **LittleFS** works on the `spiffs` partition of `huge_app.csv` (~900 KB); the first mount after flashing formats it (prints `Corrupted dir pair` / `mount failed (-84)` and some `task_wdt ... task not found` once — harmless).
- BLE (NimBLE) in the user's home: ~12 devices visible within 10 s.