This commit is contained in:
Chris Cochrun 2025-07-08 09:34:25 -05:00
parent 96732ec6ae
commit 7742ff8909
4 changed files with 2251 additions and 138 deletions

Binary file not shown.

BIN
fonts/vm.ttf Normal file

Binary file not shown.

1501
nesp.yml Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,13 @@
substitutions:
color_bg: '0x282A36'
color_bg_alt: '0x34353e'
color_white: '0xE2E4E5'
color_temp_current: '0x57c7ff'
color_dark_blue: '0x57c7ff'
color_light_blue: '0x9aedfe'
color_green: '0x5AF78E'
color_red: '0xff5c57'
color_darker_white: '0xA5A5A9'
esphome: esphome:
name: thermostat name: thermostat
@ -13,7 +23,7 @@ esphome:
# CONFIG_SPIRAM_RODATA: y # CONFIG_SPIRAM_RODATA: y
esp32: esp32:
board: esp32-s3-devkitc-1 board: esp32s3box
variant: esp32s3 variant: esp32s3
flash_size: 8MB flash_size: 8MB
framework: framework:
@ -41,12 +51,15 @@ psram:
# Enable logging # Enable logging
logger: logger:
level: DEBUG
# baud_rate: 0
ch422g: ch422g:
# address: 0x24 # address: 0x24
# Enable Home Assistant API # Enable Home Assistant API
api: api:
reboot_timeout: 10s
password: "" password: ""
ota: ota:
@ -54,11 +67,20 @@ ota:
password: "" password: ""
wifi: wifi:
# ssid: "Cochrun" ssid: "Cochrun"
# password: "GreasyChicken784" password: "GreasyChicken784"
power_save_mode: none
reboot_timeout: 10s
# output_power: 20.5dB
# enable_btm: true
# enable_rrm: true
manual_ip:
static_ip: 192.168.1.123
gateway: 192.168.1.1
subnet: 255.255.255.0
ssid: "TFC" # ssid: "TFC"
password: "Disciple77" # password: "Disciple77"
# Enable fallback hotspot (captive portal) in case wifi connection fails # Enable fallback hotspot (captive portal) in case wifi connection fails
ap: ap:
@ -67,38 +89,66 @@ wifi:
captive_portal: captive_portal:
# external_components:
# # replace 1234 with the number of the Pull Request
# - source: github://pr#5198
# components:
# # list all components modified by this Pull Request here
# - aht10
i2c: i2c:
- id: bus_a - id: bus_a
sda: GPIO08 sda: GPIO08
scl: GPIO09 scl: GPIO09
scan: True scan: True
# - id: bus_b frequency: 400kHz
# sda:
# pin:
# ch422g:
# number: 6
# scl:
# pin:
# ch422g:
# number: 5
# scan: True
# uart: # uart:
# tx_pin: GPIO27 # tx_pin: GPIO27
# rx_pin: GPIO26 # rx_pin: GPIO26
# baud_rate: 115200 # baud_rate: 115200
time:
- platform: homeassistant
id: hass_time
on_time:
- seconds: /5 # Update every second
then:
- lvgl.label.update:
id: time_label
text: !lambda |-
auto time = id(hass_time).now();
return time.strftime("%a %m/%d/%y %I:%M");
# - script.execute: update_date_time_label
sensor: sensor:
- platform: aht10 # - platform: aht10
variant: AHT20 # variant: AHT20
# temperature:
# name: "Temperature"
# id: temperature
# humidity:
# name: "Humidity"
# id: humidity
# address: 0x66
- platform: bme280_i2c
temperature: temperature:
name: "Temperature" name: "Temperature"
id: temperature id: temperature
filters:
- offset: 1.0
on_value:
then:
- lvgl.label.update:
id: temp_label
text: !lambda |-
auto temp = x * (9.0/5.0) + 32.0;
return str_truncate(to_string(temp), 4);
humidity: humidity:
name: "Humidity" name: "Humidity"
id: humidity id: humidity
# address: 0x76 pressure:
name: "Pressure"
id: pressure
address: 0x76
# dfrobot_sen0395: # dfrobot_sen0395:
# binary_sensor: # binary_sensor:
@ -128,45 +178,38 @@ sensor:
# mode: INPUT_PULLDOWN # mode: INPUT_PULLDOWN
switch: switch:
# # Switch to turn on/off detection
# - platform: dfrobot_sen0395
# type: sensor_active
# name: Mmwave Active
# - platform: dfrobot_sen0395
# type: presence_via_uart
# name: Mmwave UART active
# restore_mode: always_on
# - platform: dfrobot_sen0395
# type: turn_on_led
# name: Mmwave Led
# restore_mode: always_on
# icon: "mdi:led-on"
# - platform: dfrobot_sen0395
# type: start_after_boot
# name: Mmwave Start
# restore_mode: always_on
# on_turn_on:
# then:
# - number.set:
# id: sensitivity
# value: !lambda |-
# return id(mmwave_uart).state + 1;
- platform: gpio - platform: gpio
pin: 16 pin:
ch422g:
number: 5
mode:
output: true
input: false
inverted: true
id: heater id: heater
restore_mode: RESTORE_DEFAULT_OFF restore_mode: ALWAYS_OFF
- platform: gpio - platform: gpio
pin: 44 pin:
ch422g:
number: 8
mode:
output: true
input: false
open_drain: false
inverted: true
id: ac id: ac
restore_mode: RESTORE_DEFAULT_OFF restore_mode: ALWAYS_OFF
- platform: gpio - platform: gpio
pin: 15 pin:
ch422g:
number: 9
mode:
output: true
input: false
open_drain: false
inverted: true
id: fan id: fan
restore_mode: RESTORE_DEFAULT_OFF restore_mode: ALWAYS_OFF
# button: # button:
# - platform: template # - platform: template
@ -197,6 +240,7 @@ switch:
climate: climate:
- platform: thermostat - platform: thermostat
id: climape
name: "Thermostat" name: "Thermostat"
sensor: temperature sensor: temperature
humidity_sensor: humidity humidity_sensor: humidity
@ -204,6 +248,8 @@ climate:
min_cooling_run_time: 10s min_cooling_run_time: 10s
min_heating_off_time: 10s min_heating_off_time: 10s
min_heating_run_time: 10s min_heating_run_time: 10s
min_fanning_off_time: 10s
min_fanning_run_time: 10s
min_idle_time: 30s min_idle_time: 30s
cool_action: cool_action:
- switch.turn_off: heater - switch.turn_off: heater
@ -217,11 +263,135 @@ climate:
- switch.turn_off: ac - switch.turn_off: ac
- switch.turn_off: heater - switch.turn_off: heater
- switch.turn_off: fan - switch.turn_off: fan
fan_only_action:
- lambda: !lambda ESP_LOGD("fan", "DA FAN");
default_preset: Home default_preset: Home
preset: preset:
- name: Home - name: Home
default_target_temperature_low: 67 °F default_target_temperature_low: 67 °F
default_target_temperature_high: 76 °F default_target_temperature_high: 78 °F
on_state:
then:
- if:
condition:
lambda: "return x.mode != CLIMATE_MODE_OFF;"
then:
- lvgl.label.update:
id: action_label
text:
format: "%s"
args: [ 'climate_action_to_string(x.action)' ]
else:
- lvgl.label.update:
id: set_temp_label
text: "CLIMATE_OFF"
- if:
condition:
lambda: "return x.mode == CLIMATE_MODE_COOL;"
then:
- lvgl.widget.update:
id: cool_button
state:
checked: true
- lvgl.widget.update:
id: heat_button
state:
checked: false
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: off_button
state:
checked: false
# - lvgl.arc.update:
# id: current_temp_arc
# value: !lambda |-
# return x.target_temperature_high * (9.0/5.0) + 32.0;
- lvgl.label.update:
id: set_temp_label
text:
format: "%.1f°"
args: [ 'x.target_temperature_high * (9.0/5.0) + 32.0' ]
- if:
condition:
lambda: "return x.mode == CLIMATE_MODE_HEAT;"
then:
- lvgl.widget.update:
id: cool_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: true
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: off_button
state:
checked: false
# - lvgl.arc.update:
# id: current_temp_arc
# value: !lambda |-
# return x.target_temperature_low * (9.0/5.0) + 32.0;
- lvgl.label.update:
id: set_temp_label
text:
format: "%.1f°"
args: [ 'x.target_temperature_low * (9.0/5.0) + 32.0' ]
- if:
condition:
lambda: "return x.mode == CLIMATE_MODE_FAN_ONLY;"
then:
- lvgl.widget.update:
id: cool_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: false
- lvgl.widget.update:
id: fan_button
state:
checked: true
- lvgl.widget.update:
id: off_button
state:
checked: false
# - lvgl.arc.update:
# id: current_temp_arc
# value: !lambda |-
# return x.target_temperature_high * (9.0/5.0) + 32.0;
- lvgl.label.update:
id: set_temp_label
text:
format: "%.1f°"
args: [ 'x.target_temperature_high * (9.0/5.0) + 32.0' ]
- if:
condition:
lambda: "return x.mode == CLIMATE_MODE_OFF;"
then:
- lvgl.widget.update:
id: cool_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: false
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: off_button
state:
checked: true
# Example minimal configuration entry # Example minimal configuration entry
display: display:
@ -230,10 +400,16 @@ display:
auto_clear_enabled: false auto_clear_enabled: false
update_interval: never update_interval: never
color_order: RGB color_order: RGB
pclk_frequency: 14MHz pclk_frequency: 16MHz
dimensions: dimensions:
width: 800 width: 800
height: 480 height: 480
reset_pin:
ch422g:
number: 3
enable_pin:
ch422g:
number: 2
de_pin: de_pin:
number: 5 number: 5
hsync_pin: hsync_pin:
@ -243,38 +419,98 @@ display:
number: 3 number: 3
ignore_strapping_warning: true ignore_strapping_warning: true
pclk_pin: 7 pclk_pin: 7
pclk_inverted: false hsync_back_porch: 30
hsync_back_porch: 10 #30 hsync_front_porch: 210
hsync_front_porch: 20 #210 hsync_pulse_width: 30
hsync_pulse_width: 10 #30 vsync_back_porch: 4
vsync_back_porch: 10 #4 vsync_front_porch: 4
vsync_front_porch: 10 #4 vsync_pulse_width: 4
vsync_pulse_width: 10 #4
data_pins: data_pins:
red: red: [1, 2, 42, 41, 40]
- 1 #r3 blue: [14, 38, 18, 17, 10]
- 2 #r4 green: [39, 0, 45, 48, 47, 21]
- 42 #r5
- 41 #r6
- 40 #r7
blue:
- 14 #b3
- 38 #b4
- 18 #b5
- 17 #b6
- 10 #b7
green:
- 39 #g2
- 0 #g3
- 45 #g4
- 48 #g5
- 47 #g6
- 21 #g7
font: font:
- file: "gfonts://Roboto" - file:
id: chu_nano type: gfonts
family: Quicksand
weight: 500
id: h2
size: 40
- file:
type: gfonts
family: Quicksand
weight: 400
id: h3
size: 20
- file:
type: gfonts
family: Quicksand
weight: 400
id: body
size: 18 size: 18
- file:
type: gfonts
family: Quicksand
weight: 700
id: h1
size: 120
- file: "fonts/vm.ttf"
id: md_icons
size: 30
glyphs: [
"󰖩", # nf:wifi
]
- file: "fonts/vm.ttf"
id: sm_icons
size: 20
glyphs: [
"󰖩", # nf:wifi
]
- file: "fonts/materialdesignicons-webfont.ttf"
id: icons
size: 30
glyphs: [
"\U000F050F", # mdi:thermometer
"\U000F058E", # mdi:water-percent
"\U000F0425", # mdi:power
"\U000F0238", # mdi:fire
"\U000F1A45", # mdi:heat-wave
"\U000F0717", # mdi:snowflake
"\U000F1A79", # mdi:sun-snowflake-variant
"\U000F0210", # mdi:fan
"\U000F171D", # mdi:fan-auto
"\U000F1472", # mdi:fan-speed-1
"\U000F1473", # mdi:fan-speed-2
"\U000F1474", # mdi:fan-speed-3
"\U000F032A", # mdi:leaf
"\U000F04B9", # mdi:sofa
"\U000F14DE", # mdi:rocket-launch
"\U000F0D80", # mdi:home-floor-1
"\U000F0D81", # mdi:home-floor-2
"\U000F004D", # mdi:arrow-left
"\U000F09DF", # mdi:circle-small
"\U000F0E03", # mdi:thermometer-chevron-up
"\U000F0E02", # mdi:thermometer-chevron-down
"\U000F0594", # mdi:weather-night (clear)
"\U000F0590", # mdi:weather-cloudy (cloudy)
"\U000F0898", # mdi:weather-hurricane (cyclone, tropical_cyclone)
"\U000F0F30", # mdi:weather-hazy (dust, dusty, haze, hazy)
"\U000F0591", # mdi:weather-fog (fog)
"\U000F12CB", # mdi:snowflake-melt (frost)
"\U000F0596", # mdi:weather-pouring (heavy_shower, heavy_showers, rain)
"\U000F0F33", # mdi:weather-partly-rainy (light_rain)
# mdi:weather-light-showers (light_shower, light_showers)
"\U000F0599", # mdi:weather-sunny (mostly_sunny, sunny)
"\U000F0595", # mdi:weather-partly-cloudy (partly_cloudy)
"\U000F0597", # mdi:weather-rainy (shower, showers)
"\U000F0598", # mdi:weather-snowy (snow)
"\U000F0593", # mdi:weather-lightning
"\U000F067E", # mdi:weather-lightning-rainy (storm, storms)
"\U000F059D", # mdi:weather-windy (wind, windy)
"\U000F0592", # mdi:weather-hail
]
touchscreen: touchscreen:
- platform: gt911 - platform: gt911
@ -297,69 +533,445 @@ color:
hex: 282A36 hex: 282A36
lvgl: lvgl:
buffer_size: 25% # buffer_size: 25%
default_font: chu_nano default_font: h2
theme:
slider:
bg_color: 0x57c7ff
knob:
bg_color: 0x57c7ff
pages: pages:
- id: page - id: main_page
bg_color: 0x282A36 bg_color: 0x282A36
layout:
type: flex
pad_row: 4
pad_column: 4
flex_align_main: CENTER
flex_align_cross: CENTER
flex_align_track: CENTER
widgets: widgets:
- obj: - obj:
align: top_mid align: top_mid
width: 800 bg_color: 0x282A36
height: 480
flex_grow: 1
border_width: 0
widgets: widgets:
- slider: # Clock container
pad_top: 30px
align: top_mid
width: 600
id: heat_id
value: 76
min_value: 50
max_value: 100
- label: - label:
pad_top: 40px id: time_label
text: ""
text_color: ${color_darker_white}
text_font: h3
align: top_left
x: 10
y: 10
- label:
id: wifi_label
text: "󰖩"
text_color: ${color_darker_white}
align: top_right
text_font: sm_icons
y: 10
x: -10
- button:
id: beat_button
height: 60
width: 60
x: 30
y: 50
bg_color: 0x34353e
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0xff5c57
on_click:
then:
- switch.toggle: heater
widgets:
- label:
text: "\U000F0238"
align: center align: center
text: "Hello World" text_font: icons
text_color: 0xE2E4E5 text_color: 0xE2E4E5
- slider: # Clock container - button:
pad_top: 60px id: k_button
align: top_mid height: 60
width: 600 width: 60
id: cool_id x: 40
value: 68 y: 120
min_value: 50 bg_color: 0x34353e
max_value: 100 border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0x9aedfe
on_click:
then:
- switch.toggle: ac
widgets:
- label: - label:
pad_top: 70px text: "\U000F0717"
align: center align: center
text: "Hello World" text_font: icons
text_color: 0xE2E4E5 text_color: 0xE2E4E5
- button:
id: f_button
height: 60
width: 60
x: 40
y: 200
bg_color: 0x34353e
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0x9aedfe
on_click:
then:
- switch.toggle: fan
widgets:
- label: - label:
text: "\U000F0717"
align: center
text_font: icons
text_color: 0xE2E4E5
- obj:
bg_color: 0x282A36
height: 480
flex_grow: 2
border_width: 0
widgets:
- obj:
align: BOTTOM_MID
width: 100%
bg_color: 0x282A36
border_width: 0
layout:
type: flex
pad_row: 4
pad_column: 4
flex_align_main: CENTER
flex_align_cross: END
flex_align_track: END
widgets:
- button:
id: heat_button
height: 60
width: 60
bg_color: 0x34353e
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0xff5c57
on_click:
then:
- lvgl.widget.update:
id: off_button
state:
checked: false
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: cool_button
state:
checked: false
- climate.control:
id: climape
mode: HEAT
widgets:
- label:
text: "\U000F0238"
align: center
text_font: icons
text_color: 0xE2E4E5
- button:
id: cool_button
height: 60
width: 60
bg_color: 0x34353e
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0x9aedfe
on_click:
then:
- lvgl.widget.update:
id: off_button
state:
checked: false
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: false
- climate.control:
id: climape
mode: COOL
widgets:
- label:
text: "\U000F0717"
align: center
text_font: icons
text_color: 0xE2E4E5
- button:
id: fan_button
height: 60
width: 60
bg_color: 0x34353e
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0x5AF78E
on_click:
then:
- lvgl.widget.update:
id: off_button
state:
checked: false
- lvgl.widget.update:
id: cool_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: false
- climate.control:
id: climape
mode: FAN_ONLY
widgets:
- label:
text: "\U000F0210"
align: center
text_font: icons
text_color: 0xE2E4E5
- button:
id: off_button
height: 60
width: 60
bg_color: 0x34353E
border_width: 2
border_color: 0xE2E4E5
radius: 20
checkable: true
checked:
bg_color: 0xFF5C57
on_click:
then:
- lvgl.widget.update:
id: fan_button
state:
checked: false
- lvgl.widget.update:
id: cool_button
state:
checked: false
- lvgl.widget.update:
id: heat_button
state:
checked: false
- climate.control:
id: climape
mode: 'OFF'
widgets:
- label:
text: "\U000F09DF"
text_font: icons
align: center align: center
text: "Hello World"
text_color: 0xE2E4E5 text_color: 0xE2E4E5
# time: # Arc Outer Ring only for visual effect
# - platform: homeassistant - arc:
# id: time_comp id: arc_ring_outer
height: 370
width: 370
y: -20
align: CENTER
arc_color: ${color_light_blue}
arc_opa: COVER
arc_width: 5
knob:
bg_color: ${color_white}
# Meter ticks and needle
- meter:
id: meter_ticks
hidden: false
height: 360
width: 360
y: -20
align: CENTER
bg_opa: TRANSP
border_width: 0
scales:
range_from: 160 # 16°C
range_to: 340 # 34°C
# Set temperature ticks background
ticks:
count: 60
length: 18
width: 2
color: ${color_white}
indicators:
# Curremt temperature needle
- line:
id: current_temp_needle
color: ${color_green}
width: 3
r_mod: 10
value: 340 # 34°C Should be the same as the current temperature label
# Set temperature ticks
- tick_style:
id: hvac_temp_ticks
start_value: 160
end_value: 240 # 24°C Should be the same as the set temperature label
color_start: ${color_red}
color_end: ${color_red}
width: 2
# Meter inner Ring only for visual effect
- obj:
id: meter_ring_inner
height: 300
width: 300
y: -20
radius: 150
align: CENTER
border_width: 12
border_color: ${color_bg}
bg_color: ${color_bg_alt}
# Set temperature knob, should change with the set temperature ticks
- arc:
id: hvac_temp_knob
hidden: false
align: CENTER
arc_opa: TRANSP
adjustable: true
value: 240 # 24°C (24x10) Should be the same as the set temperature needle
min_value: 160
max_value: 340
width: 335
height: 335
y: -20
arc_width: 16
change_rate: 30
indicator:
arc_opa: TRANSP
arc_width: 16
knob:
bg_color: ${color_dark_blue}
border_width: 4
border_color: ${color_white}
on_change:
then:
- if:
condition:
lambda: "return id(climape).mode == CLIMATE_MODE_COOL;"
then:
- climate.control:
id: climape
target_temperature_high: !lambda |-
return (x - 32.0) / (9.0 / 5.0);
- if:
condition:
lambda: "return id(climape).mode == CLIMATE_MODE_FAN_ONLY;"
then:
- climate.control:
id: climape
target_temperature_high: !lambda |-
return (x - 32.0) / (9.0 / 5.0);
- if:
condition:
lambda: "return id(climape).mode == CLIMATE_MODE_HEAT;"
then:
- climate.control:
id: climape
target_temperature_low: !lambda |-
return (x - 32.0) / (9.0 / 5.0);
# interval:
# - interval: 30s
# then:
# if:
# condition:
# time.has_time:
# then:
# - script.execute: time_update
# script:
# - id: time_update
# then:
# - lvgl.indicator.update:
# id: minute_hand # - arc:
# value: !lambda |- # id: current_temp_arc
# return id(time_comp).now().minute; # adjustable: true
# - lvgl.indicator.update: # min_value: 40 # 12°C (* 10 because decimals not supported)
# id: hour_hand # max_value: 95 # 34°C
# value: !lambda |- # height: 360
# auto now = id(time_comp).now(); # width: 360
# return std::fmod(now.hour, 12) * 60 + now.minute; # align: CENTER
# arc_width: 20
# indicator:
# arc_width: 20
# knob:
# width: 30
# value: !lambda |-
# auto mode = id(climape).mode;
# if (mode == CLIMATE_MODE_COOL)
# return id(climape).target_temperature_high * (9.0 / 5.0) + 32;
# on_change:
# then:
# - if:
# condition:
# lambda: "return id(climape).mode == CLIMATE_MODE_COOL;"
# then:
# - climate.control:
# id: climape
# target_temperature_high: !lambda |-
# return (x - 32.0) / (9.0 / 5.0);
# - if:
# condition:
# lambda: "return id(climape).mode == CLIMATE_MODE_FAN_ONLY;"
# then:
# - climate.control:
# id: climape
# target_temperature_high: !lambda |-
# return (x - 32.0) / (9.0 / 5.0);
# - if:
# condition:
# lambda: "return id(climape).mode == CLIMATE_MODE_HEAT;"
# then:
# - climate.control:
# id: climape
# target_temperature_low: !lambda |-
# return (x - 32.0) / (9.0 / 5.0);
- label:
id: set_temp_label
text_font: h2
text: "Loading..."
align: center
y: -90
text_color: 0xA5A5A9
- label:
id: action_label
text_font: h3
text: "Loading..."
align: center
# x: 145
y: 70
text_color: 0xA5A5A9
- label:
id: temp_label
text_font: h1
text: "Loading..."
align: center
y: -10
text_color: 0xE2E4E5