Comment cleanup, added refresh delays for stability
This commit is contained in:
@@ -35,7 +35,16 @@ output (LOW silences it). No impact on UART download mode or OTA flashing.
|
|||||||
- 2-bit grayscale fully working via custom component `waveshare_epaper_2bit`
|
- 2-bit grayscale fully working via custom component `waveshare_epaper_2bit`
|
||||||
- LVGL rendering working, 4-shade colorspace confirmed
|
- LVGL rendering working, 4-shade colorspace confirmed
|
||||||
- Bayer ordered dithering implemented for gradient smoothing
|
- Bayer ordered dithering implemented for gradient smoothing
|
||||||
- Touchscreen wiring complete, driver integration in progress
|
- Touchscreen working: polling mode, mirror_x + mirror_y transforms confirmed correct
|
||||||
|
- LVGL touch input and button state working end-to-end
|
||||||
|
- **Active: LVGL layout work**
|
||||||
|
|
||||||
|
## Display Refresh Pattern
|
||||||
|
Two-tier refresh wired to `lvgl.on_draw_end` via two `mode: restart` scripts:
|
||||||
|
- **Partial** (`refresh_display`, 60ms): calls `display_partial()` — fast 1-bit, non-blocking, immediate interaction feedback
|
||||||
|
- **Full** (`full_refresh_display`, 10s): calls `component.update: display0` → `display()` — full 2-bit grayscale quality restore after idle
|
||||||
|
|
||||||
|
60ms was empirically tuned: 80ms felt sluggish, 40ms intermittently raced. `component.update: display0` only pushes the frame buffer to hardware and does not re-trigger `on_draw_end`.
|
||||||
|
|
||||||
## Grayscale Implementation
|
## Grayscale Implementation
|
||||||
Custom component model name: `2.90inv2-r2-2bpp`
|
Custom component model name: `2.90inv2-r2-2bpp`
|
||||||
@@ -43,7 +52,7 @@ Custom component model name: `2.90inv2-r2-2bpp`
|
|||||||
Key implementation details:
|
Key implementation details:
|
||||||
- Gray4 waveform LUT loaded via register 0x32 on every display() call
|
- Gray4 waveform LUT loaded via register 0x32 on every display() call
|
||||||
- Activation uses 0xC7 (custom LUT), not 0xF7 (which reloads OTP LUT)
|
- Activation uses 0xC7 (custom LUT), not 0xF7 (which reloads OTP LUT)
|
||||||
- Always does full refresh — partial update removed from 2bpp class
|
- `display()` = full 2-bit grayscale refresh; `display_partial()` = fast 1-bit partial refresh
|
||||||
- Second bitplane buffer (`buffer2_`) allocated in `initialize()`
|
- Second bitplane buffer (`buffer2_`) allocated in `initialize()`
|
||||||
|
|
||||||
Confirmed bitplane table for this panel with Gray4 LUT:
|
Confirmed bitplane table for this panel with Gray4 LUT:
|
||||||
@@ -66,7 +75,7 @@ Amplitude chosen to keep 0xAAAAAA and 0x555555 as solid fills.
|
|||||||
- Touch count at register 0x1001, touch data at 0x1002 (7 bytes/point), clear by writing 0x00 to 0x1001
|
- Touch count at register 0x1001, touch data at 0x1002 (7 bytes/point), clear by writing 0x00 to 0x1001
|
||||||
- Waveshare's own driver inverts both axes: X = 295 - raw_x, Y = 127 - raw_y
|
- Waveshare's own driver inverts both axes: X = 295 - raw_x, Y = 127 - raw_y
|
||||||
- INT pulse is very brief (sub-ms); component works in polling mode regardless
|
- INT pulse is very brief (sub-ms); component works in polling mode regardless
|
||||||
- Coordinate transform (mirror_x, mirror_y, swap_xy) to be confirmed after first flash
|
- Coordinate transform: mirror_x + mirror_y confirmed correct; swap_xy not needed
|
||||||
|
|
||||||
## Relevant Files
|
## Relevant Files
|
||||||
- Custom component: `custom_components/waveshare_epaper_2bit/`
|
- Custom component: `custom_components/waveshare_epaper_2bit/`
|
||||||
|
|||||||
+75
-119
@@ -59,12 +59,28 @@ captive_portal:
|
|||||||
|
|
||||||
### Setup Interfaces ###
|
### Setup Interfaces ###
|
||||||
|
|
||||||
# For the display
|
# Display
|
||||||
spi:
|
spi:
|
||||||
clk_pin: GPIO18
|
clk_pin: GPIO18
|
||||||
mosi_pin: GPIO23
|
mosi_pin: GPIO23
|
||||||
|
|
||||||
# For the touchscreen
|
display:
|
||||||
|
# This platform is a custom_component forked from upstream waveshare_epaper
|
||||||
|
# but modified by Claude to support 2bpp greyscale
|
||||||
|
- platform: waveshare_epaper_2bit
|
||||||
|
id: display0
|
||||||
|
cs_pin: GPIO19
|
||||||
|
dc_pin: GPIO17
|
||||||
|
busy_pin: GPIO4
|
||||||
|
reset_pin: GPIO5
|
||||||
|
model: 2.90inv2-r2-2bpp
|
||||||
|
# model: 2.90inv2-r2
|
||||||
|
full_update_every: 30
|
||||||
|
rotation: 270°
|
||||||
|
auto_clear_enabled: false
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
|
# Touchscreen
|
||||||
i2c:
|
i2c:
|
||||||
id: i2c_bus
|
id: i2c_bus
|
||||||
sda: GPIO21
|
sda: GPIO21
|
||||||
@@ -72,6 +88,7 @@ i2c:
|
|||||||
scan: false
|
scan: false
|
||||||
|
|
||||||
touchscreen:
|
touchscreen:
|
||||||
|
# This platform is a custom_component written from scratch by Claude
|
||||||
platform: icnt86x
|
platform: icnt86x
|
||||||
id: touchscreen0
|
id: touchscreen0
|
||||||
address: 0x48
|
address: 0x48
|
||||||
@@ -88,7 +105,7 @@ touchscreen:
|
|||||||
|
|
||||||
### Okay, now the Good Stuff ###
|
### Okay, now the Good Stuff ###
|
||||||
|
|
||||||
# Load some fonts for the display renderer (we don't neeed these with LVGL)
|
# Load some fonts for the display renderer
|
||||||
font:
|
font:
|
||||||
- file: "FreeSans.ttf"
|
- file: "FreeSans.ttf"
|
||||||
id: font1
|
id: font1
|
||||||
@@ -102,6 +119,7 @@ font:
|
|||||||
size: 30
|
size: 30
|
||||||
|
|
||||||
# Pull in some icons from the material design library
|
# Pull in some icons from the material design library
|
||||||
|
# https://pictogrammers.com/library/mdi/
|
||||||
image:
|
image:
|
||||||
defaults:
|
defaults:
|
||||||
transparency: chroma_key
|
transparency: chroma_key
|
||||||
@@ -129,8 +147,26 @@ image:
|
|||||||
- file: "mdi:home-off"
|
- file: "mdi:home-off"
|
||||||
id: away_button
|
id: away_button
|
||||||
|
|
||||||
# Force a full display refresh when we press the "boot" button on the devboard
|
# When the user taps the screen, introduce a small delay for the LVGL framebuffer
|
||||||
|
# to stabilize, then do a quick, 1-bit B/W selective update to visually indicate
|
||||||
|
# that a button was pushed.
|
||||||
|
#
|
||||||
|
# When the user is done interacting with the screen and several seconds have
|
||||||
|
# passed, do a slow, full-screen redraw at 2-bit grayscale.
|
||||||
|
script:
|
||||||
|
- id: refresh_display
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 60ms
|
||||||
|
- lambda: 'id(display0).display_partial();'
|
||||||
|
- id: full_refresh_display
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 10s
|
||||||
|
- component.update: display0
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
|
# Force a full display refresh when we press the "boot" button on the devboard
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
pin:
|
pin:
|
||||||
number: GPIO0
|
number: GPIO0
|
||||||
@@ -145,56 +181,52 @@ binary_sensor:
|
|||||||
- component.update: lvgl0
|
- component.update: lvgl0
|
||||||
- delay: 1s
|
- delay: 1s
|
||||||
- component.update: display0
|
- component.update: display0
|
||||||
|
# List the actual home entitites that we want this panel to control here
|
||||||
|
- platform: homeassistant
|
||||||
|
id: goat_summit_light
|
||||||
|
entity_id: light.goat_summit_light
|
||||||
|
publish_initial_state: true
|
||||||
|
on_state:
|
||||||
|
then:
|
||||||
|
- lvgl.widget.update:
|
||||||
|
id: button_ceiling_fan_light
|
||||||
|
state:
|
||||||
|
checked: !lambda return x;
|
||||||
|
|
||||||
display:
|
# Here's where we put the actual GUI layout
|
||||||
- platform: waveshare_epaper_2bit
|
|
||||||
id: display0
|
|
||||||
cs_pin: GPIO19
|
|
||||||
dc_pin: GPIO17
|
|
||||||
busy_pin: GPIO4
|
|
||||||
reset_pin: GPIO5
|
|
||||||
model: 2.90inv2-r2-2bpp
|
|
||||||
full_update_every: 30
|
|
||||||
rotation: 270°
|
|
||||||
auto_clear_enabled: false
|
|
||||||
update_interval: never
|
|
||||||
# show_test_card: true
|
|
||||||
# lambda: |-
|
|
||||||
# it.print(0, 0, id(headerfont), "Bleat!");
|
|
||||||
|
|
||||||
lvgl:
|
lvgl:
|
||||||
- id: lvgl0
|
- id: lvgl0
|
||||||
displays:
|
displays:
|
||||||
- display0
|
- display0
|
||||||
touchscreens:
|
touchscreens:
|
||||||
- touchscreen0
|
- touchscreen0
|
||||||
# on_draw_end:
|
on_draw_end:
|
||||||
# - component.update: display0
|
- script.execute: refresh_display
|
||||||
bg_color: 0xFFFFFF
|
- script.execute: full_refresh_display
|
||||||
# Black: 0x000000
|
# Black: 0x000000
|
||||||
# Dark Grey: 0x555555
|
# Dark Grey: 0x555555
|
||||||
# Light Grey: 0xAAAAAA
|
# Light Grey: 0xAAAAAA
|
||||||
# White: 0xFFFFFF
|
# White: 0xFFFFFF
|
||||||
|
bg_color: 0x000000
|
||||||
theme:
|
theme:
|
||||||
label:
|
|
||||||
text_color: 0x000000
|
|
||||||
button:
|
button:
|
||||||
# bg_color: 0xAAAAAA
|
|
||||||
bg_color: 0xFFFFFF
|
bg_color: 0xFFFFFF
|
||||||
text_color: 0x000000
|
text_color: 0x000000
|
||||||
height: 64
|
height: 60
|
||||||
width: 64
|
width: 64
|
||||||
pad_all: 0
|
pad_all: 0
|
||||||
border_width: 2
|
border_width: 2
|
||||||
border_color: 0x000000
|
border_color: 0x000000
|
||||||
checked:
|
checked:
|
||||||
bg_color: 0x555555
|
bg_color: 0x888888
|
||||||
|
align: CENTER
|
||||||
obj:
|
obj:
|
||||||
bg_color: 0xFFFFFF
|
bg_color: 0xFFFFFF
|
||||||
widgets:
|
widgets:
|
||||||
- obj:
|
- obj:
|
||||||
width: 100%
|
width: 100%
|
||||||
height: 100%
|
height: 100%
|
||||||
|
scrollable: false
|
||||||
pad_all: 0
|
pad_all: 0
|
||||||
# pad_row: 0
|
# pad_row: 0
|
||||||
# pad_column: 0
|
# pad_column: 0
|
||||||
@@ -206,6 +238,7 @@ lvgl:
|
|||||||
grid_columns: [fr(1), fr(1), fr(1), fr(1)]
|
grid_columns: [fr(1), fr(1), fr(1), fr(1)]
|
||||||
widgets:
|
widgets:
|
||||||
- button:
|
- button:
|
||||||
|
id: button_ceiling_fan
|
||||||
grid_cell_row_pos: 0
|
grid_cell_row_pos: 0
|
||||||
grid_cell_column_pos: 0
|
grid_cell_column_pos: 0
|
||||||
on_click:
|
on_click:
|
||||||
@@ -213,75 +246,81 @@ lvgl:
|
|||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: ceiling_fan
|
src: ceiling_fan
|
||||||
|
align: CENTER
|
||||||
- button:
|
- button:
|
||||||
|
id: button_ceiling_fan_light
|
||||||
grid_cell_row_pos: 0
|
grid_cell_row_pos: 0
|
||||||
grid_cell_column_pos: 1
|
grid_cell_column_pos: 1
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "ceiling_fan_light"
|
- logger.log: "ceiling_fan_light"
|
||||||
- lambda: 'id(display0).display_partial();'
|
- homeassistant.action:
|
||||||
|
action: light.toggle
|
||||||
|
data:
|
||||||
|
entity_id: light.goat_summit_light
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: ceiling_fan_light
|
src: ceiling_fan_light
|
||||||
- button:
|
- button:
|
||||||
|
id: button_fan_off
|
||||||
grid_cell_row_pos: 0
|
grid_cell_row_pos: 0
|
||||||
grid_cell_column_pos: 2
|
grid_cell_column_pos: 2
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "fan_off"
|
- logger.log: "fan_off"
|
||||||
- component.update: display0
|
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: fan_off
|
src: fan_off
|
||||||
- button:
|
- button:
|
||||||
|
id: button_fan_1
|
||||||
grid_cell_row_pos: 0
|
grid_cell_row_pos: 0
|
||||||
grid_cell_column_pos: 3
|
grid_cell_column_pos: 3
|
||||||
checkable: true
|
checkable: true
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "fan_speed_1"
|
- logger.log: "fan_speed_1"
|
||||||
- lvgl.widget.update:
|
- lvgl.widget.update:
|
||||||
id: fan_speed_2
|
id: button_fan_2
|
||||||
state:
|
state:
|
||||||
checked: false
|
checked: false
|
||||||
- lvgl.widget.update:
|
- lvgl.widget.update:
|
||||||
id: fan_speed_3
|
id: button_fan_3
|
||||||
state:
|
state:
|
||||||
checked: false
|
checked: false
|
||||||
- component.update: display0
|
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: fan_speed_1
|
src: fan_speed_1
|
||||||
- button:
|
- button:
|
||||||
|
id: button_fan_2
|
||||||
grid_cell_row_pos: 1
|
grid_cell_row_pos: 1
|
||||||
grid_cell_column_pos: 0
|
grid_cell_column_pos: 0
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "fan_speed_2"
|
- logger.log: "fan_speed_2"
|
||||||
- component.update: display0
|
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: fan_speed_2
|
src: fan_speed_2
|
||||||
- button:
|
- button:
|
||||||
|
id: button_fan_3
|
||||||
grid_cell_row_pos: 1
|
grid_cell_row_pos: 1
|
||||||
grid_cell_column_pos: 1
|
grid_cell_column_pos: 1
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "fan_speed_3"
|
- logger.log: "fan_speed_3"
|
||||||
- component.update: display0
|
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: fan_speed_3
|
src: fan_speed_3
|
||||||
- button:
|
- button:
|
||||||
|
id: button_floor_lamp
|
||||||
grid_cell_row_pos: 1
|
grid_cell_row_pos: 1
|
||||||
grid_cell_column_pos: 2
|
grid_cell_column_pos: 2
|
||||||
on_click:
|
on_click:
|
||||||
- logger.log: "floor_lamp"
|
- logger.log: "floor_lamp"
|
||||||
- component.update: display0
|
|
||||||
checkable: true
|
checkable: true
|
||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: floor_lamp
|
src: floor_lamp
|
||||||
- button:
|
- button:
|
||||||
|
id: button_away
|
||||||
grid_cell_row_pos: 1
|
grid_cell_row_pos: 1
|
||||||
grid_cell_column_pos: 3
|
grid_cell_column_pos: 3
|
||||||
on_click:
|
on_click:
|
||||||
@@ -290,86 +329,3 @@ lvgl:
|
|||||||
widgets:
|
widgets:
|
||||||
- image:
|
- image:
|
||||||
src: away_button
|
src: away_button
|
||||||
# - label:
|
|
||||||
# text: 'Test 2bpp 1636'
|
|
||||||
# align: TOP_LEFT
|
|
||||||
# text_font: font1
|
|
||||||
# - button:
|
|
||||||
# id: button0
|
|
||||||
# align: TOP_RIGHT
|
|
||||||
# on_click:
|
|
||||||
# - logger.log: "button0 clicked (unlock)"
|
|
||||||
# widgets:
|
|
||||||
# - image:
|
|
||||||
# src: home_lock_open
|
|
||||||
# outline_width: 1
|
|
||||||
# - button:
|
|
||||||
# id: button1
|
|
||||||
# align: BOTTOM_RIGHT
|
|
||||||
# on_click:
|
|
||||||
# - logger.log: "button1 clicked (lock)"
|
|
||||||
# widgets:
|
|
||||||
# - image:
|
|
||||||
# src: home_lock
|
|
||||||
# outline_width: 1
|
|
||||||
# - slider:
|
|
||||||
# id: slider0
|
|
||||||
# align: LEFT_MID
|
|
||||||
# width: 180
|
|
||||||
# height: 16
|
|
||||||
# min_value: 0
|
|
||||||
# max_value: 100
|
|
||||||
# value: 50
|
|
||||||
# on_value:
|
|
||||||
# - logger.log:
|
|
||||||
# format: "Slider: %.0f"
|
|
||||||
# args: ['x']
|
|
||||||
# - lambda: 'id(display0).display_partial();'
|
|
||||||
# - label:
|
|
||||||
# text: "0xFFFFFF"
|
|
||||||
# text_color: 0xFFFFFF
|
|
||||||
# x: 0
|
|
||||||
# y: 15
|
|
||||||
# - label:
|
|
||||||
# text: "0xAAAAAA"
|
|
||||||
# text_color: 0xAAAAAA
|
|
||||||
# x: 0
|
|
||||||
# y: 30
|
|
||||||
# - label:
|
|
||||||
# text: "0x555555"
|
|
||||||
# text_color: 0x555555
|
|
||||||
# x: 0
|
|
||||||
# y: 45
|
|
||||||
# - label:
|
|
||||||
# text: "0x000000"
|
|
||||||
# text_color: 0x000000
|
|
||||||
# x: 0
|
|
||||||
# y: 60
|
|
||||||
# - obj:
|
|
||||||
# align: BOTTOM_LEFT
|
|
||||||
# width: 33%
|
|
||||||
# height: 30%
|
|
||||||
# bg_color: 0x000000 #Black
|
|
||||||
# - obj:
|
|
||||||
# align: BOTTOM_MID
|
|
||||||
# width: 33%
|
|
||||||
# height: 30%
|
|
||||||
# bg_color: 0x555555 #Dark Grey
|
|
||||||
# - obj:
|
|
||||||
# align: BOTTOM_RIGHT
|
|
||||||
# width: 33%
|
|
||||||
# height: 30%
|
|
||||||
# bg_color: 0xAAAAAA #Light Grey
|
|
||||||
# - obj:
|
|
||||||
# align: TOP_RIGHT
|
|
||||||
# width: 33%
|
|
||||||
# height: 30%
|
|
||||||
# bg_color: 0xFFFFFF #White
|
|
||||||
# - obj:
|
|
||||||
# align: LEFT_MID
|
|
||||||
# width: 66%
|
|
||||||
# height: 30%
|
|
||||||
# bg_color: 0x000000
|
|
||||||
# bg_grad_color: 0xFFFFFF
|
|
||||||
# bg_grad_dir: HOR
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user