12-OBD-Speed: battery voltage arc and idle/screen-off mode
- ATRV every 2 s; voltage arc in the bottom gap (11-15 V) + number, colored by state (low / weak / resting / charging / overcharge) - idle after Off/2/5/10 min without speed data (settings page, NVS): screen off, GIF paused, OBD polling stopped, ATRV every 30 s; wakes on touch (swallowed by a top-layer overlay) or > 13.2 V - status pill moved up to make room for the voltage Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0777db3498
commit
be69bda4f1
5 files changed
+258
-16
No files matched your search
@@ -5,11 +5,17 @@ Uses the shared board library and LVGL 9.6 (`../shared`), NimBLE-Arduino 2.5.1.
|
||||
|
||||
## Use
|
||||
- **Speed page** — speed in km/h (`--` = no data: not connected, or ignition off), arc gauge 0–200 km/h,
|
||||
status at the bottom (adapter name when running, otherwise what it is doing).
|
||||
status pill below the number (adapter name when running, otherwise what it is doing).
|
||||
**Battery voltage** (`ATRV`, every 2 s) as a small arc in the bottom gap (11–15 V) + number; color:
|
||||
red < 11.8 V, orange < 12.2 V, green < 13.0 V (resting OK), blue ≤ 14.8 V (charging), red above (overcharge).
|
||||
- **Swipe left → adapter menu** — scans while open. OBD-like adapters (name OBD/ELM/…, or service FFF0/FFE0/18F0)
|
||||
are listed first in green. **Tap one** to select it: it is remembered (NVS) and connected immediately.
|
||||
**Forget** clears the remembered adapter, **Done** (or swipe right) returns to the speed page.
|
||||
- **Swipe left again → settings** — screen rotation 0 / 90 / 180 / 270 degrees (tap to apply; remembered in NVS, applied at boot).
|
||||
- **Swipe left again → settings** — screen rotation 0 / 90 / 180 / 270 degrees, and **screen off without data** Off / 2 / 5 / 10 min (default 2). Both remembered in NVS.
|
||||
- **Idle** — after that time without speed data (ignition off, adapter gone) the screen goes dark, the GIF pauses and
|
||||
OBD polling stops; only `ATRV` every 30 s (answered by the adapter itself, doesn't wake the car's bus).
|
||||
Wakes on a touch (the touch is swallowed) or by itself when the voltage rises above 13.2 V (engine started).
|
||||
Not used while no adapter is chosen.
|
||||
- On power-up the remembered adapter is connected automatically; if the link drops or the adapter is not
|
||||
there (car off), it retries every 3 s. With no adapter remembered, the device starts on the menu page.
|
||||
|
||||
|
||||
+159
-10
@@ -1,12 +1,16 @@
|
||||
// OBD speed display for the JC3636W518: vehicle speed from an ELM327 over BLE on top of an animated GIF.
|
||||
// Swipe left for the adapter menu (scan, select, forget); the selected adapter is remembered and reconnected.
|
||||
// Swipe left again for settings (screen rotation, remembered).
|
||||
// Swipe left again for settings (screen rotation, idle timeout; remembered).
|
||||
// Battery voltage is shown as a small arc in the bottom gap; after a while without speed data the
|
||||
// screen goes dark (idle) and wakes on touch or when the engine starts.
|
||||
#include <Arduino.h>
|
||||
#include <Preferences.h>
|
||||
#include "jc3636w518.h"
|
||||
#include "obd_link.h"
|
||||
|
||||
#define SPEED_MAX 200
|
||||
#define VOLT_MIN_X10 110 // voltage arc range 11.0 .. 15.0 V
|
||||
#define VOLT_MAX_X10 150
|
||||
#define MAX_MENU_ITEMS 30
|
||||
|
||||
LV_FONT_DECLARE(font_speed_120);
|
||||
@@ -22,8 +26,10 @@ static const lv_color_t COLOR_OBD = lv_color_hex(0x4CD964);
|
||||
static const lv_color_t COLOR_PANEL = lv_color_hex(0x161C22);
|
||||
|
||||
static lv_obj_t *tileview, *tile_speed, *tile_menu, *tile_settings;
|
||||
static lv_obj_t *rot_buttons[4];
|
||||
static lv_obj_t *rot_buttons[4], *idle_buttons[4];
|
||||
static const uint8_t IDLE_MINUTES[4] = {0, 2, 5, 10}; // 0 = never
|
||||
static lv_obj_t *speed_arc, *speed_label, *speed_shadow, *status_label;
|
||||
static lv_obj_t *volt_arc, *volt_label, *bg_gif, *idle_overlay;
|
||||
static lv_obj_t *menu_saved, *menu_status, *menu_list;
|
||||
static lv_obj_t *menu_items[MAX_MENU_ITEMS];
|
||||
static int menu_item_count = 0;
|
||||
@@ -109,6 +115,38 @@ static void on_rotation_clicked(lv_event_t *e)
|
||||
p.end();
|
||||
}
|
||||
|
||||
// ---------- settings: idle timeout ----------
|
||||
static uint8_t load_idle_minutes()
|
||||
{
|
||||
Preferences p;
|
||||
p.begin("ui", true);
|
||||
uint8_t m = p.getUChar("idle", 2);
|
||||
p.end();
|
||||
return m;
|
||||
}
|
||||
|
||||
static void highlight_idle(uint8_t minutes)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
lv_obj_set_style_bg_color(idle_buttons[i], IDLE_MINUTES[i] == minutes ? COLOR_ARC : lv_color_hex(0x222A33), 0);
|
||||
}
|
||||
|
||||
static void on_idle_clicked(lv_event_t *e)
|
||||
{
|
||||
uint8_t minutes = IDLE_MINUTES[(int)(intptr_t)lv_event_get_user_data(e)];
|
||||
obd_set_idle_timeout(minutes);
|
||||
highlight_idle(minutes);
|
||||
Preferences p;
|
||||
p.begin("ui", false);
|
||||
p.putUChar("idle", minutes);
|
||||
p.end();
|
||||
}
|
||||
|
||||
static void on_idle_overlay_pressed(lv_event_t *e)
|
||||
{
|
||||
obd_wake(); // touch while the screen is dark: wake up (the touch itself is swallowed)
|
||||
}
|
||||
|
||||
// ---------- speed page ----------
|
||||
static void build_speed_tile()
|
||||
{
|
||||
@@ -120,10 +158,10 @@ static void build_speed_tile()
|
||||
gif_dsc.header.h = bg_gif_start[8] | (bg_gif_start[9] << 8);
|
||||
gif_dsc.data_size = bg_gif_end - bg_gif_start;
|
||||
gif_dsc.data = bg_gif_start;
|
||||
lv_obj_t *gif = lv_gif_create(tile_speed);
|
||||
lv_gif_set_color_format(gif, lv_display_get_color_format(lv_display_get_default()));
|
||||
lv_gif_set_src(gif, &gif_dsc);
|
||||
lv_obj_center(gif);
|
||||
bg_gif = lv_gif_create(tile_speed);
|
||||
lv_gif_set_color_format(bg_gif, lv_display_get_color_format(lv_display_get_default()));
|
||||
lv_gif_set_src(bg_gif, &gif_dsc);
|
||||
lv_obj_center(bg_gif);
|
||||
|
||||
// arc gauge along the round edge: 270° sweep, gap at the bottom
|
||||
speed_arc = lv_arc_create(tile_speed);
|
||||
@@ -141,6 +179,28 @@ static void build_speed_tile()
|
||||
lv_obj_set_style_arc_opa(speed_arc, LV_OPA_30, LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_color(speed_arc, COLOR_ARC, LV_PART_INDICATOR);
|
||||
|
||||
// battery voltage arc in the bottom gap of the speed arc, filling left to right
|
||||
volt_arc = lv_arc_create(tile_speed);
|
||||
lv_obj_set_size(volt_arc, 344, 344);
|
||||
lv_obj_center(volt_arc);
|
||||
lv_arc_set_rotation(volt_arc, 45);
|
||||
lv_arc_set_bg_angles(volt_arc, 8, 82); // small margin to the speed arc ends
|
||||
lv_arc_set_mode(volt_arc, LV_ARC_MODE_REVERSE); // starts at the left end (bottom-left)
|
||||
lv_arc_set_range(volt_arc, VOLT_MIN_X10, VOLT_MAX_X10);
|
||||
lv_arc_set_value(volt_arc, VOLT_MIN_X10);
|
||||
lv_obj_remove_style(volt_arc, NULL, LV_PART_KNOB);
|
||||
lv_obj_remove_flag(volt_arc, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_arc_width(volt_arc, 12, LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_width(volt_arc, 12, LV_PART_INDICATOR);
|
||||
lv_obj_set_style_arc_color(volt_arc, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
|
||||
lv_obj_set_style_arc_opa(volt_arc, LV_OPA_30, LV_PART_MAIN);
|
||||
|
||||
volt_label = lv_label_create(tile_speed);
|
||||
lv_obj_set_style_text_font(volt_label, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(volt_label, COLOR_DIM, 0);
|
||||
lv_label_set_text(volt_label, "-- V");
|
||||
lv_obj_align(volt_label, LV_ALIGN_BOTTOM_MID, 0, -30);
|
||||
|
||||
// speed number with a soft drop shadow so it stays readable on the GIF
|
||||
speed_shadow = lv_label_create(tile_speed);
|
||||
lv_obj_set_style_text_font(speed_shadow, &font_speed_120, 0);
|
||||
@@ -171,7 +231,7 @@ static void build_speed_tile()
|
||||
lv_obj_set_style_pad_hor(status_label, 10, 0);
|
||||
lv_obj_set_style_pad_ver(status_label, 3, 0);
|
||||
lv_label_set_text(status_label, "");
|
||||
lv_obj_align(status_label, LV_ALIGN_BOTTOM_MID, 0, -34);
|
||||
lv_obj_align(status_label, LV_ALIGN_CENTER, 0, 100);
|
||||
}
|
||||
|
||||
// ---------- menu page ----------
|
||||
@@ -220,14 +280,14 @@ static void build_settings_tile()
|
||||
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(label, COLOR_DIM, 0);
|
||||
lv_label_set_text(label, "Screen rotation (degrees)");
|
||||
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 86);
|
||||
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 54);
|
||||
|
||||
static const char *names[4] = {"0", "90", "180", "270"};
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
lv_obj_t *b = lv_button_create(tile_settings);
|
||||
lv_obj_set_size(b, 104, 48);
|
||||
lv_obj_align(b, LV_ALIGN_TOP_MID, (i % 2) ? 58 : -58, 118 + (i / 2) * 60);
|
||||
lv_obj_set_size(b, 96, 40);
|
||||
lv_obj_align(b, LV_ALIGN_TOP_MID, (i % 2) ? 52 : -52, 80 + (i / 2) * 46);
|
||||
lv_obj_add_event_cb(b, on_rotation_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
|
||||
lv_obj_t *l = lv_label_create(b);
|
||||
lv_label_set_text(l, names[i]);
|
||||
@@ -237,6 +297,27 @@ static void build_settings_tile()
|
||||
}
|
||||
highlight_rotation(getRotation());
|
||||
|
||||
lv_obj_t *idle_label = lv_label_create(tile_settings);
|
||||
lv_obj_set_style_text_font(idle_label, &lv_font_montserrat_16, 0);
|
||||
lv_obj_set_style_text_color(idle_label, COLOR_DIM, 0);
|
||||
lv_label_set_text(idle_label, "Screen off without data (min)");
|
||||
lv_obj_align(idle_label, LV_ALIGN_TOP_MID, 0, 180);
|
||||
|
||||
static const char *idle_names[4] = {"Off", "2", "5", "10"};
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
lv_obj_t *b = lv_button_create(tile_settings);
|
||||
lv_obj_set_size(b, 58, 40);
|
||||
lv_obj_align(b, LV_ALIGN_TOP_MID, -99 + i * 66, 206);
|
||||
lv_obj_add_event_cb(b, on_idle_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
|
||||
lv_obj_t *l = lv_label_create(b);
|
||||
lv_label_set_text(l, idle_names[i]);
|
||||
lv_obj_set_style_text_font(l, &lv_font_montserrat_16, 0);
|
||||
lv_obj_center(l);
|
||||
idle_buttons[i] = b;
|
||||
}
|
||||
highlight_idle(load_idle_minutes());
|
||||
|
||||
make_button(tile_settings, "Done", 0, 296, 110, on_done_clicked);
|
||||
}
|
||||
|
||||
@@ -253,9 +334,74 @@ static void build_ui()
|
||||
build_speed_tile();
|
||||
build_menu_tile();
|
||||
build_settings_tile();
|
||||
|
||||
idle_overlay = lv_obj_create(lv_layer_top());
|
||||
lv_obj_remove_style_all(idle_overlay);
|
||||
lv_obj_set_size(idle_overlay, SCREEN_RES_HOR, SCREEN_RES_VER);
|
||||
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(idle_overlay, on_idle_overlay_pressed, LV_EVENT_PRESSED, NULL);
|
||||
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
|
||||
// ---------- periodic updates ----------
|
||||
static lv_color_t voltage_color(float v)
|
||||
{
|
||||
if (v < 0)
|
||||
return COLOR_DIM;
|
||||
if (v < 11.8f)
|
||||
return lv_color_hex(0xFF3B30); // red: battery low
|
||||
if (v < 12.2f)
|
||||
return lv_color_hex(0xFF9500); // orange: getting weak
|
||||
if (v < 13.0f)
|
||||
return lv_color_hex(0x4CD964); // green: resting, OK
|
||||
if (v <= 14.8f)
|
||||
return lv_color_hex(0x0A84FF); // blue: charging (engine running)
|
||||
return lv_color_hex(0xFF3B30); // red: overcharging
|
||||
}
|
||||
|
||||
static void update_voltage()
|
||||
{
|
||||
static int shown_x10 = -2;
|
||||
float v = obd_voltage();
|
||||
int x10 = v < 0 ? -1 : (int)(v * 10 + 0.5f);
|
||||
if (x10 == shown_x10)
|
||||
return;
|
||||
shown_x10 = x10;
|
||||
lv_color_t c = voltage_color(v);
|
||||
lv_arc_set_value(volt_arc, x10 < 0 ? VOLT_MIN_X10 : constrain(x10, VOLT_MIN_X10, VOLT_MAX_X10));
|
||||
lv_obj_set_style_arc_color(volt_arc, c, LV_PART_INDICATOR);
|
||||
char buf[12];
|
||||
if (x10 < 0)
|
||||
strcpy(buf, "-- V");
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "%d.%d V", x10 / 10, x10 % 10);
|
||||
lv_label_set_text(volt_label, buf);
|
||||
lv_obj_set_style_text_color(volt_label, c, 0);
|
||||
}
|
||||
|
||||
static void update_idle()
|
||||
{
|
||||
static bool shown_idle = false;
|
||||
bool idle = obd_is_idle();
|
||||
if (idle == shown_idle)
|
||||
return;
|
||||
shown_idle = idle;
|
||||
if (idle)
|
||||
{
|
||||
// screen off, animation paused, touches only wake up
|
||||
lv_tileview_set_tile(tileview, tile_speed, LV_ANIM_OFF);
|
||||
lv_gif_pause(bg_gif);
|
||||
lv_obj_remove_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
|
||||
screen_switch(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
lv_gif_resume(bg_gif);
|
||||
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
|
||||
screen_switch(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void update_speed()
|
||||
{
|
||||
static int shown = -2;
|
||||
@@ -345,6 +491,7 @@ void setup()
|
||||
scr_lvgl_init();
|
||||
setRotation(load_rotation());
|
||||
build_ui();
|
||||
obd_set_idle_timeout(load_idle_minutes());
|
||||
obd_begin();
|
||||
if (obd_saved_address().length() == 0)
|
||||
{
|
||||
@@ -363,6 +510,8 @@ void loop()
|
||||
{
|
||||
last_speed = now;
|
||||
update_speed();
|
||||
update_voltage();
|
||||
update_idle();
|
||||
}
|
||||
if (now - last_menu >= 500 && menu_visible())
|
||||
{
|
||||
|
||||
@@ -4,12 +4,20 @@
|
||||
#include <Preferences.h>
|
||||
|
||||
#define RETRY_DELAY_MS 3000
|
||||
#define IDLE_RETRY_DELAY_MS 30000
|
||||
#define POLL_FAIL_LIMIT 3
|
||||
#define VOLTAGE_INTERVAL_MS 2000 // ATRV while driving
|
||||
#define IDLE_VOLTAGE_INTERVAL_MS 30000 // ATRV while idle (adapter-internal, doesn't wake the car's bus)
|
||||
#define WAKE_VOLTAGE 13.2f // above this the alternator is charging: engine running
|
||||
|
||||
// ---------- shared state (protected by `mtx`) ----------
|
||||
static SemaphoreHandle_t mtx;
|
||||
static volatile ObdState state = ObdState::NoDevice;
|
||||
static volatile int speed_kmh = -1;
|
||||
static volatile float voltage = -1;
|
||||
static volatile bool idle = false;
|
||||
static volatile uint32_t idle_timeout_ms = 2 * 60 * 1000;
|
||||
static volatile uint32_t last_data_ms = 0; // last valid speed (or wake-up)
|
||||
static String status_text = "starting";
|
||||
static String saved_addr, saved_name;
|
||||
static uint8_t saved_type = 0;
|
||||
@@ -18,7 +26,7 @@ static uint32_t devices_version = 1;
|
||||
static String elm_rx; // bytes received from the ELM (notifications)
|
||||
|
||||
// ---------- requests from the UI ----------
|
||||
static volatile bool req_select = false, req_forget = false;
|
||||
static volatile bool req_select = false, req_forget = false, req_wake = false;
|
||||
static volatile bool scan_wanted = false;
|
||||
static ObdDevInfo req_dev;
|
||||
|
||||
@@ -155,6 +163,23 @@ static int parse_speed(String reply)
|
||||
return (int)strtol(reply.substring(i + 4, i + 6).c_str(), nullptr, 16);
|
||||
}
|
||||
|
||||
// "12.6V" -> 12.6, -1 if not a voltage
|
||||
static float parse_voltage(const String &reply)
|
||||
{
|
||||
int v = reply.indexOf('V');
|
||||
if (v <= 0)
|
||||
return -1;
|
||||
float f = reply.substring(0, v).toFloat();
|
||||
return f > 0 ? f : -1;
|
||||
}
|
||||
|
||||
static void read_voltage()
|
||||
{
|
||||
float v = parse_voltage(elm_command("ATRV", 1500));
|
||||
if (v > 0)
|
||||
voltage = v;
|
||||
}
|
||||
|
||||
// ---------- connection ----------
|
||||
static void disconnect()
|
||||
{
|
||||
@@ -259,9 +284,28 @@ static void load_saved()
|
||||
static void worker_task(void *)
|
||||
{
|
||||
uint32_t next_attempt = 0;
|
||||
uint32_t next_voltage = 0;
|
||||
int fails = 0;
|
||||
last_data_ms = millis();
|
||||
for (;;)
|
||||
{
|
||||
if (req_wake)
|
||||
{
|
||||
req_wake = false;
|
||||
if (idle)
|
||||
Serial.println("[obd] wake (touch)");
|
||||
idle = false;
|
||||
last_data_ms = millis();
|
||||
next_attempt = 0;
|
||||
}
|
||||
// no speed data for a while (ignition off / adapter gone): go idle
|
||||
if (!idle && idle_timeout_ms && millis() - last_data_ms > idle_timeout_ms)
|
||||
{
|
||||
idle = true;
|
||||
speed_kmh = -1;
|
||||
Serial.println("[obd] idle");
|
||||
}
|
||||
|
||||
// requests from the UI
|
||||
if (req_forget)
|
||||
{
|
||||
@@ -296,6 +340,8 @@ static void worker_task(void *)
|
||||
if (saved_addr.length() == 0)
|
||||
{
|
||||
state = ObdState::NoDevice;
|
||||
idle = false;
|
||||
last_data_ms = millis(); // no idle while no adapter is chosen (user is setting up)
|
||||
set_status("No adapter - swipe left");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
@@ -317,11 +363,36 @@ static void worker_task(void *)
|
||||
{
|
||||
disconnect();
|
||||
state = ObdState::Waiting;
|
||||
next_attempt = millis() + RETRY_DELAY_MS;
|
||||
next_attempt = millis() + (idle ? IDLE_RETRY_DELAY_MS : RETRY_DELAY_MS);
|
||||
}
|
||||
next_voltage = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idle)
|
||||
{
|
||||
// only the battery voltage now and then; an engine start (charging voltage) wakes us up
|
||||
if ((int32_t)(millis() - next_voltage) >= 0)
|
||||
{
|
||||
next_voltage = millis() + IDLE_VOLTAGE_INTERVAL_MS;
|
||||
read_voltage();
|
||||
if (voltage > WAKE_VOLTAGE)
|
||||
{
|
||||
idle = false;
|
||||
last_data_ms = millis();
|
||||
Serial.printf("[obd] wake (%.1f V)\n", voltage);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((int32_t)(millis() - next_voltage) >= 0)
|
||||
{
|
||||
next_voltage = millis() + VOLTAGE_INTERVAL_MS;
|
||||
read_voltage();
|
||||
}
|
||||
|
||||
// connected: poll the vehicle speed
|
||||
String reply = elm_command("010D", 1500);
|
||||
int v = parse_speed(reply);
|
||||
@@ -329,6 +400,7 @@ static void worker_task(void *)
|
||||
if (v >= 0)
|
||||
{
|
||||
fails = 0;
|
||||
last_data_ms = millis();
|
||||
set_status(saved_name.length() ? saved_name : saved_addr);
|
||||
}
|
||||
else if (reply.length() == 0)
|
||||
@@ -365,6 +437,10 @@ void obd_begin()
|
||||
|
||||
ObdState obd_state() { return state; }
|
||||
int obd_speed() { return speed_kmh; }
|
||||
float obd_voltage() { return voltage; }
|
||||
bool obd_is_idle() { return idle; }
|
||||
void obd_wake() { req_wake = true; }
|
||||
void obd_set_idle_timeout(uint32_t minutes) { idle_timeout_ms = minutes * 60 * 1000; }
|
||||
|
||||
String obd_status_text()
|
||||
{
|
||||
|
||||
@@ -28,6 +28,13 @@ void obd_begin();
|
||||
ObdState obd_state();
|
||||
int obd_speed(); // km/h, -1 = no data (ignition off / not connected)
|
||||
String obd_status_text(); // short human-readable status line
|
||||
float obd_voltage(); // battery voltage from ATRV (V), -1 = unknown
|
||||
|
||||
// Idle: after `minutes` without speed data (0 = never) polling stops; only ATRV every 30 s.
|
||||
// Leaves idle by itself when the voltage shows the engine running (> 13.2 V), or via obd_wake().
|
||||
void obd_set_idle_timeout(uint32_t minutes);
|
||||
bool obd_is_idle();
|
||||
void obd_wake();
|
||||
String obd_saved_name(); // "" if none
|
||||
String obd_saved_address();
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ Display color format is `RGB565_SWAPPED` (ST77916 byte order, set in `lv_conf.h`
|
||||
| `1-Instructions` | Spec PDF (duplicate), AIDA64 remote-sensor layout `aida_remote_1.85.rslcd`, MJPEG conversion tool (Windows .NET, converts video to 360×360 MJPEG 25 fps) |
|
||||
| `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` |
|
||||
| `12-OBD-Speed` | **The new program — working in the car (2026-10-23):** speed from the ELM327 over BLE as big number + arc gauge on a GIF background; swipe left for the adapter menu (scan, select → remembered in NVS, auto-reconnect every 3 s). See its `README.md` |
|
||||
| `12-OBD-Speed` | **The new program — working in the car (2026-10-23):** speed from the ELM327 over BLE as big number + arc gauge on a GIF background, battery-voltage arc in the bottom gap; swipe left for the adapter menu (scan, select → remembered in NVS, auto-reconnect every 3 s), again for settings (rotation, idle/screen-off timeout). See its `README.md` |
|
||||
| `10-GIF-Tests` | **GIF playback experiments** (was `10-OBD-Display-2024`): `display_gif_embedded`, `display_gif_sd`, `assets/gifs`. The 2024 bridge/ELMulator/WiFi speed sketch, the broken LVGL 9 archive and the BLE scanner tool (`11-BLE-Scanner`: on-screen scan, GATT listing, ELM test, LittleFS log) were removed 2026-10-23 — all in git history |
|
||||
| `3-Specification` | Spec sheet (factory firmware features, interface photo) |
|
||||
| `4-Chip manual` | ESP32-S3R8 datasheet, PCM5100A datasheet |
|
||||
@@ -93,6 +93,9 @@ Display color format is `RGB565_SWAPPED` (ST77916 byte order, set in `lv_conf.h`
|
||||
AIDA64 secondary screen (18 styles), mic spectrum, MP3 player (`/music` on SD), photo frame (`/pic`, 360×360 JPEG), MJPEG player (`/mjpeg`), weather, theme clocks, WiFi provisioning AP "My App"/12345678 + web config at device IP.
|
||||
Re-flash it with Flash Download Tool: ESP32-S3, `1.85_demo.bin` @ 0x0.
|
||||
|
||||
## Car battery / idle
|
||||
Cheap ELM327 clones draw ~20–50 mA permanently (OBD pin 16 = always 12 V); the display ~180 mA if its USB stays powered. `12-OBD-Speed` idle mode stops OBD polling after N min without data so the car's ECUs can sleep; `ATRV` (adapter-internal) every 30 s detects an engine start (> 13.2 V).
|
||||
|
||||
## OBD adapter (user's)
|
||||
AliExpress "ELM327 Mini V2.1 Bluetooth OBD **Dual Mode** 5.1" — dual mode = Bluetooth Classic SPP **and BLE**, so the ESP32-S3 should be able to connect directly over BLE (no bridge ESP32).
|
||||
**Verified in the car (2026-10-23, with the since-removed BLE scanner tool; log in `12-OBD-Speed/docs/2026-10-23_ble_scanner_car_test.txt`):**
|
||||
@@ -123,5 +126,6 @@ AliExpress "ELM327 Mini V2.1 Bluetooth OBD **Dual Mode** 5.1" — dual mode = Bl
|
||||
- BLE (NimBLE) in the user's home: ~12 devices visible within 10 s.
|
||||
- Big fonts: generate with `npx lv_font_conv` (Node 22 installed) from `lvgl/scripts/generators/built_in_font/Montserrat-Medium.ttf`; `-DLV_LVGL_H_INCLUDE_SIMPLE` (in shared ini) makes the generated `#include` work.
|
||||
- Binary assets (GIFs): `board_build.embed_files = data/x.gif` → `extern const uint8_t x_start[] asm("_binary_data_x_gif_start")` (no huge C arrays).
|
||||
- Serial output right after a reset can be lost/garbled (USB-CDC re-enumeration, prints from two cores interleave) — don't conclude a task isn't running from a missing early line.
|
||||
- Python edit scripts: never pass C strings containing `\n` through a bash heredoc into Python — it turned into a real line break twice; write the script with the Write tool instead.
|
||||
- Serial output from the background task (core 0) is often lost, not only right after reset; don't rely on it as proof. Serial output right after a reset can be lost/garbled (USB-CDC re-enumeration, prints from two cores interleave) — don't conclude a task isn't running from a missing early line.
|
||||
- `Preferences` prints `nvs_open failed: NOT_FOUND` the first time a namespace is read before it was written — harmless.
|
||||
Reference in new issue
Block a user