Compare commits

...

16 Commits

Author SHA1 Message Date
growlph a8dc82de93 Board routed 2026-05-11 17:51:54 -07:00
growlph c5a3ee0866 More layout tweaking 2026-05-11 10:58:17 -07:00
growlph 342b243984 Merge remote-tracking branch 'origin/master' 2026-05-10 23:44:01 -07:00
growlph b36ddcbbc8 Wiring documentation changes 2026-05-10 23:42:15 -07:00
growlph 1b04be0afe Initial Board Layout 2026-05-10 23:41:06 -07:00
growlph cd900cabbd Base schematic complete 2026-05-10 19:33:46 -07:00
Christopher Wiebe dd9048605e Merge branch 'master' of https://goatcode.ibex.social/growlph/waveshare-panel 2026-05-10 18:55:02 -07:00
Christopher Wiebe 7ada8759bd Document the partial-refresh retry pattern in CLAUDE.md
Reflect the bumped 80ms tap-feedback delay and the 350ms retry that
catches HA state syncs landing in the e-paper's busy window.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 18:53:23 -07:00
Christopher Wiebe 7295048c32 Bump partial refresh to 80ms, add retry to catch HA state syncs
display_partial() drops calls when the e-paper is still busy, which
silently lost HA-triggered widget updates (e.g. fan speed) that landed
during the previous refresh's busy window. The retry fires once the
busy window has cleared so the state-synced state makes it to the panel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 18:52:34 -07:00
Christopher Wiebe 80378b1d95 Portrait UI redesign + HA wire-ups for bedroom fan/light
- Rotate display to portrait (rotation: 0°), 4-row LVGL grid
- Fix touchscreen calibration/transform for portrait orientation
- Wire buttons to alpha_bedroom_{light,ceiling_fan,color_temp}
- Sync button checked state from HA (light/fan on-off + fan percentage)
- Document new orientation and Windows venv path in CLAUDE.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 18:00:10 -07:00
growlph e07666a12b Wires! 2026-05-10 16:51:49 -07:00
growlph a2fbb7ad6a Power and ICSP 2026-05-10 15:08:12 -07:00
growlph c1f0f429b1 Schematic work 2026-05-10 13:59:55 -07:00
growlph 7934f83ee2 PCB Schematic faffing about 2026-05-10 12:59:05 -07:00
growlph dd9acfe94e Started PCB 2026-05-10 01:03:38 -07:00
growlph 15cafb9222 Fixed pin numbering mistake 2026-05-10 01:00:11 -07:00
12 changed files with 17002 additions and 41 deletions
+5
View File
@@ -5,3 +5,8 @@ secrets.yaml
# Python bytecode from custom components
__pycache__/
*.pyc
# We don't need KiCad backups
**/*-backups/*
~*
\#*\#
+20 -5
View File
@@ -35,16 +35,26 @@ output (LOW silences it). No impact on UART download mode or OTA flashing.
- 2-bit grayscale fully working via custom component `waveshare_epaper_2bit`
- LVGL rendering working, 4-shade colorspace confirmed
- Bayer ordered dithering implemented for gradient smoothing
- Touchscreen working: polling mode, mirror_x + mirror_y transforms confirmed correct
- Touchscreen working: polling mode, portrait-orientation transform confirmed correct
- LVGL touch input and button state working end-to-end
- Panel now mounted vertically (portrait); 4-row layout for light/fan controls
- **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
- **Partial** (`refresh_display`, 80ms then a 350ms retry): 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`.
The retry exists because `display_partial()` silently drops calls when the
e-paper is still busy from a previous refresh (`waveshare_epaper_2bit.cpp`
~line 2003). Without it, HA-triggered widget updates (e.g. fan speed sync
arriving 200-500ms after a tap) could land during the busy window of the
tap's partial refresh and be lost until the 10s full refresh. The retry
fires ~430ms after the last draw, by which point the e-paper's ~300ms
partial cycle has cleared. `mode: restart` cancels the retry if a new
draw arrives, so it only fires in the quiet period.
`component.update: display0` only pushes the frame buffer to hardware and does not re-trigger `on_draw_end`.
## Grayscale Implementation
Custom component model name: `2.90inv2-r2-2bpp`
@@ -75,7 +85,11 @@ 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
- 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
- Coordinate transform: mirror_x + mirror_y confirmed correct; swap_xy not needed
- Coordinate transform (portrait, display rotation 0°): `swap_xy: true, mirror_x: true`,
calibration `x_max: 127, y_max: 295`. Gotcha: ESPHome's base class applies `swap_xy`
*before* calibration normalization (see `touchscreen.cpp::add_raw_touch_position_`),
so calibration `x_max`/`y_max` must describe the **post-swap** range — i.e., match
the display width/height, not the raw sensor's native axes.
## Relevant Files
- Custom component: `custom_components/waveshare_epaper_2bit/`
@@ -85,4 +99,5 @@ Amplitude chosen to keep 0xAAAAAA and 0x555555 as solid fills.
- `waveshare_epaper_2bit.h` — header
- Main config: `waveshare-test.yaml` (pin table here is source of truth)
- Build cache: `.esphome/build/waveshare-epaper-test/src/esphome/components/`
- ESPHome source (venv): ~/Code/esphome-2026.1.0/
- ESPHome venv (Windows): `C:\Projects\python_virtual\esphome-2026.1.0` (activate before `esphome` commands)
- ESPHome source (WSL venv): ~/Code/esphome-2026.1.0/
+1 -1
View File
@@ -16,7 +16,7 @@ ESPHome project for the [Waveshare 2.9inch Touch e-Paper HAT](https://www.wavesh
| Display MOSI | GPIO23 | Pin19/GPIO10/SPI0_MOSI |
| Display CS | GPIO19 | Pin24/GPIO8/SPI0_CE0 |
| Display DC | GPIO17 | Pin22/GPIO25 |
| Display BUSY | GPIO4 | Pin24/GPIO24 |
| Display BUSY | GPIO4 | Pin18/GPIO24 |
| Display RST | GPIO5 | Pin11/GPIO17/SPI1_CE1 |
| Touch SDA | GPIO21 | Pin3/GPIO2/I2C1_SDA |
| Touch SCL | GPIO22 | Pin5/GPIO3/I2C1_SCL |
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

+22
View File
@@ -0,0 +1,22 @@
# PCB Layout
For connecting [Waveshare 2.9inch Touch e-Paper HAT](https://www.waveshare.com/2.9inch-Touch-e-Paper-HAT.htm) with an ESP32
## Controller Selection
12E/12F style module for simplicity, and ICSP with an external serial TTL.
Will probably be worth running a paste stencil.
## Enclosure Considerations
Intent is for this to fit into a junction box, so it can mount to a printed
replacement for a switch plate.
NA Jbox screw spacing:
* "Box Mount" (Where the switch mounts to the jbox): 3.281"
* "Strap Mount" (Where the cover plate mounts to the switch): 3.812"
![3d_front.png](3d_front.png)
![copper_front.png](copper_front.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,125 @@
{
"board": {
"active_layer": 5,
"active_layer_preset": "",
"auto_track_width": false,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"shapes": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"prototype_zone_fills": false,
"selection_filter": {
"dimensions": false,
"footprints": false,
"graphics": true,
"keepouts": false,
"lockedItems": false,
"otherItems": false,
"pads": false,
"text": true,
"tracks": false,
"vias": false,
"zones": false
},
"visible_items": [
"vias",
"footprint_text",
"footprint_anchors",
"ratsnest",
"grid",
"footprints_front",
"footprints_back",
"footprint_values",
"footprint_references",
"tracks",
"drc_errors",
"drawing_sheet",
"bitmaps",
"pads",
"zones",
"drc_warnings",
"drc_exclusions",
"locked_item_shadows",
"conflict_shadows",
"shapes"
],
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
"zone_display_mode": 0
},
"git": {
"integration_disabled": false,
"repo_type": "",
"repo_username": "",
"ssh_key": ""
},
"meta": {
"filename": "waveshare-panel.kicad_prl",
"version": 5
},
"net_inspector_panel": {
"col_hidden": [
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
],
"col_order": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9
],
"col_widths": [],
"custom_group_rules": [],
"expanded_rows": [],
"filter_by_net_name": true,
"filter_by_netclass": true,
"filter_text": "",
"group_by_constraint": false,
"group_by_netclass": false,
"show_time_domain_details": false,
"show_unconnected_nets": false,
"show_zero_pad_nets": false,
"sort_ascending": true,
"sorting_column": -1
},
"open_jobsets": [],
"project": {
"files": []
},
"schematic": {
"hierarchy_collapsed": [],
"selection_filter": {
"graphics": true,
"images": true,
"labels": true,
"lockedItems": false,
"otherItems": true,
"pins": true,
"ruleAreas": true,
"symbols": true,
"text": true,
"wires": true
}
}
}
@@ -0,0 +1,724 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 1.45,
"width": 1.175
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 1.016,
"corner_smoothing": 2,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.3048,
"min_island_area": 6.4516,
"min_thickness": 0.2032,
"pad_connection": 3,
"remove_islands": 0,
"thermal_relief_gap": 0.508,
"thermal_relief_spoke_width": 0.508
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.5,
"min_groove_width": 0.0,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.1524,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.5,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0
],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"field_name_whitespace": "warning",
"footprint_filter": "ignore",
"footprint_link_issues": "warning",
"four_way_junction": "ignore",
"global_label_dangling": "warning",
"ground_pin_not_ground": "warning",
"hier_label_mismatch": "error",
"isolated_pin_label": "warning",
"label_dangling": "error",
"label_multiple_wires": "warning",
"lib_symbol_issues": "warning",
"lib_symbol_mismatch": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"same_local_global_label": "warning",
"similar_label_and_power": "warning",
"similar_labels": "warning",
"similar_power": "warning",
"simulation_model_issue": "ignore",
"single_global_label": "ignore",
"stacked_pin_name": "warning",
"unannotated": "error",
"unconnected_wire_endpoint": "warning",
"undefined_netclass": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "waveshare-panel.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.1016,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.1524,
"tuning_profile": "",
"via_diameter": 0.6,
"via_drill": 0.3,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2032,
"line_style": 0,
"name": "Power",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 0,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.762,
"tuning_profile": "",
"via_diameter": 0.762,
"via_drill": 0.4572,
"wire_width": 12
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "Power",
"pattern": "3V3"
},
{
"netclass": "Power",
"pattern": "+5V"
},
{
"netclass": "Power",
"pattern": "+3.3V"
},
{
"netclass": "Power",
"pattern": "GND"
}
]
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"plot": "",
"pos_files": "",
"specctra_dsn": "",
"step": "",
"svg": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"annotation": {
"method": 0,
"sort_order": 0
},
"bom_export_filename": "${PROJECTNAME}.csv",
"bom_fmt_presets": [],
"bom_fmt_settings": {
"field_delimiter": ",",
"keep_line_breaks": false,
"keep_tabs": false,
"name": "CSV",
"ref_delimiter": ",",
"ref_range_delimiter": "",
"string_delimiter": "\""
},
"bom_presets": [],
"bom_settings": {
"exclude_dnp": false,
"fields_ordered": [
{
"group_by": false,
"label": "Reference",
"name": "Reference",
"show": true
},
{
"group_by": false,
"label": "Qty",
"name": "${QUANTITY}",
"show": true
},
{
"group_by": true,
"label": "Value",
"name": "Value",
"show": true
},
{
"group_by": true,
"label": "DNP",
"name": "${DNP}",
"show": true
},
{
"group_by": true,
"label": "Exclude from BOM",
"name": "${EXCLUDE_FROM_BOM}",
"show": true
},
{
"group_by": true,
"label": "Exclude from Board",
"name": "${EXCLUDE_FROM_BOARD}",
"show": true
},
{
"group_by": true,
"label": "Footprint",
"name": "Footprint",
"show": true
},
{
"group_by": false,
"label": "Datasheet",
"name": "Datasheet",
"show": true
}
],
"filter_string": "",
"group_symbols": true,
"include_excluded_from_bom": true,
"name": "Default Editing",
"sort_asc": true,
"sort_field": "Reference"
},
"bus_aliases": {},
"connection_grid_size": 50.0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"hop_over_size_choice": 0,
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"operating_point_overlay_i_precision": 3,
"operating_point_overlay_i_range": "~A",
"operating_point_overlay_v_precision": 3,
"operating_point_overlay_v_range": "~V",
"overbar_offset_ratio": 1.23,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"page_layout_descr_file": "",
"plot_directory": "",
"reuse_designators": true,
"space_save_all_events": true,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_dissipations": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0,
"top_level_sheets": [
{
"filename": "waveshare-panel.kicad_sch",
"name": "waveshare-panel",
"uuid": "c20e9c1a-dd23-4f44-b3fb-ba3a30ec31ea"
}
],
"used_designators": "J4",
"variants": []
},
"sheets": [
[
"c20e9c1a-dd23-4f44-b3fb-ba3a30ec31ea",
"waveshare-panel"
]
],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}
File diff suppressed because it is too large Load Diff
+142 -35
View File
@@ -13,13 +13,18 @@
# |Display MOSI |GPIO23 |Pin19/GPIO10/SPI0_MOSI |Green |
# |Display CS |GPIO19 |Pin24/GPIO8/SPI0_CE0 |Mgnta |
# |Display DC |GPIO17 |Pin22/GPIO25 |White |
# |Display Busy |GPIO4 |Pin24/GPIO24 |Brown |
# |Display Busy |GPIO4 |Pin18/GPIO24 |Brown |
# |Display RST |GPIO5 |Pin11/GPIO17/SPI1_CE1 |Orange |
# |Touch SDA |GPIO21 |Pin3/GPIO2/I2C1_SDA |Gray |
# |Touch SCL |GPIO22 |Pin5/GPIO3/I2C1_SCL |Purple |
# |Touch RST |GPIO15 |Pin13/GPIO27 |Blue |
# |Touch INT |GPIO16 |Pin15/GPIO22 |White |
#
# DISP_RST Moves from GPIO5 -> GPIO25 on PCB to avoid potential
# strapping issue
# TOUCH_IRQ Moves from GPIO16 -> GPIO14 on PCB to avoid potential
# strapping issue
#
# Exposed Entities
# ---
#
@@ -76,7 +81,7 @@ display:
model: 2.90inv2-r2-2bpp
# model: 2.90inv2-r2
full_update_every: 30
rotation: 270°
rotation: 0°
auto_clear_enabled: false
update_interval: never
@@ -96,12 +101,12 @@ touchscreen:
update_interval: 50ms
calibration:
x_min: 0
x_max: 295
x_max: 127
y_min: 0
y_max: 127
y_max: 295
transform:
swap_xy: true
mirror_x: true
mirror_y: true
### Okay, now the Good Stuff ###
@@ -146,6 +151,8 @@ image:
id: floor_lamp
- file: "mdi:home-off"
id: away_button
- file: "mdi:thermometer"
id: light_temp
# 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
@@ -157,7 +164,12 @@ script:
- id: refresh_display
mode: restart
then:
- delay: 60ms
- delay: 80ms
- lambda: 'id(display0).display_partial();'
# Retry once the e-paper's busy window has cleared, to catch HA-synced
# widget updates that landed while the first partial refresh was in flight
# (display_partial drops calls when busy).
- delay: 350ms
- lambda: 'id(display0).display_partial();'
- id: full_refresh_display
mode: restart
@@ -183,8 +195,8 @@ binary_sensor:
- 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
id: alpha_bedroom_light
entity_id: light.alpha_bedroom_light
publish_initial_state: true
on_state:
then:
@@ -192,6 +204,38 @@ binary_sensor:
id: button_ceiling_fan_light
state:
checked: !lambda return x;
- platform: homeassistant
id: alpha_bedroom_ceiling_fan
entity_id: fan.alpha_bedroom_ceiling_fan
publish_initial_state: true
on_state:
then:
- lvgl.widget.update:
id: button_ceiling_fan
state:
checked: !lambda return x;
sensor:
# Track the fan's current percentage so the speed buttons reflect the
# actual HA state (including changes made via other controls).
- platform: homeassistant
id: alpha_bedroom_ceiling_fan_pct
entity_id: fan.alpha_bedroom_ceiling_fan
attribute: percentage
on_value:
then:
- lvgl.widget.update:
id: button_fan_1
state:
checked: !lambda 'return x >= 1 && x < 50;'
- lvgl.widget.update:
id: button_fan_2
state:
checked: !lambda 'return x >= 50 && x < 84;'
- lvgl.widget.update:
id: button_fan_3
state:
checked: !lambda 'return x >= 84;'
# Here's where we put the actual GUI layout
lvgl:
@@ -234,50 +278,74 @@ lvgl:
bg_color: 0xFFFFFF
layout:
type: GRID
grid_rows: [fr(1), fr(1)]
grid_columns: [fr(1), fr(1), fr(1), fr(1)]
grid_rows: [fr(1), fr(1), fr(1), fr(1)]
grid_columns: [fr(1), fr(1)]
widgets:
- button:
id: button_ceiling_fan
grid_cell_row_pos: 0
grid_cell_column_pos: 0
on_click:
- logger.log: "ceiling_fan"
widgets:
- image:
src: ceiling_fan
align: CENTER
# Row 1: Light on/off (full width)
- button:
id: button_ceiling_fan_light
grid_cell_row_pos: 0
grid_cell_column_pos: 1
grid_cell_column_pos: 0
grid_cell_column_span: 2
grid_cell_x_align: STRETCH
width: 124
on_click:
- logger.log: "ceiling_fan_light"
- homeassistant.action:
action: light.toggle
data:
entity_id: light.goat_summit_light
entity_id: light.alpha_bedroom_light
checkable: true
widgets:
- image:
src: ceiling_fan_light
align: CENTER
# Row 2: Fan on/off (full width)
- button:
id: button_fan_off
grid_cell_row_pos: 0
grid_cell_column_pos: 2
id: button_ceiling_fan
grid_cell_row_pos: 1
grid_cell_column_pos: 0
grid_cell_column_span: 2
grid_cell_x_align: STRETCH
width: 124
on_click:
- logger.log: "fan_off"
- logger.log: "ceiling_fan"
- homeassistant.action:
action: fan.toggle
data:
entity_id: fan.alpha_bedroom_ceiling_fan
checkable: true
widgets:
- image:
src: fan_off
src: ceiling_fan
align: CENTER
# Row 3: Light temperature | Fan low
- button:
id: button_light_temp
grid_cell_row_pos: 2
grid_cell_column_pos: 0
on_click:
- logger.log: "light_temp"
- homeassistant.action:
action: button.press
data:
entity_id: button.alpha_bedroom_color_temp
widgets:
- image:
src: light_temp
align: CENTER
- button:
id: button_fan_1
grid_cell_row_pos: 0
grid_cell_column_pos: 3
grid_cell_row_pos: 2
grid_cell_column_pos: 1
checkable: true
on_click:
- logger.log: "fan_speed_1"
- homeassistant.action:
action: fan.set_percentage
data:
entity_id: fan.alpha_bedroom_ceiling_fan
percentage: '33'
- lvgl.widget.update:
id: button_fan_2
state:
@@ -289,30 +357,71 @@ lvgl:
widgets:
- image:
src: fan_speed_1
# Row 4: Fan medium | Fan high
- button:
id: button_fan_2
grid_cell_row_pos: 1
grid_cell_row_pos: 3
grid_cell_column_pos: 0
on_click:
- logger.log: "fan_speed_2"
- homeassistant.action:
action: fan.set_percentage
data:
entity_id: fan.alpha_bedroom_ceiling_fan
percentage: '66'
- lvgl.widget.update:
id: button_fan_1
state:
checked: false
- lvgl.widget.update:
id: button_fan_3
state:
checked: false
checkable: true
widgets:
- image:
src: fan_speed_2
- button:
id: button_fan_3
grid_cell_row_pos: 1
grid_cell_row_pos: 3
grid_cell_column_pos: 1
on_click:
- logger.log: "fan_speed_3"
- homeassistant.action:
action: fan.set_percentage
data:
entity_id: fan.alpha_bedroom_ceiling_fan
percentage: '100'
- lvgl.widget.update:
id: button_fan_1
state:
checked: false
- lvgl.widget.update:
id: button_fan_2
state:
checked: false
checkable: true
widgets:
- image:
src: fan_speed_3
# Unused buttons — kept defined but hidden for future use
- obj:
hidden: true
width: 1
height: 1
border_width: 0
pad_all: 0
widgets:
- button:
id: button_fan_off
on_click:
- logger.log: "fan_off"
checkable: true
widgets:
- image:
src: fan_off
- button:
id: button_floor_lamp
grid_cell_row_pos: 1
grid_cell_column_pos: 2
on_click:
- logger.log: "floor_lamp"
checkable: true
@@ -321,8 +430,6 @@ lvgl:
src: floor_lamp
- button:
id: button_away
grid_cell_row_pos: 1
grid_cell_column_pos: 3
on_click:
- logger.log: "away_button"
checkable: true