feat(boards): add device detection and text scrolling support
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BOARD_NAME "WS_32E_4"
|
#define BOARD_NAME "esp32-32e-4"
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════
|
||||||
// Hosyond ESP32-32E 4" (320x480) with ST7796 + XPT2046
|
// Hosyond ESP32-32E 4" (320x480) with ST7796 + XPT2046
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BOARD_NAME "WS_32E"
|
#define BOARD_NAME "esp32-32e"
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════
|
||||||
// TODO: Set these to match YOUR display + wiring.
|
// TODO: Set these to match YOUR display + wiring.
|
||||||
|
|||||||
@@ -395,16 +395,37 @@ void DisplayDriverGFX::drawAlert(const ScreenState& state) {
|
|||||||
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
||||||
_gfx->fillScreen(STYLE_COLOR_BG);
|
_gfx->fillScreen(STYLE_COLOR_BG);
|
||||||
|
|
||||||
// Header
|
// Header - use Layout for safe positioning
|
||||||
_gfx->fillRect(0, 0, DISP_W, STYLE_HEADER_HEIGHT, STYLE_COLOR_HEADER);
|
_gfx->fillRect(0, 0, DISP_W, STYLE_HEADER_HEIGHT, STYLE_COLOR_HEADER);
|
||||||
|
Layout header = Layout::header(DISP_W, STYLE_HEADER_HEIGHT);
|
||||||
|
Layout safeText = header.padded(STYLE_SPACING_X);
|
||||||
|
|
||||||
setBodyFont();
|
setBodyFont();
|
||||||
_gfx->setTextColor(STYLE_COLOR_FG);
|
_gfx->setTextColor(STYLE_COLOR_FG);
|
||||||
_gfx->setCursor(STYLE_SPACING_X, 12);
|
|
||||||
_gfx->printf("KLUBHAUS");
|
|
||||||
|
|
||||||
// WiFi status
|
// Title with scrolling support
|
||||||
_gfx->setCursor(DISP_W - 120, 12);
|
_headerScroller.setText("KLUBHAUS");
|
||||||
_gfx->printf("WiFi:%s", state.wifiSsid.length() > 0 ? "ON" : "OFF");
|
_headerScroller.setScrollSpeed(80);
|
||||||
|
_headerScroller.setPauseDuration(2000);
|
||||||
|
_headerScroller.render(
|
||||||
|
[&](int16_t x, const char* s) {
|
||||||
|
_gfx->setCursor(safeText.x + x, safeText.y + 4);
|
||||||
|
_gfx->print(s);
|
||||||
|
},
|
||||||
|
safeText.w);
|
||||||
|
|
||||||
|
// WiFi status - right aligned with scrolling
|
||||||
|
Layout wifiArea(DISP_W - 150, 0, 140, STYLE_HEADER_HEIGHT);
|
||||||
|
Layout safeWifi = wifiArea.padded(4);
|
||||||
|
_wifiScroller.setText(state.wifiSsid.length() > 0 ? state.wifiSsid.c_str() : "WiFi: OFF");
|
||||||
|
_wifiScroller.setScrollSpeed(60);
|
||||||
|
_wifiScroller.setPauseDuration(1500);
|
||||||
|
_wifiScroller.render(
|
||||||
|
[&](int16_t x, const char* s) {
|
||||||
|
_gfx->setCursor(safeWifi.x + x, safeWifi.y + 4);
|
||||||
|
_gfx->print(s);
|
||||||
|
},
|
||||||
|
safeWifi.w);
|
||||||
|
|
||||||
// Get tile layouts from library helper
|
// Get tile layouts from library helper
|
||||||
int tileCount = display.calculateDashboardLayouts(STYLE_HEADER_HEIGHT, STYLE_TILE_GAP);
|
int tileCount = display.calculateDashboardLayouts(STYLE_HEADER_HEIGHT, STYLE_TILE_GAP);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IDisplayDriver.h"
|
#include "IDisplayDriver.h"
|
||||||
|
#include "Style.h"
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
@@ -54,4 +55,8 @@ private:
|
|||||||
ScreenID _lastScreen = ScreenID::BOOT;
|
ScreenID _lastScreen = ScreenID::BOOT;
|
||||||
BootStage _lastBootStage = BootStage::SPLASH;
|
BootStage _lastBootStage = BootStage::SPLASH;
|
||||||
bool _needsRedraw = true;
|
bool _needsRedraw = true;
|
||||||
|
|
||||||
|
// Text scrollers for scrolling elements
|
||||||
|
TextScroller _headerScroller;
|
||||||
|
TextScroller _wifiScroller;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FQBN="esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB"
|
FQBN="esp32:esp32:waveshare_esp32_s3_touch_lcd_43:PSRAM=enabled,FlashSize=16M,USBMode=hwcdc,PartitionScheme=app3M_fat9M_16MB"
|
||||||
PORT="/dev/ttyACM0"
|
PORT="/dev/ttyUSB0"
|
||||||
LIBS="--library ./vendor/esp32-s3-lcd-43/LovyanGFX"
|
LIBS="--library ./vendor/esp32-s3-lcd-43/LovyanGFX"
|
||||||
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLGFX_USE_V1 -DLOCAL_SECRETS"
|
OPTS="-DDEBUG_MODE -DBOARD_HAS_PSRAM -DLGFX_USE_V1 -DLOCAL_SECRETS"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BOARD_NAME "WS_S3_43"
|
#define BOARD_NAME "esp32-s3-lcd-43"
|
||||||
#define DISPLAY_WIDTH 800
|
#define DISPLAY_WIDTH 800
|
||||||
#define DISPLAY_HEIGHT 480
|
#define DISPLAY_HEIGHT 480
|
||||||
#define DISPLAY_ROTATION 0
|
#define DISPLAY_ROTATION 0
|
||||||
|
|||||||
@@ -304,8 +304,10 @@ void DoorbellLogic::onSerialCommand(const String& cmd) {
|
|||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.printf("[NET] %s RSSI:%d IP:%s\n", _state.wifiSsid.c_str(), _state.wifiRssi,
|
Serial.printf("[NET] %s RSSI:%d IP:%s\n", _state.wifiSsid.c_str(), _state.wifiRssi,
|
||||||
_state.ipAddr.c_str());
|
_state.ipAddr.c_str());
|
||||||
|
} else if(cmd == "board") {
|
||||||
|
Serial.printf("[BOARD] %s\n", _board);
|
||||||
} else
|
} else
|
||||||
Serial.println(F("[CMD] alert|silence|reboot|dashboard|off|status"));
|
Serial.println(F("[CMD] alert|silence|reboot|dashboard|off|status|board"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DoorbellLogic::setScreen(ScreenID s) {
|
void DoorbellLogic::setScreen(ScreenID s) {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public:
|
|||||||
void processSerial();
|
void processSerial();
|
||||||
|
|
||||||
const ScreenState& getScreenState() const { return _state; }
|
const ScreenState& getScreenState() const { return _state; }
|
||||||
|
const char* getBoard() const { return _board; }
|
||||||
|
|
||||||
/// Externally trigger silence (e.g. hold-to-silence gesture).
|
/// Externally trigger silence (e.g. hold-to-silence gesture).
|
||||||
void silenceAlert();
|
void silenceAlert();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
struct Layout {
|
struct Layout {
|
||||||
@@ -36,6 +37,89 @@ struct Layout {
|
|||||||
uint16_t y = row * (tileH + gap);
|
uint16_t y = row * (tileH + gap);
|
||||||
return Layout(x, y, tileW, tileH);
|
return Layout(x, y, tileW, tileH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Layout clamp(uint16_t maxW, uint16_t maxH) const {
|
||||||
|
return Layout(
|
||||||
|
x < maxW ? x : maxW, y < maxH ? y : maxH, w <= maxW ? w : maxW, h <= maxH ? h : maxH);
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout padded(uint16_t padding) const {
|
||||||
|
uint16_t newX = x + padding;
|
||||||
|
uint16_t newY = y + padding;
|
||||||
|
uint16_t newW = w > 2 * padding ? w - 2 * padding : 0;
|
||||||
|
uint16_t newH = h > 2 * padding ? h - 2 * padding : 0;
|
||||||
|
return Layout(newX, newY, newW, newH);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(uint16_t px, uint16_t py) const {
|
||||||
|
return px >= x && px < x + w && py >= y && py < y + h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextScroller {
|
||||||
|
public:
|
||||||
|
TextScroller()
|
||||||
|
: _text()
|
||||||
|
, _scrollOffset(0)
|
||||||
|
, _lastUpdateMs(0)
|
||||||
|
, _scrollSpeed(50)
|
||||||
|
, _pauseMs(2000) { }
|
||||||
|
|
||||||
|
void setText(const char* text) {
|
||||||
|
if(text != _text.c_str()) {
|
||||||
|
_text = text;
|
||||||
|
_scrollOffset = 0;
|
||||||
|
_lastUpdateMs = 0;
|
||||||
|
_paused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setScrollSpeed(uint16_t msPerPixel) { _scrollSpeed = msPerPixel; }
|
||||||
|
void setPauseDuration(uint16_t ms) { _pauseMs = ms; }
|
||||||
|
|
||||||
|
template <typename DrawFn> void render(DrawFn draw, uint16_t maxWidth) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if(_text.length() <= maxWidth / 8) {
|
||||||
|
draw(0, _text.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_paused && now - _lastUpdateMs > _scrollSpeed) {
|
||||||
|
_scrollOffset++;
|
||||||
|
_lastUpdateMs = now;
|
||||||
|
|
||||||
|
if(_scrollOffset > (int)_text.length() * 6) {
|
||||||
|
_paused = true;
|
||||||
|
_pauseStartMs = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_paused && now - _pauseStartMs > _pauseMs) {
|
||||||
|
_paused = false;
|
||||||
|
_scrollOffset = 0;
|
||||||
|
_lastUpdateMs = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t x = -_scrollOffset;
|
||||||
|
char buf[2] = { 0 };
|
||||||
|
for(size_t i = 0; i < _text.length(); i++) {
|
||||||
|
buf[0] = _text.charAt(i);
|
||||||
|
if(x + 8 > 0 && x < (int)maxWidth) {
|
||||||
|
draw(x, buf);
|
||||||
|
}
|
||||||
|
x += 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
String _text;
|
||||||
|
int16_t _scrollOffset;
|
||||||
|
uint32_t _lastUpdateMs;
|
||||||
|
uint32_t _pauseStartMs;
|
||||||
|
uint16_t _scrollSpeed;
|
||||||
|
uint16_t _pauseMs;
|
||||||
|
bool _paused;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TileMetrics {
|
struct TileMetrics {
|
||||||
|
|||||||
@@ -135,6 +135,10 @@ run = """
|
|||||||
./boards/$BOARD/install.sh
|
./boards/$BOARD/install.sh
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
[tasks.detect]
|
||||||
|
description = "Detect connected doorbell board"
|
||||||
|
run = "bash ./scripts/detect-device.sh"
|
||||||
|
|
||||||
# Convenience
|
# Convenience
|
||||||
|
|
||||||
[tasks.clean]
|
[tasks.clean]
|
||||||
|
|||||||
160
scripts/detect-device.sh
Executable file
160
scripts/detect-device.sh
Executable file
@@ -0,0 +1,160 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# detect-device.sh - Detect which doorbell board is connected
|
||||||
|
#
|
||||||
|
# Uses two methods:
|
||||||
|
# 1. Firmware query (if flashed): sends "board" command, expects "[BOARD] xxx"
|
||||||
|
# 2. Hardware probe (fallback): uses esptool to query chip type and flash size
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/detect-device.sh [port]
|
||||||
|
# If no port specified, tries common ports: /dev/ttyUSB0 /dev/ttyACM0
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
TIMEOUT_FIRMWARE=2
|
||||||
|
TIMEOUT_HARDWARE=5
|
||||||
|
|
||||||
|
# Known boards and their characteristics
|
||||||
|
declare -A BOARD_CHIP=(
|
||||||
|
["esp32-s3-lcd-43"]="ESP32-S3"
|
||||||
|
["esp32-32e"]="ESP32"
|
||||||
|
["esp32-32e-4"]="ESP32"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find available serial ports
|
||||||
|
find_ports() {
|
||||||
|
local ports=()
|
||||||
|
for p in /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyACM0 /dev/ttyACM1; do
|
||||||
|
if [ -e "$p" ]; then
|
||||||
|
ports+=("$p")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s\n' "${ports[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Query firmware for board name
|
||||||
|
query_firmware() {
|
||||||
|
local port="$1"
|
||||||
|
|
||||||
|
# Try using Python for more reliable serial communication
|
||||||
|
local output
|
||||||
|
output=$(python3 -c "
|
||||||
|
import serial
|
||||||
|
import time
|
||||||
|
try:
|
||||||
|
s = serial.Serial('$port', 115200, timeout=$TIMEOUT_FIRMWARE)
|
||||||
|
time.sleep(0.5)
|
||||||
|
s.write(b'board\r\n')
|
||||||
|
time.sleep($TIMEOUT_FIRMWARE)
|
||||||
|
print(s.read(200).decode('utf-8', errors='ignore'))
|
||||||
|
s.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Look for [BOARD] xxx pattern
|
||||||
|
if echo "$output" | grep -q '\[BOARD\]'; then
|
||||||
|
local board
|
||||||
|
board=$(echo "$output" | grep '\[BOARD\]' | sed 's/.*\[BOARD\] //' | tr -d '\r\n')
|
||||||
|
if [ -n "$board" ]; then
|
||||||
|
echo "$board"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hardware probe using esptool
|
||||||
|
probe_hardware() {
|
||||||
|
local port="$1"
|
||||||
|
|
||||||
|
# Get chip info
|
||||||
|
local chip_info
|
||||||
|
chip_info=$(timeout "$TIMEOUT_HARDWARE" esptool --port "$port" chip_id 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -z "$chip_info" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for ESP32-S3
|
||||||
|
if echo "$chip_info" | grep -q "ESP32-S3"; then
|
||||||
|
echo "esp32-s3-lcd-43"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For ESP32, check flash size
|
||||||
|
if echo "$chip_info" | grep -q "ESP32"; then
|
||||||
|
local flash_size
|
||||||
|
flash_size=$(echo "$chip_info" | grep -i "flash size" | grep -oE '[0-9]+' | head -1 || echo "0")
|
||||||
|
|
||||||
|
if [ -n "$flash_size" ] && [ "$flash_size" -ge 8 ]; then
|
||||||
|
# 8MB+ flash - likely esp32-32e-4
|
||||||
|
echo "esp32-32e-4 (or esp32-32e)"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
# 4MB or less - likely esp32-32e
|
||||||
|
echo "esp32-32e (or esp32-32e-4)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main detection logic
|
||||||
|
detect() {
|
||||||
|
local port="$1"
|
||||||
|
|
||||||
|
# Try firmware query first (100% accurate if firmware responds)
|
||||||
|
echo "Trying firmware query on $port..." >&2
|
||||||
|
local result
|
||||||
|
result=$(query_firmware "$port" || true)
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
echo "Detected via firmware: $result"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fall back to hardware probe
|
||||||
|
echo "Trying hardware probe on $port..." >&2
|
||||||
|
result=$(probe_hardware "$port" || true)
|
||||||
|
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
echo "Detected via hardware: $result"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "No device detected on $port" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
main() {
|
||||||
|
local target_port="$1"
|
||||||
|
|
||||||
|
if [ -n "$target_port" ]; then
|
||||||
|
# Specific port requested
|
||||||
|
if [ ! -e "$target_port" ]; then
|
||||||
|
echo "Error: Port $target_port does not exist" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
detect "$target_port"
|
||||||
|
else
|
||||||
|
# Auto-detect: try all available ports
|
||||||
|
local found=0
|
||||||
|
for port in $(find_ports); do
|
||||||
|
if detect "$port"; then
|
||||||
|
found=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$found" -eq 0 ]; then
|
||||||
|
echo "No doorbell device detected" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
2
vendor/.gitignore
vendored
Normal file
2
vendor/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
Reference in New Issue
Block a user