diff --git a/10-OBD-Display-2024/README.md b/10-GIF-Tests/README.md similarity index 53% rename from 10-OBD-Display-2024/README.md rename to 10-GIF-Tests/README.md index 32948c0..a77796c 100644 --- a/10-OBD-Display-2024/README.md +++ b/10-GIF-Tests/README.md @@ -1,17 +1,14 @@ -# OBD Display (2024 experiments) +# GIF tests -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`. +GIF playback experiments on the JC3636W518 (originally part of the 2024 OBD display attempt; renamed 2026-10-23). +Used to find the fastest GIF setup — results are in `../shared/README.md` and `../CLAUDE.md`. ## 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_embedded/` | `loligod.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). 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 ``` diff --git a/10-OBD-Display-2024/assets/gifs/dump/35302.gif b/10-GIF-Tests/assets/gifs/dump/35302.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/35302.gif rename to 10-GIF-Tests/assets/gifs/dump/35302.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/401886a3a5d01a2168c933a3a7f8dabb.gif b/10-GIF-Tests/assets/gifs/dump/401886a3a5d01a2168c933a3a7f8dabb.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/401886a3a5d01a2168c933a3a7f8dabb.gif rename to 10-GIF-Tests/assets/gifs/dump/401886a3a5d01a2168c933a3a7f8dabb.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/59291146fc51ddaadf559a8ad69e1970.gif b/10-GIF-Tests/assets/gifs/dump/59291146fc51ddaadf559a8ad69e1970.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/59291146fc51ddaadf559a8ad69e1970.gif rename to 10-GIF-Tests/assets/gifs/dump/59291146fc51ddaadf559a8ad69e1970.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/SjwVIVm.gif b/10-GIF-Tests/assets/gifs/dump/SjwVIVm.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/SjwVIVm.gif rename to 10-GIF-Tests/assets/gifs/dump/SjwVIVm.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/dcyqs4k-f637a987-4c88-4495-b21d-817ce801dc4b.gif b/10-GIF-Tests/assets/gifs/dump/dcyqs4k-f637a987-4c88-4495-b21d-817ce801dc4b.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/dcyqs4k-f637a987-4c88-4495-b21d-817ce801dc4b.gif rename to 10-GIF-Tests/assets/gifs/dump/dcyqs4k-f637a987-4c88-4495-b21d-817ce801dc4b.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/shigure-goddess.gif b/10-GIF-Tests/assets/gifs/dump/shigure-goddess.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/shigure-goddess.gif rename to 10-GIF-Tests/assets/gifs/dump/shigure-goddess.gif diff --git a/10-OBD-Display-2024/assets/gifs/dump/steamuserimages-a.akamaihd.gif b/10-GIF-Tests/assets/gifs/dump/steamuserimages-a.akamaihd.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/dump/steamuserimages-a.akamaihd.gif rename to 10-GIF-Tests/assets/gifs/dump/steamuserimages-a.akamaihd.gif diff --git a/10-OBD-Display-2024/assets/gifs/ezgif-5-cb56986bc7.gif b/10-GIF-Tests/assets/gifs/ezgif-5-cb56986bc7.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/ezgif-5-cb56986bc7.gif rename to 10-GIF-Tests/assets/gifs/ezgif-5-cb56986bc7.gif diff --git a/10-OBD-Display-2024/assets/gifs/ezgif-6-48e5b7d11c.gif b/10-GIF-Tests/assets/gifs/ezgif-6-48e5b7d11c.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/ezgif-6-48e5b7d11c.gif rename to 10-GIF-Tests/assets/gifs/ezgif-6-48e5b7d11c.gif diff --git a/10-OBD-Display-2024/assets/gifs/loligod.gif b/10-GIF-Tests/assets/gifs/loligod.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/loligod.gif rename to 10-GIF-Tests/assets/gifs/loligod.gif diff --git a/10-OBD-Display-2024/assets/gifs/megumin.gif b/10-GIF-Tests/assets/gifs/megumin.gif similarity index 100% rename from 10-OBD-Display-2024/assets/gifs/megumin.gif rename to 10-GIF-Tests/assets/gifs/megumin.gif diff --git a/10-OBD-Display-2024/display_gif_embedded/platformio.ini b/10-GIF-Tests/display_gif_embedded/platformio.ini similarity index 100% rename from 10-OBD-Display-2024/display_gif_embedded/platformio.ini rename to 10-GIF-Tests/display_gif_embedded/platformio.ini diff --git a/10-OBD-Display-2024/display_gif_embedded/src/loligod.c b/10-GIF-Tests/display_gif_embedded/src/loligod.c similarity index 100% rename from 10-OBD-Display-2024/display_gif_embedded/src/loligod.c rename to 10-GIF-Tests/display_gif_embedded/src/loligod.c diff --git a/10-OBD-Display-2024/display_gif_embedded/src/main.cpp b/10-GIF-Tests/display_gif_embedded/src/main.cpp similarity index 100% rename from 10-OBD-Display-2024/display_gif_embedded/src/main.cpp rename to 10-GIF-Tests/display_gif_embedded/src/main.cpp diff --git a/10-OBD-Display-2024/display_gif_embedded/src/montserrat_100.c b/10-GIF-Tests/display_gif_embedded/src/montserrat_100.c similarity index 100% rename from 10-OBD-Display-2024/display_gif_embedded/src/montserrat_100.c rename to 10-GIF-Tests/display_gif_embedded/src/montserrat_100.c diff --git a/10-OBD-Display-2024/display_gif_sd/platformio.ini b/10-GIF-Tests/display_gif_sd/platformio.ini similarity index 100% rename from 10-OBD-Display-2024/display_gif_sd/platformio.ini rename to 10-GIF-Tests/display_gif_sd/platformio.ini diff --git a/10-OBD-Display-2024/display_gif_sd/src/main.cpp b/10-GIF-Tests/display_gif_sd/src/main.cpp similarity index 100% rename from 10-OBD-Display-2024/display_gif_sd/src/main.cpp rename to 10-GIF-Tests/display_gif_sd/src/main.cpp diff --git a/11-BLE-Scanner/README.md b/11-BLE-Scanner/README.md deleted file mode 100644 index 135a7fd..0000000 --- a/11-BLE-Scanner/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# BLE Scanner (JC3636W518) - -Stand-alone BLE scanner with on-screen UI, made to find out how the user's ELM327 ("ELM327 Mini V2.1 Dual Mode 5.1") talks over BLE. -Uses the shared board library and LVGL 9.6 (`../shared`), NimBLE-Arduino 2.5.1. - -## On the device -1. **Scan screen** — live list of BLE devices: name, MAC, signal (dBm). Devices that look like an OBD adapter (name contains OBD/ELM/VLINK/…, or advertises service FFF0/FFE0/18F0) are shown in **green** with `OBD?`. `(nc)` = not connectable. - - **Pause / Scan** — stop/resume scanning. **Save** — write the current list to flash. -2. **Tap a device** — connects and lists all services and characteristics with their properties - (`R` read, `W` write, `WnR` write without response, `N` notify, `I` indicate). - If a service has a notify + write pair (ELM-style UART), it is shown as **ELM candidate** and **ELM test** becomes active. -3. **ELM test** — sends `ATZ`, `ATE0`, `ATI`, `ATRV` (battery voltage) and `0100` (supported PIDs, needs ignition on) and shows the replies. -4. **Back** — disconnect and return to scanning. - -Every inspect and ELM test is saved automatically (together with the scan list) to `/ble_log.txt` in internal flash (LittleFS, ~900 KB). - -## Reading the results later (USB, serial 115200) -Send a single character: -- `d` — dump `/ble_log.txt` -- `c` — clear the log -- `s` — print the current device list - -e.g. `pio device monitor` in this folder, then type `d`. - -## Build / flash -``` -pio run -t upload -``` -Note: the ELM327 is only powered when plugged into the car's OBD port. diff --git a/11-BLE-Scanner/platformio.ini b/11-BLE-Scanner/platformio.ini deleted file mode 100644 index 4ea5ef6..0000000 --- a/11-BLE-Scanner/platformio.ini +++ /dev/null @@ -1,13 +0,0 @@ -; BLE scanner / inspector / ELM327 tester with on-screen UI — JC3636W518, common settings in shared/jc3636w518.ini - -[platformio] -extra_configs = ../shared/jc3636w518.ini - -[shared] -dir = ../shared - -[env:jc3636w518] -extends = jc3636w518 -lib_deps = - ${jc3636w518.lib_deps} - h2zero/NimBLE-Arduino@2.5.1 diff --git a/11-BLE-Scanner/src/ble_worker.cpp b/11-BLE-Scanner/src/ble_worker.cpp deleted file mode 100644 index 835ba42..0000000 --- a/11-BLE-Scanner/src/ble_worker.cpp +++ /dev/null @@ -1,436 +0,0 @@ -#include "ble_worker.h" - -#include - -#include "result_log.h" - -// ---------- shared state (protected by `mtx`) ---------- -static SemaphoreHandle_t mtx; -static std::vector devices; -static uint32_t devices_version = 1; -static String text; -static uint32_t text_version = 1; -static volatile BleState state = BleState::Scanning; -static volatile bool elm_candidate = false; - -// ---------- requests from the UI to the worker task ---------- -enum class Req -{ - None, - Inspect, - ElmTest, - Back, - Pause, - Resume, -}; -static volatile Req request = Req::None; -static BleDeviceInfo request_dev; - -// ---------- worker-owned BLE objects ---------- -static NimBLEClient *client = nullptr; -static NimBLERemoteCharacteristic *elm_write = nullptr; -static NimBLERemoteCharacteristic *elm_notify = nullptr; -static String elm_rx; // notifications received from the ELM (guarded by mtx) - -static void lock() { xSemaphoreTake(mtx, portMAX_DELAY); } -static void unlock() { xSemaphoreGive(mtx); } - -static void set_text(const String &t) -{ - lock(); - text = t; - text_version++; - unlock(); -} - -static void add_text(const String &t) -{ - lock(); - text += t; - text_version++; - unlock(); -} - -static String upper(String s) -{ - s.toUpperCase(); - return s; -} - -// Services that cheap BLE OBD adapters typically use for their serial (UART-like) link -static const uint16_t ELM_SERVICE_HINTS[] = {0xFFF0, 0xFFE0, 0x18F0}; - -static bool name_looks_like_obd(const String &name) -{ - String n = upper(name); - return n.indexOf("OBD") >= 0 || n.indexOf("ELM") >= 0 || n.indexOf("VLINK") >= 0 || - n.indexOf("V-LINK") >= 0 || n.indexOf("VEEPEAK") >= 0 || n.indexOf("VGATE") >= 0; -} - -// ---------- scanning ---------- -class ScanCallbacks : public NimBLEScanCallbacks -{ - void onResult(const NimBLEAdvertisedDevice *adv) override - { - String addr = adv->getAddress().toString().c_str(); - String name = adv->haveName() ? String(adv->getName().c_str()) : String(); - String svcs; - bool hint = name_looks_like_obd(name); - for (uint8_t i = 0; i < adv->getServiceUUIDCount(); i++) - { - NimBLEUUID uuid = adv->getServiceUUID(i); - if (svcs.length()) - svcs += ","; - svcs += uuid.toString().c_str(); - for (uint16_t h : ELM_SERVICE_HINTS) - if (uuid.equals(NimBLEUUID(h))) - hint = true; - } - - lock(); - BleDeviceInfo *d = nullptr; - for (auto &e : devices) - if (e.address == addr) - d = &e; - if (!d) - { - devices.push_back(BleDeviceInfo()); - d = &devices.back(); - d->address = addr; - } - d->addr_type = adv->getAddress().getType(); - if (name.length()) - d->name = name; // keep a name once seen (scan responses don't always repeat it) - if (svcs.length()) - d->services = svcs; - d->rssi = adv->getRSSI(); - d->elm_hint = d->elm_hint || hint; - d->connectable = adv->isConnectable(); - devices_version++; - unlock(); - } -}; - -static ScanCallbacks scan_callbacks; - -static void scan_start() -{ - NimBLEScan *scan = NimBLEDevice::getScan(); - scan->setScanCallbacks(&scan_callbacks, true); // want duplicates: keeps RSSI live - scan->setActiveScan(true); // ask for scan responses (names) - scan->setInterval(100); - scan->setWindow(90); - scan->start(0, false, true); // 0 = scan until stopped - state = BleState::Scanning; -} - -static void scan_stop() -{ - NimBLEDevice::getScan()->stop(); -} - -// ---------- inspect ---------- -static String props(const NimBLERemoteCharacteristic *c) -{ - String p; - if (c->canRead()) - p += "R "; - if (c->canWrite()) - p += "W "; - if (c->canWriteNoResponse()) - p += "WnR "; - if (c->canNotify()) - p += "N "; - if (c->canIndicate()) - p += "I "; - p.trim(); - return p; -} - -static void disconnect_client() -{ - elm_candidate = false; - elm_write = nullptr; - elm_notify = nullptr; - if (client) - { - if (client->isConnected()) - client->disconnect(); - NimBLEDevice::deleteClient(client); - client = nullptr; - } -} - -static void do_inspect(const BleDeviceInfo &dev) -{ - scan_stop(); - state = BleState::Connecting; - String title = dev.name.length() ? dev.name : String("(no name)"); - set_text(title + "\n" + dev.address + " " + dev.rssi + " dBm\nConnecting...\n"); - - client = NimBLEDevice::createClient(); - client->setConnectTimeout(10000); - NimBLEAddress addr(std::string(dev.address.c_str()), dev.addr_type); - if (!client->connect(addr)) - { - add_text(String("Connect FAILED (error ") + client->getLastError() + ")\n"); - log_append("Inspect " + dev.address, title + "\nconnect failed, error " + client->getLastError()); - disconnect_client(); - state = BleState::Failed; - return; - } - add_text(String("Connected, MTU ") + client->getMTU() + "\n"); - - // list services and characteristics, and look for an ELM-style UART pair (notify + write) - String out; - int best_score = -1; - const std::vector &svcs = client->getServices(true); - for (NimBLERemoteService *s : svcs) - { - out += String("\nSVC ") + s->getUUID().toString().c_str() + "\n"; - NimBLERemoteCharacteristic *w = nullptr, *n = nullptr; - for (NimBLERemoteCharacteristic *c : s->getCharacteristics(true)) - { - out += String(" ") + c->getUUID().toString().c_str() + " [" + props(c) + "]\n"; - if (!n && (c->canNotify() || c->canIndicate())) - n = c; - if (!w && (c->canWrite() || c->canWriteNoResponse())) - w = c; - } - if (w && n) - { - int score = 1; - for (uint16_t h : ELM_SERVICE_HINTS) - if (s->getUUID().equals(NimBLEUUID(h))) - score = 2; - if (score > best_score) - { - best_score = score; - elm_write = w; - elm_notify = n; - } - } - } - if (elm_write && elm_notify) - { - elm_candidate = true; - out += String("\nELM candidate:\n write ") + elm_write->getUUID().toString().c_str() + - "\n notify " + elm_notify->getUUID().toString().c_str() + "\n"; - } - else - { - out += "\nNo notify+write pair found (not an ELM-style UART).\n"; - } - add_text(out); - state = BleState::Connected; - - log_append("Scan list", ble_devices_as_text()); - log_append("Inspect " + dev.address, title + " " + dev.rssi + " dBm\n" + out); -} - -// ---------- ELM327 test ---------- -static void on_elm_notify(NimBLERemoteCharacteristic *c, uint8_t *data, size_t len, bool is_notify) -{ - lock(); - for (size_t i = 0; i < len; i++) - elm_rx += (char)data[i]; - unlock(); -} - -// Sends one command and waits for the ELM prompt '>' (or a timeout). Returns the reply. -static String elm_command(const char *cmd, uint32_t timeout_ms) -{ - lock(); - elm_rx = ""; - unlock(); - String line = String(cmd) + "\r"; - bool with_response = !elm_write->canWriteNoResponse(); // prefer write-without-response if offered - if (!elm_write->writeValue((const uint8_t *)line.c_str(), line.length(), with_response)) - return "(write failed)"; - - uint32_t t0 = millis(); - String rx; - while (millis() - t0 < timeout_ms) - { - lock(); - rx = elm_rx; - unlock(); - if (rx.indexOf('>') >= 0) - break; - vTaskDelay(pdMS_TO_TICKS(20)); - } - if (rx.indexOf('>') < 0) - rx += " (timeout)"; - // tidy: CR -> newline, drop prompt - rx.replace("\r", "\n"); - rx.replace(">", ""); - while (rx.indexOf("\n\n") >= 0) - rx.replace("\n\n", "\n"); - rx.trim(); - return rx; -} - -static void do_elm_test() -{ - if (!client || !client->isConnected() || !elm_write || !elm_notify) - { - add_text("\nELM test: not connected / no candidate\n"); - return; - } - state = BleState::ElmTesting; - add_text("\n--- ELM327 test ---\n"); - if (!elm_notify->subscribe(elm_notify->canNotify(), on_elm_notify)) - { - add_text("subscribe FAILED\n"); - state = BleState::Connected; - return; - } - struct - { - const char *cmd; - uint32_t timeout; - const char *what; - } cmds[] = { - {"ATZ", 4000, "reset"}, - {"ATE0", 2000, "echo off"}, - {"ATI", 2000, "version"}, - {"ATRV", 2000, "battery voltage"}, - {"0100", 8000, "supported PIDs (needs ignition on)"}, - }; - String log_text; - for (auto &c : cmds) - { - if (!client->isConnected()) - { - add_text("disconnected!\n"); - break; - } - String reply = elm_command(c.cmd, c.timeout); - String block = String("> ") + c.cmd + " (" + c.what + ")\n" + reply + "\n"; - add_text(block); - log_text += block; - } - elm_notify->unsubscribe(); - add_text("--- done ---\n"); - log_append("ELM test " + String(client->getPeerAddress().toString().c_str()), log_text); - state = BleState::Connected; -} - -// ---------- worker task ---------- -static void worker_task(void *) -{ - for (;;) - { - Req r = request; - if (r != Req::None) - { - request = Req::None; - switch (r) - { - case Req::Inspect: - do_inspect(request_dev); - break; - case Req::ElmTest: - do_elm_test(); - break; - case Req::Back: - disconnect_client(); - set_text(""); - scan_start(); - break; - case Req::Pause: - if (state == BleState::Scanning) - { - scan_stop(); - state = BleState::Paused; - } - break; - case Req::Resume: - if (state == BleState::Paused) - scan_start(); - break; - default: - break; - } - } - // a connection can drop by itself (adapter unplugged / out of range) - if (client && !client->isConnected() && (state == BleState::Connected)) - { - add_text("\nConnection lost.\n"); - state = BleState::Failed; - } - vTaskDelay(pdMS_TO_TICKS(20)); - } -} - -// ---------- public API ---------- -void ble_begin() -{ - mtx = xSemaphoreCreateMutex(); - NimBLEDevice::init("JC3636W518-scanner"); - NimBLEDevice::setPower(9); // dBm, max for ESP32-S3 - scan_start(); - // core 0: Arduino loop/LVGL run on core 1 - xTaskCreatePinnedToCore(worker_task, "ble_worker", 8192, nullptr, 2, nullptr, 0); -} - -void ble_scan_pause(bool pause) { request = pause ? Req::Pause : Req::Resume; } - -bool ble_get_devices(std::vector &out, uint32_t &version) -{ - lock(); - bool changed = version != devices_version; - if (changed) - { - out = devices; - version = devices_version; - } - unlock(); - return changed; -} - -void ble_request_inspect(const BleDeviceInfo &dev) -{ - request_dev = dev; - request = Req::Inspect; -} - -void ble_request_elm_test() { request = Req::ElmTest; } - -void ble_request_back() { request = Req::Back; } - -BleState ble_state() { return state; } - -bool ble_has_elm_candidate() { return elm_candidate; } - -bool ble_get_text(String &out, uint32_t &version) -{ - lock(); - bool changed = version != text_version; - if (changed) - { - out = text; - version = text_version; - } - unlock(); - return changed; -} - -String ble_devices_as_text() -{ - String s; - lock(); - for (auto &d : devices) - { - s += d.address + " " + d.rssi + " dBm " + (d.name.length() ? d.name : String("(no name)")); - if (d.elm_hint) - s += " [OBD?]"; - if (!d.connectable) - s += " [not connectable]"; - if (d.services.length()) - s += " svc: " + d.services; - s += "\n"; - } - unlock(); - return s; -} diff --git a/11-BLE-Scanner/src/ble_worker.h b/11-BLE-Scanner/src/ble_worker.h deleted file mode 100644 index 29e55e0..0000000 --- a/11-BLE-Scanner/src/ble_worker.h +++ /dev/null @@ -1,53 +0,0 @@ -// BLE scanning, device inspection and ELM327 test, running in a background task. -// All functions are safe to call from the UI (loop) task. -#pragma once - -#include -#include - -struct BleDeviceInfo -{ - String address; // "aa:bb:cc:dd:ee:ff" - uint8_t addr_type; // public / random (needed to connect) - String name; // advertised name ("" if none) - String services; // advertised service UUIDs, comma separated - int rssi; // dBm - bool elm_hint; // name or advertised service looks like an OBD/ELM327 adapter - bool connectable; -}; - -enum class BleState -{ - Scanning, - Paused, - Connecting, - Connected, // inspected; services listed - ElmTesting, - Failed, // connect/discovery failed (see text) -}; - -void ble_begin(); - -// Scan control (only while not connected) -void ble_scan_pause(bool pause); - -// Copies the device list if it changed since `version` (returns true and updates `version`). -bool ble_get_devices(std::vector &out, uint32_t &version); - -// Connect to the device and list its services. -void ble_request_inspect(const BleDeviceInfo &dev); - -// Run the ELM327 command test on the connected device (needs an ELM candidate). -void ble_request_elm_test(); - -// Disconnect and resume scanning. -void ble_request_back(); - -BleState ble_state(); -bool ble_has_elm_candidate(); - -// Inspect / ELM test output text; returns true if it changed since `version`. -bool ble_get_text(String &out, uint32_t &version); - -// Plain-text snapshot of the device list (for the log). -String ble_devices_as_text(); diff --git a/11-BLE-Scanner/src/main.cpp b/11-BLE-Scanner/src/main.cpp deleted file mode 100644 index eae3d3a..0000000 --- a/11-BLE-Scanner/src/main.cpp +++ /dev/null @@ -1,239 +0,0 @@ -// BLE scanner for the JC3636W518: live device list, tap a device to list its services, -// then run an ELM327 command test. Results are saved to internal flash (dump over serial with 'd'). -#include -#include "jc3636w518.h" -#include "ble_worker.h" -#include "result_log.h" - -#define MAX_LIST_ITEMS 50 - -static lv_obj_t *scr_scan, *scan_title, *scan_status, *scan_list, *btn_pause, *btn_pause_label; -static lv_obj_t *scr_inspect, *insp_title, *insp_text, *insp_text_cont, *btn_elm; -static lv_obj_t *list_items[MAX_LIST_ITEMS]; -static int list_count = 0; - -static std::vector devs; -static uint32_t devs_version = 0; -static uint32_t text_version = 0; -static bool paused = false; -static uint32_t status_msg_until = 0; - -static const lv_color_t COLOR_BG = lv_color_hex(0x000000); -static const lv_color_t COLOR_TEXT = lv_color_hex(0xE0E0E0); -static const lv_color_t COLOR_DIM = lv_color_hex(0x8A8A8A); -static const lv_color_t COLOR_OBD = lv_color_hex(0x4CD964); - -// ---------- helpers ---------- -static lv_obj_t *make_button(lv_obj_t *parent, const char *text, int x, int y, int w, lv_event_cb_t cb) -{ - lv_obj_t *b = lv_button_create(parent); - lv_obj_set_size(b, w, 34); - lv_obj_align(b, LV_ALIGN_TOP_MID, x, y); - lv_obj_add_event_cb(b, cb, LV_EVENT_CLICKED, NULL); - lv_obj_t *l = lv_label_create(b); - lv_label_set_text(l, text); - lv_obj_set_style_text_font(l, &lv_font_montserrat_16, 0); - lv_obj_center(l); - return b; -} - -static lv_obj_t *make_screen() -{ - lv_obj_t *s = lv_obj_create(lv_screen_active()); - lv_obj_remove_style_all(s); - lv_obj_set_size(s, SCREEN_RES_HOR, SCREEN_RES_VER); - lv_obj_set_style_bg_color(s, COLOR_BG, 0); - lv_obj_set_style_bg_opa(s, LV_OPA_COVER, 0); - lv_obj_remove_flag(s, LV_OBJ_FLAG_SCROLLABLE); - return s; -} - -static void show_scan_screen(bool scan) -{ - if (scan) - { - lv_obj_remove_flag(scr_scan, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(scr_inspect, LV_OBJ_FLAG_HIDDEN); - } - else - { - lv_obj_add_flag(scr_scan, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_flag(scr_inspect, LV_OBJ_FLAG_HIDDEN); - } -} - -// ---------- event handlers ---------- -static void on_device_clicked(lv_event_t *e) -{ - int idx = (int)(intptr_t)lv_event_get_user_data(e); - if (idx < 0 || idx >= (int)devs.size()) - return; - const BleDeviceInfo &d = devs[idx]; - lv_label_set_text(insp_title, d.name.length() ? d.name.c_str() : d.address.c_str()); - lv_label_set_text(insp_text, "Connecting..."); - lv_obj_add_state(btn_elm, LV_STATE_DISABLED); - show_scan_screen(false); - ble_request_inspect(d); -} - -static void on_pause_clicked(lv_event_t *e) -{ - paused = !paused; - ble_scan_pause(paused); - lv_label_set_text(btn_pause_label, paused ? "Scan" : "Pause"); -} - -static void on_save_clicked(lv_event_t *e) -{ - log_append("Scan list (saved by button)", ble_devices_as_text()); - lv_label_set_text(scan_status, "Saved to flash"); - status_msg_until = millis() + 2000; -} - -static void on_back_clicked(lv_event_t *e) -{ - ble_request_back(); - paused = false; - lv_label_set_text(btn_pause_label, "Pause"); - show_scan_screen(true); -} - -static void on_elm_clicked(lv_event_t *e) -{ - lv_obj_add_state(btn_elm, LV_STATE_DISABLED); - ble_request_elm_test(); -} - -// ---------- UI construction ---------- -static void build_ui() -{ - lv_obj_set_style_bg_color(lv_screen_active(), COLOR_BG, 0); - - // scan screen - scr_scan = make_screen(); - scan_title = lv_label_create(scr_scan); - lv_label_set_text(scan_title, "BLE scan"); - lv_obj_set_style_text_font(scan_title, &lv_font_montserrat_20, 0); - lv_obj_set_style_text_color(scan_title, COLOR_TEXT, 0); - lv_obj_align(scan_title, LV_ALIGN_TOP_MID, 0, 20); - - scan_status = lv_label_create(scr_scan); - lv_label_set_text(scan_status, "starting..."); - lv_obj_set_style_text_color(scan_status, COLOR_DIM, 0); - lv_obj_align(scan_status, LV_ALIGN_TOP_MID, 0, 46); - - scan_list = lv_list_create(scr_scan); - lv_obj_set_size(scan_list, 270, 222); - lv_obj_align(scan_list, LV_ALIGN_TOP_MID, 0, 68); - lv_obj_set_style_bg_color(scan_list, lv_color_hex(0x101418), 0); - lv_obj_set_style_border_width(scan_list, 0, 0); - lv_obj_set_style_pad_all(scan_list, 4, 0); - - lv_obj_t *b = make_button(scr_scan, "Pause", -48, 296, 88, on_pause_clicked); - btn_pause = b; - btn_pause_label = lv_obj_get_child(b, 0); - make_button(scr_scan, "Save", 48, 296, 88, on_save_clicked); - - // inspect screen - scr_inspect = make_screen(); - insp_title = lv_label_create(scr_inspect); - lv_obj_set_width(insp_title, 220); - lv_label_set_long_mode(insp_title, LV_LABEL_LONG_MODE_DOTS); - lv_obj_set_style_text_align(insp_title, LV_TEXT_ALIGN_CENTER, 0); - lv_obj_set_style_text_font(insp_title, &lv_font_montserrat_20, 0); - lv_obj_set_style_text_color(insp_title, COLOR_TEXT, 0); - lv_obj_align(insp_title, LV_ALIGN_TOP_MID, 0, 26); - - insp_text_cont = lv_obj_create(scr_inspect); - lv_obj_set_size(insp_text_cont, 284, 228); - lv_obj_align(insp_text_cont, LV_ALIGN_TOP_MID, 0, 60); - lv_obj_set_style_bg_color(insp_text_cont, lv_color_hex(0x101418), 0); - lv_obj_set_style_border_width(insp_text_cont, 0, 0); - lv_obj_set_style_pad_all(insp_text_cont, 6, 0); - insp_text = lv_label_create(insp_text_cont); - lv_obj_set_width(insp_text, lv_pct(100)); - lv_label_set_long_mode(insp_text, LV_LABEL_LONG_MODE_WRAP); - lv_obj_set_style_text_color(insp_text, COLOR_TEXT, 0); - lv_label_set_text(insp_text, ""); - - make_button(scr_inspect, "Back", -52, 296, 92, on_back_clicked); - btn_elm = make_button(scr_inspect, "ELM test", 52, 296, 104, on_elm_clicked); - lv_obj_add_state(btn_elm, LV_STATE_DISABLED); - - show_scan_screen(true); -} - -// ---------- periodic UI updates ---------- -static void update_scan_list() -{ - if (!ble_get_devices(devs, devs_version)) - return; - int n = min((int)devs.size(), MAX_LIST_ITEMS); - for (int i = 0; i < n; i++) - { - const BleDeviceInfo &d = devs[i]; - if (i >= list_count) - { - lv_obj_t *btn = lv_list_add_button(scan_list, NULL, ""); - lv_obj_add_event_cb(btn, on_device_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i); - lv_obj_set_style_bg_color(btn, lv_color_hex(0x1C232B), 0); - lv_obj_set_style_pad_ver(btn, 6, 0); - list_items[i] = btn; - list_count = i + 1; - } - lv_obj_t *label = lv_obj_get_child_by_type(list_items[i], 0, &lv_label_class); - String line = (d.elm_hint ? "OBD? " : "") + (d.name.length() ? d.name : String("(no name)")) + - "\n" + d.address + " " + d.rssi + " dBm" + (d.connectable ? "" : " (nc)"); - if (strcmp(lv_label_get_text(label), line.c_str()) != 0) - { - lv_label_set_text(label, line.c_str()); - lv_obj_set_style_text_color(label, d.elm_hint ? COLOR_OBD : COLOR_TEXT, 0); - } - } - if (millis() > status_msg_until) - { - String status = String(devs.size()) + " devices" + (paused ? " (paused)" : " scanning"); - lv_label_set_text(scan_status, status.c_str()); - } -} - -static void update_inspect() -{ - String t; - if (ble_get_text(t, text_version)) - { - lv_label_set_text(insp_text, t.c_str()); - lv_obj_scroll_to_y(insp_text_cont, LV_COORD_MAX, LV_ANIM_OFF); // follow new output - } - bool can_test = ble_state() == BleState::Connected && ble_has_elm_candidate(); - if (can_test) - lv_obj_remove_state(btn_elm, LV_STATE_DISABLED); - else - lv_obj_add_state(btn_elm, LV_STATE_DISABLED); -} - -void setup() -{ - delay(200); - Serial.begin(115200); - scr_lvgl_init(); - build_ui(); - log_begin(); - ble_begin(); -} - -void loop() -{ - static uint32_t last_ui = 0; - lv_timer_handler(); - if (millis() - last_ui > 250) - { - last_ui = millis(); - if (lv_obj_has_flag(scr_scan, LV_OBJ_FLAG_HIDDEN)) - update_inspect(); - else - update_scan_list(); - } - log_handle_serial(); - vTaskDelay(2); -} diff --git a/11-BLE-Scanner/src/result_log.cpp b/11-BLE-Scanner/src/result_log.cpp deleted file mode 100644 index 903f0e6..0000000 --- a/11-BLE-Scanner/src/result_log.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "result_log.h" - -#include - -#include "ble_worker.h" - -static const char *LOG_PATH = "/ble_log.txt"; -static bool log_ok = false; - -static void print_help() -{ - Serial.println("BLE scanner: send 'd' to dump /ble_log.txt, 'c' to clear it, 's' to print the current device list."); -} - -void log_begin() -{ - // uses the "spiffs" data partition of huge_app.csv; formats it on first use - log_ok = LittleFS.begin(true); - if (!log_ok) - { - Serial.println("LittleFS mount failed - results will not be saved"); - return; - } - unsigned size = 0; - if (LittleFS.exists(LOG_PATH)) - { - File f = LittleFS.open(LOG_PATH, FILE_READ); - size = f.size(); - f.close(); - } - Serial.printf("Log: %u bytes stored (flash %u / %u bytes used)\n", size, - (unsigned)LittleFS.usedBytes(), (unsigned)LittleFS.totalBytes()); - print_help(); -} - -void log_append(const String &title, const String &text) -{ - if (!log_ok) - return; - File f = LittleFS.open(LOG_PATH, FILE_APPEND); - if (!f) - return; - // no RTC: time since boot - f.printf("===== %s (t=%lus since boot) =====\n", title.c_str(), millis() / 1000); - f.print(text); - if (!text.endsWith("\n")) - f.print("\n"); - f.print("\n"); - f.close(); -} - -void log_handle_serial() -{ - while (Serial.available()) - { - char c = Serial.read(); - if (c == 'd' || c == 'D') - { - Serial.println("----- BEGIN /ble_log.txt -----"); - if (LittleFS.exists(LOG_PATH)) - { - File f = LittleFS.open(LOG_PATH, FILE_READ); - while (f.available()) - Serial.write(f.read()); - f.close(); - } - Serial.println("----- END /ble_log.txt -----"); - } - else if (c == 'c' || c == 'C') - { - LittleFS.remove(LOG_PATH); - Serial.println("Log cleared."); - } - else if (c == 's' || c == 'S') - { - Serial.println("----- devices -----"); - Serial.print(ble_devices_as_text()); - Serial.println("-------------------"); - } - else if (c == 'h' || c == 'H' || c == '?') - { - print_help(); - } - } -} diff --git a/11-BLE-Scanner/src/result_log.h b/11-BLE-Scanner/src/result_log.h deleted file mode 100644 index 9556eb5..0000000 --- a/11-BLE-Scanner/src/result_log.h +++ /dev/null @@ -1,12 +0,0 @@ -// Result log in internal flash (LittleFS on the "spiffs" partition), readable later over serial. -#pragma once - -#include - -void log_begin(); - -// Appends a block of text (with a timestamp header) to /ble_log.txt. -void log_append(const String &title, const String &text); - -// Serial commands: 'd' = dump the log, 'c' = clear it, 's' = print the device list. Call every loop. -void log_handle_serial(); diff --git a/12-OBD-Speed/README.md b/12-OBD-Speed/README.md index cd851d7..1145331 100644 --- a/12-OBD-Speed/README.md +++ b/12-OBD-Speed/README.md @@ -14,7 +14,7 @@ Uses the shared board library and LVGL 9.6 (`../shared`), NimBLE-Arduino 2.5.1. ## How it talks to the ELM327 Service `FFF0`, notifications + commands on `FFF1` (verified with the user's "OBDII" adapter, see -`../11-BLE-Scanner/results/2026-10-23_car_test.txt`); other adapters: first notify+write pair. +`docs/2026-10-23_ble_scanner_car_test.txt`, made with the BLE scanner tool that was removed afterwards — in git history); other adapters: first notify+write pair. Init: `ATZ`, `ATE0`, `ATL0`, `ATS0`, `ATH0`, `ATSP0`, `0100`; then `010D` (speed) is polled continuously. Serial (115200) prints status changes as `[obd] ...`. diff --git a/11-BLE-Scanner/results/2026-10-23_car_test.txt b/12-OBD-Speed/docs/2026-10-23_ble_scanner_car_test.txt similarity index 100% rename from 11-BLE-Scanner/results/2026-10-23_car_test.txt rename to 12-OBD-Speed/docs/2026-10-23_ble_scanner_car_test.txt diff --git a/CLAUDE.md b/CLAUDE.md index 0a678c5..f965324 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,9 +79,8 @@ 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 (in progress):** 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` | -| `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` | **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) | +| `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` | +| `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 | | `5-Dimension` | Mechanical drawings (JPG + DWG) | @@ -96,7 +95,7 @@ Re-flash it with Flash Download Tool: ESP32-S3, `1.85_demo.bin` @ 0x0. ## 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, `11-BLE-Scanner`, log in `11-BLE-Scanner/results/2026-10-23_car_test.txt`):** +**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`):** - BLE name **`OBDII`**, MAC `1c:a1:35:69:8d:c5`, advertises service **`0xFFF0`**. Characteristics: **`FFF1` [W, WnR, Notify]**, **`FFF2` [W, WnR]**. Working setup: subscribe to notifications on `FFF1`, write commands (`"... "`) to `FFF1` (write without response) — `FFF2` is the usual write characteristic on such clones and should work too. - Replies: `ATZ` → `ELM327 v2.1` (echo on by default → send `ATE0`), `ATRV` → `12.9V`, `0100` → `41 00 BE 3E A8 13`. @@ -114,8 +113,8 @@ 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, 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). +- **All JC3636W518 LVGL projects now use LVGL 9.6.0 + the shared library** (10-GIF-Tests/*, 12-OBD-Speed). SD drive `A:` = `LV_USE_FS_STDIO` on `/sdcard/`. +- 80 MHz QSPI and RGB565_SWAPPED rendering: **visually confirmed OK by the user** (colors correct, no 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` `setup()` returns early if the SD card fails to mount (black screen); `display_gif_embedded` starts its GIF first.