generated from CubeCraft-Creations/Tracehound
firmware: add OLED status panel to camera node (C6)
Bring up the 1.3" SH1106 128x64 I2C OLED on the XIAO ESP32-C6 (D4/SDA, D5/SCL @ 0x3C) per the Notion wiring diagram. - add U8g2 dependency to the seeed_xiao_esp32c6 env - I2C bus scan at boot (logs responders to serial) - boot splash + live status screen: camera id, IDLE/REC + session timer, battery (raw until calibrated) + video-remaining, hub link state (MQTT/wifi/offline), and camera reachability - refresh runs at the top of loop() so the panel stays live even when WiFi/MQTT are down Verified on hardware: I2C scan finds 0x3C, U8g2 begin ok, panel shows clean readable text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -65,7 +65,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
|||||||
board = seeed_xiao_esp32c6
|
board = seeed_xiao_esp32c6
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps = ${common.lib_deps}
|
lib_deps =
|
||||||
|
${common.lib_deps}
|
||||||
|
olikraus/U8g2 @ ^2.35
|
||||||
build_flags = ${common.build_flags}
|
build_flags = ${common.build_flags}
|
||||||
-D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192
|
-D CONFIG_ARDUINO_LOOP_STACK_SIZE=8192
|
||||||
-D ARDUINO_USB_MODE=1
|
-D ARDUINO_USB_MODE=1
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
#include <PubSubClient.h>
|
#include <PubSubClient.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <SPIFFS.h>
|
#include <SPIFFS.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
// ────────────────────────────────────────────
|
// ────────────────────────────────────────────
|
||||||
// Configuration (SPIFFS)
|
// Configuration (SPIFFS)
|
||||||
@@ -91,9 +93,58 @@ bool saveConfig() {
|
|||||||
#define UART_TX_PIN D6
|
#define UART_TX_PIN D6
|
||||||
|
|
||||||
// Camera-online indicator → green channel of the RGB STAT LED.
|
// Camera-online indicator → green channel of the RGB STAT LED.
|
||||||
// (Full RGB/OLED status-panel bring-up is not implemented yet.)
|
|
||||||
#define STAT_LED_PIN D1
|
#define STAT_LED_PIN D1
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
// Status OLED — 1.3" I2C panel on D4(SDA)/D5(SCL)
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
// 1.3" 128x64 modules are SH1106. If the image is shifted ~2px or
|
||||||
|
// wrapped, the panel is an SSD1306 — swap the constructor below to
|
||||||
|
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C.
|
||||||
|
#define OLED_SDA_PIN D4
|
||||||
|
#define OLED_SCL_PIN D5
|
||||||
|
#define OLED_I2C_ADDR 0x3C
|
||||||
|
|
||||||
|
U8G2_SH1106_128X64_NONAME_F_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE);
|
||||||
|
bool oledReady = false;
|
||||||
|
|
||||||
|
// Last-known camera status, mirrored for the display.
|
||||||
|
int dispBatteryRaw = 0;
|
||||||
|
bool dispRecording = false;
|
||||||
|
int dispVideoRemain = 0; // seconds
|
||||||
|
unsigned long recStartMs = 0; // 0 = not recording
|
||||||
|
|
||||||
|
// Walk the bus and log every responder — confirms the OLED address
|
||||||
|
// (and wiring) independent of the display driver.
|
||||||
|
void i2cScan() {
|
||||||
|
Serial.println("[I2C] Scanning...");
|
||||||
|
byte found = 0;
|
||||||
|
for (byte a = 1; a < 127; a++) {
|
||||||
|
Wire.beginTransmission(a);
|
||||||
|
if (Wire.endTransmission() == 0) {
|
||||||
|
Serial.printf("[I2C] device @ 0x%02X\n", a);
|
||||||
|
found++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) Serial.println("[I2C] none found — check wiring/power");
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayInit() {
|
||||||
|
Wire.begin(OLED_SDA_PIN, OLED_SCL_PIN);
|
||||||
|
i2cScan();
|
||||||
|
oled.setI2CAddress(OLED_I2C_ADDR << 1);
|
||||||
|
oledReady = oled.begin();
|
||||||
|
Serial.printf("[OLED] begin %s\n", oledReady ? "ok" : "FAILED");
|
||||||
|
if (!oledReady) return;
|
||||||
|
oled.clearBuffer();
|
||||||
|
oled.setFont(u8g2_font_7x14B_tr);
|
||||||
|
oled.drawStr(0, 14, "RemoteRig");
|
||||||
|
oled.setFont(u8g2_font_6x10_tr);
|
||||||
|
oled.drawStr(0, 32, "Camera node");
|
||||||
|
oled.drawStr(0, 46, "booting...");
|
||||||
|
oled.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
void sendCmdToESP8266(const String& command) {
|
void sendCmdToESP8266(const String& command) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc["type"] = "cmd";
|
doc["type"] = "cmd";
|
||||||
@@ -201,6 +252,48 @@ bool connectMQTT() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
// Status screen
|
||||||
|
// ────────────────────────────────────────────
|
||||||
|
|
||||||
|
void renderStatus() {
|
||||||
|
if (!oledReady) return;
|
||||||
|
oled.clearBuffer();
|
||||||
|
|
||||||
|
// Camera id (top, bold)
|
||||||
|
oled.setFont(u8g2_font_7x14B_tr);
|
||||||
|
String id = cfg.camera_id.length() ? cfg.camera_id : clientID();
|
||||||
|
oled.drawStr(0, 13, id.c_str());
|
||||||
|
|
||||||
|
oled.setFont(u8g2_font_6x10_tr);
|
||||||
|
char line[24];
|
||||||
|
|
||||||
|
// REC state + session timer
|
||||||
|
if (dispRecording) {
|
||||||
|
unsigned long s = recStartMs ? (millis() - recStartMs) / 1000 : 0;
|
||||||
|
oled.drawBox(0, 19, 6, 6); // filled square = REC
|
||||||
|
snprintf(line, sizeof(line), "REC %02lu:%02lu", s / 60, s % 60);
|
||||||
|
oled.drawStr(10, 26, line);
|
||||||
|
} else {
|
||||||
|
oled.drawStr(0, 26, "IDLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Battery (raw until calibrated) + video remaining (minutes)
|
||||||
|
snprintf(line, sizeof(line), "BAT %d VID %dm", dispBatteryRaw, dispVideoRemain / 60);
|
||||||
|
oled.drawStr(0, 38, line);
|
||||||
|
|
||||||
|
// Uplink to the hub
|
||||||
|
const char* link = mqtt.connected() ? "LINK: MQTT ok"
|
||||||
|
: WiFi.status() == WL_CONNECTED ? "LINK: wifi only"
|
||||||
|
: "LINK: offline";
|
||||||
|
oled.drawStr(0, 50, link);
|
||||||
|
|
||||||
|
// Camera reachability
|
||||||
|
oled.drawStr(0, 62, cameraOnline ? "CAM: online" : "CAM: --");
|
||||||
|
|
||||||
|
oled.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────
|
// ────────────────────────────────────────────
|
||||||
// Setup
|
// Setup
|
||||||
// ────────────────────────────────────────────
|
// ────────────────────────────────────────────
|
||||||
@@ -214,6 +307,8 @@ void setup() {
|
|||||||
pinMode(STAT_LED_PIN, OUTPUT); // RGB STAT LED — green = camera online
|
pinMode(STAT_LED_PIN, OUTPUT); // RGB STAT LED — green = camera online
|
||||||
digitalWrite(STAT_LED_PIN, LOW);
|
digitalWrite(STAT_LED_PIN, LOW);
|
||||||
|
|
||||||
|
displayInit(); // I2C scan + OLED splash
|
||||||
|
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
|
||||||
// UART to ESP-01S
|
// UART to ESP-01S
|
||||||
@@ -251,6 +346,10 @@ void loop() {
|
|||||||
static unsigned long lastBeat = 0, lastRecon = 0;
|
static unsigned long lastBeat = 0, lastRecon = 0;
|
||||||
static int reconDelay = 1;
|
static int reconDelay = 1;
|
||||||
|
|
||||||
|
// ── OLED refresh (always — keep the panel live even when offline) ──
|
||||||
|
static unsigned long lastDisp = 0;
|
||||||
|
if (now - lastDisp > 500) { lastDisp = now; renderStatus(); }
|
||||||
|
|
||||||
// ── Wi-Fi watchdog ──
|
// ── Wi-Fi watchdog ──
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
if (now - lastRecon > 5000) { lastRecon = now; WiFi.reconnect(); }
|
if (now - lastRecon > 5000) { lastRecon = now; WiFi.reconnect(); }
|
||||||
@@ -288,6 +387,14 @@ void loop() {
|
|||||||
digitalWrite(STAT_LED_PIN, online ? HIGH : LOW);
|
digitalWrite(STAT_LED_PIN, online ? HIGH : LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirror status onto the OLED fields
|
||||||
|
dispBatteryRaw = doc["battery_raw"] | 0;
|
||||||
|
dispVideoRemain = doc["video_remaining_sec"] | 0;
|
||||||
|
bool rec = doc["recording"] | false;
|
||||||
|
if (rec && !dispRecording) recStartMs = millis();
|
||||||
|
if (!rec) recStartMs = 0;
|
||||||
|
dispRecording = rec;
|
||||||
|
|
||||||
if (cfg.camera_id.length() > 0) {
|
if (cfg.camera_id.length() > 0) {
|
||||||
// Build the MQTT status payload per contract
|
// Build the MQTT status payload per contract
|
||||||
JsonDocument mqttDoc;
|
JsonDocument mqttDoc;
|
||||||
|
|||||||
Reference in New Issue
Block a user