Great thank you for the explication
Is there any way to try test and include this device with the simple functions in the HA with a yaml file?
Thank you
Great thank you for the explication
Is there any way to try test and include this device with the simple functions in the HA with a yaml file?
Thank you
Brilliant; thank you for the API info.
Yeh, I figured as much but the only way I can see me figuring it out is to play with a working file and changing things to see what they do, then hopefully getting to a point I can make my own.
My goal is to have it be exclusively a climate control device so the home screen is where I select the room and then the climate. I think these files are a great starting point, but I think Iâm going to fire up a dummy HA instance on a VM and just play with it.
If I ever get to the point of having a barebone template file, Iâll post it here.
This is brilliant!
This with the pages from atomic10âs code will get me exactly where I need to be.
Great work!
Looks good, a bit better looking than my climate interface⊠look forward to seeing the yaml you used.
Itâs done.
my custome knob climate is finish.
It allow to toggle my light.
https://youtube.com/shorts/YyUqwTbqZxg?feature=share
Thanks for youre help
Nice indeed. That display, is a card from HA or ESP specific?
esp specific.
i make it with the display icon and button
Look very nice.
Can you share the code?
How did you make the plate for the knob. It this made form an existing plate from a switch?
Yeah, Iâd like to see the yaml code - particularly for the slider/temp selector bit
Hi,
you can find my knob yaml.
Itâs a little fat
If you have any question about it, donât hesitate
script:
- id: touchscreen_display_on_script
mode: single
then:
- lambda: |-
id(mode).publish_state("lights");
- light.turn_on:
id: touchscreen_back_lighting
brightness: !lambda |-
return id(touchscreen_display_brightness).state / 100;
- sensor.rotary_encoder.set_value:
id: touchscreen_rotary
value: 0
- sensor.template.publish:
id: climate_new_temperature
state: !lambda |-
return id(climate_target_temperature).state;
- delay: 500ms
- lambda: |-
id(touchscreen_display_on).publish_state(true);
- id: touchscreen_display_off_script
mode: single
then:
- light.turn_off:
id: touchscreen_back_lighting
- lambda: |-
id(touchscreen_display_on).publish_state(false);
- script.execute:
id: climate_apply_new_temperature
- id: touchscreen_display_set_countdown
mode: single
then:
- number.set:
id: touchscreen_display_countdown
value: !lambda |-
return id(touchscreen_display_delay).state;
- id: touchscreen_display_change_mode
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && id(touchscreen_button_freeze_countdown).state == 0;
then:
- lambda: |-
if (strcmp(id(mode).state.c_str(), "lights") == 0) {
id(mode).publish_state("climate");
} else if (strcmp(id(mode).state.c_str(), "climate") == 0) {
id(mode).publish_state("lights");
} else {
id(mode).publish_state("lights");
}
- id: change_display_page
mode: single
then:
- display.page.show: !lambda |-
if (strcmp(id(mode).state.c_str(), "lights") == 0) {
return id(lights);
} else if (strcmp(id(mode).state.c_str(), "climate") == 0) {
return id(climate);
} else {
return id(lights);
}
- component.update: touchscreen_lcd
- id: lights_toggle_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state;
then:
- homeassistant.service:
service: switch.toggle
data:
entity_id: switch.lumieres_salle_a_manger
- delay: 500ms
- id: climate_apply_new_temperature
mode: single
then:
- delay: 500ms
- if:
condition:
- lambda: |-
return !id(touchscreen_display_on).state && id(climate_new_temperature).state >= 10;
then:
- homeassistant.service:
service: climate.set_temperature
data:
entity_id: climate.thermostat_principal
temperature: !lambda |-
return id(climate_new_temperature).state;
- id: climate_new_temperature_auto_mode
mode: single
then:
- lambda: |-
if (id(climate_new_temperature).state <= id(climate_away_temperature).state) {
id(climate_away_script).execute();
} else if (id(climate_new_temperature).state > id(climate_away_temperature).state && id(climate_new_temperature).state <= id(climate_sleep_temperature).state) {
id(climate_sleep_script).execute();
} else if (id(climate_new_temperature).state > id(climate_sleep_temperature).state && id(climate_new_temperature).state <= id(climate_home_temperature).state) {
id(climate_home_script).execute();
} else if (id(climate_new_temperature).state > id(climate_home_temperature).state) {
id(climate_comfort_script).execute();
}
- id: climate_new_temperature_up_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- number.set:
id: touchscreen_button_freeze_countdown
value: 2
- lambda: |-
return id(climate_new_temperature).publish_state(id(climate_new_temperature).state + 0.5);
- lambda: |-
return id(climate_new_temperature).publish_state((id(climate_new_temperature).state < id(climate_max_temperature).state) ? id(climate_new_temperature).state : id(climate_max_temperature).state);
- script.execute:
id: climate_new_temperature_auto_mode
- component.update: touchscreen_lcd
- id: climate_new_temperature_down_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- number.set:
id: touchscreen_button_freeze_countdown
value: 2
- lambda: |-
return id(climate_new_temperature).publish_state(id(climate_new_temperature).state - 0.5);
- lambda: |-
return id(climate_new_temperature).publish_state((id(climate_new_temperature).state > id(climate_min_temperature).state) ? id(climate_new_temperature).state : id(climate_min_temperature).state);
- script.execute:
id: climate_new_temperature_auto_mode
- component.update: touchscreen_lcd
- id: climate_away_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- text_sensor.template.publish:
id: climate_mode
state: away
- id: climate_sleep_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- text_sensor.template.publish:
id: climate_mode
state: sleep
- id: climate_home_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- text_sensor.template.publish:
id: climate_mode
state: home
- id: climate_comfort_script
mode: single
then:
- if:
condition:
- lambda: |-
return id(touchscreen_display_on).state && (strcmp(id(mode).state.c_str(), "climate") == 0);
then:
- text_sensor.template.publish:
id: climate_mode
state: comfort
- number.set:
id: climate_comfort_countdown
value: !lambda |-
return id(climate_comfort_duration).state * 60;
- id: climate_runtime
mode: single
then:
- if:
condition:
- lambda: |-
return !id(touchscreen_display_on).state && id(climate_comfort_countdown).state == 0 && strcmp(id(climate_mode).state.c_str(), "away") != 0;
then:
- lambda: |-
// Variables
float hours = id(homeassistant_time).now().hour;
float minutes = id(homeassistant_time).now().minute;
hours = hours + minutes / 60;
int day = id(homeassistant_time).now().day_of_week;
// Change climate mode
if (hours > 6 and hours < 20 and (day == 1 or day == 2 or hours < 8.5 or hours > 15.5)) {
id(climate_mode).publish_state("home");
} else {
id(climate_mode).publish_state("sleep");
}
font:
- file: "gfonts://Roboto"
id: roboto20
size: 20
- file: "gfonts://Roboto"
id: roboto40
size: 40
#############
# Variables #
#############
time:
- platform: homeassistant
id: homeassistant_time
on_time:
- seconds: 0
minutes: /1
then:
- script.execute:
id: climate_runtime
- seconds: /1
then:
- if:
condition:
- number.in_range:
id: touchscreen_display_countdown
above: 1
then:
- number.decrement:
id: touchscreen_display_countdown
cycle: false
- if:
condition:
- number.in_range:
id: touchscreen_button_freeze_countdown
above: 1
then:
- number.decrement:
id: touchscreen_button_freeze_countdown
cycle: false
- if:
condition:
- number.in_range:
id: climate_comfort_countdown
above: 1
then:
- number.decrement:
id: climate_comfort_countdown
cycle: false
number:
# Public
- platform: template
id: touchscreen_display_delay
name: Durée rétro-éclairage
icon: mdi:wrench-clock
optimistic: true
min_value: 0
max_value: 60
step: 5
initial_value: 10
- platform: template
id: touchscreen_display_brightness
name: Luminosité
icon: mdi:brightness-6
optimistic: true
min_value: 0
max_value: 100
step: 5
initial_value: 50
- platform: template
id: climate_away_temperature
name: Température du mode "Absent"
icon: mdi:thermometer-low
optimistic: true
min_value: 10
max_value: 22
step: 1
initial_value: 10
- platform: template
id: climate_sleep_temperature
name: Température du mode "Couché"
icon: mdi:thermometer
optimistic: true
min_value: 10
max_value: 22
step: 1
initial_value: 15
- platform: template
id: climate_home_temperature
name: Température du mode "Maison"
icon: mdi:thermometer
optimistic: true
min_value: 10
max_value: 22
step: 1
initial_value: 19.5
- platform: template
id: climate_comfort_duration
name: Durée du mode "Boost"
icon: mdi:wrench-clock
optimistic: true
min_value: 30
max_value: 120
step: 5
initial_value: 60
- platform: template
id: climate_comfort_temperature
name: Température du mode "Boost"
icon: mdi:thermometer-high
optimistic: true
min_value: 10
max_value: 22
step: 1
initial_value: 22
# Private
- platform: template
id: touchscreen_display_countdown
internal: true
optimistic: true
min_value: 0
max_value: 60
step: 1
initial_value: 1
on_value_range:
- above: 1
then:
- script.execute:
id: touchscreen_display_on_script
- below: 0
then:
- script.execute:
id: touchscreen_display_off_script
- platform: template
id: touchscreen_button_freeze_countdown
internal: true
optimistic: true
min_value: 0
max_value: 2
step: 1
initial_value: 1
- platform: template
id: pi
internal: true
optimistic: true
min_value: 3.14159265359
max_value: 3.14159265360
step: 0.00000000001
initial_value: 3.14159265359
- platform: template
id: climate_icon_distance_radius
internal: true
optimistic: true
min_value: 0
max_value: 1
step: 0.01
initial_value: 0.6
- platform: template
id: climate_icon_separation_angle
internal: true
optimistic: true
min_value: 30
max_value: 60
step: 1
initial_value: 55
- platform: template
id: climate_bottom_gauge_angle
internal: true
optimistic: true
min_value: 30
max_value: 180
step: 1
initial_value: 70
- platform: template
id: climate_temperature_distance_from_center
internal: true
optimistic: true
min_value: 0
max_value: 40
step: 1
initial_value: 20
- platform: template
id: climate_comfort_countdown
internal: true
optimistic: true
min_value: 0
max_value: 10000
step: 1
initial_value: 1
############
# Settings #
############
external_components:
- source: github://dgaust/esphome@gc9a01
components:
- gc9a01
- ft3267
refresh: 0s
- source: "github://pr#6096"
components:
- display
refresh: 0s
spi:
mosi_pin: GPIO5
clk_pin: GPIO6
i2c:
- id: bus_internal
sda: GPIO11
scl: GPIO12
- id: bus_porta
sda: 13
scl: 15
display:
- platform: gc9a01
id: touchscreen_lcd
reset_pin: GPIO8
cs_pin: GPIO7
dc_pin: GPIO4
rotation: 90
update_interval: 1h
pages:
- id: lights
lambda: |-
// Variables
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth / 2;
it
.image(
halfscreenwidth,
halfscreenheight,
lights_icon,
ImageAlign::CENTER,
(id(lights_switch).state) ? id(activated_button_color) : id(desactivated_button_color));
// Display the swith menu
it.filled_circle(halfscreenwidth -6, 225, 3);
it.circle(halfscreenwidth +6, 225, 3);
- id: climate
lambda: |-
// Variables
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth / 2;
float currentTemperatureCircleAngle = (360 - id(climate_bottom_gauge_angle).state) * ((id(climate_current_temperature).state - id(climate_min_temperature).state) / (id(climate_max_temperature).state - id(climate_min_temperature).state));
float targetTemperatureCircleAngle = (360 - id(climate_bottom_gauge_angle).state) * ((id(climate_new_temperature).state - id(climate_min_temperature).state) / (id(climate_max_temperature).state - id(climate_min_temperature).state));
float startAngle = id(climate_bottom_gauge_angle).state / 2;
float firstStepAngle = (currentTemperatureCircleAngle < targetTemperatureCircleAngle) ? currentTemperatureCircleAngle : targetTemperatureCircleAngle;
float secondStepAngle = (currentTemperatureCircleAngle > targetTemperatureCircleAngle) ? firstStepAngle : targetTemperatureCircleAngle;
float endAngle = 360 - id(climate_bottom_gauge_angle).state;
// Display temperature arc circle
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle) * id(pi).state / 180),
5,
id(dark_orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + endAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + endAngle) * id(pi).state / 180),
5,
id(gray));
for (int i = startAngle; i <= startAngle + firstStepAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(dark_orange));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(dark_orange));
}
for (int i = startAngle + firstStepAngle; i <= startAngle + secondStepAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(orange));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(orange));
}
for (int i = startAngle + secondStepAngle; i <= startAngle + endAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(gray));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(gray));
}
// Current temperature round
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + currentTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + currentTemperatureCircleAngle) * id(pi).state / 180),
2,
id(white));
// Start of the second step
if (firstStepAngle < secondStepAngle) {
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + firstStepAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + firstStepAngle) * id(pi).state / 180),
5,
id(orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + firstStepAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + firstStepAngle) * id(pi).state / 180),
2,
id(dark_orange));
}
// Target temperature round
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
7,
id(orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
5,
id(white));
// Dislay icons
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 - 1.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 - 1.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_away_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "away") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 - 0.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 - 0.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_sleep_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "sleep") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 + 0.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 + 0.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_home_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "home") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 + 1.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 + 1.5 * id(climate_icon_separation_angle).state)*id(pi).state / 180),
climate_comfort_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "comfort") == 0) ? id(activated_button_color) : id(desactivated_button_color));
// Display temperature
it.printf(halfscreenwidth, halfscreenheight - id(climate_temperature_distance_from_center).state, id(roboto40), TextAlign::TOP_CENTER, "%2.1f", id(climate_new_temperature).state, id(white));
it.printf(halfscreenwidth, halfscreenheight - id(climate_temperature_distance_from_center).state + 40, id(roboto20), TextAlign::TOP_CENTER, "%2.1f", id(climate_current_temperature).state, id(white));
it
.image(
halfscreenwidth - 40,
halfscreenheight - id(climate_temperature_distance_from_center).state + 42,
climate_thermometer_icon,
ImageAlign::TOP_LEFT,
id(white));
it
.image(
halfscreenwidth + 20,
halfscreenheight - id(climate_temperature_distance_from_center).state + 42,
climate_celcius_icon,
ImageAlign::TOP_LEFT,
id(white));
it
.image(
halfscreenwidth,
halfscreenheight - id(climate_temperature_distance_from_center).state + 92,
climate_fire_icon,
ImageAlign::CENTER,
(id(climate_new_temperature).state > id(climate_current_temperature).state) ? id(red) : id(gray));
// Display the swith menu
it.circle(halfscreenwidth - 6, 225, 3);
it.filled_circle(halfscreenwidth + 6, 225, 3);
touchscreen:
platform: ft3267
i2c_id: bus_internal
on_update:
- then:
- script.execute:
id: touchscreen_display_set_countdown
binary_sensor:
- platform: gpio
pin: GPIO42
name: Bouton
internal: true
filters:
- invert:
on_state:
- then:
- script.execute:
id: touchscreen_display_set_countdown
on_press:
- then:
- script.execute:
id: touchscreen_display_change_mode
- platform: template
id: touchscreen_display_on
internal: true
- platform: touchscreen
id: lights_toggle_touchscreen_button
internal: true
x_min: 85
x_max: 165
y_min: 85
y_max: 165
page_id: lights
on_state:
then:
- script.execute:
id: lights_toggle_script
- platform: homeassistant
id: lights_switch
entity_id: switch.lumieres_salle_a_manger
on_state:
then:
- component.update: touchscreen_lcd
- platform: touchscreen
id: climate_away_touchscreen_button
internal: true
x_min: 31
x_max: 71
y_min: 95
y_max: 135
page_id: climate
on_state:
then:
- script.execute:
id: climate_away_script
- sensor.template.publish:
id: climate_new_temperature
state: !lambda |-
return id(climate_away_temperature).state;
- platform: touchscreen
id: climate_sleep_touchscreen_button
internal: true
x_min: 70
x_max: 110
y_min: 38
y_max: 78
page_id: climate
on_state:
then:
- script.execute:
id: climate_sleep_script
- sensor.template.publish:
id: climate_new_temperature
state: !lambda |-
return id(climate_sleep_temperature).state;
- platform: touchscreen
id: climate_home_touchscreen_button
internal: true
x_min: 140
x_max: 180
y_min: 38
y_max: 78
page_id: climate
on_state:
then:
- script.execute:
id: climate_home_script
- sensor.template.publish:
id: climate_new_temperature
state: !lambda |-
return id(climate_home_temperature).state;
- platform: touchscreen
id: climate_comfort_touchscreen_button
internal: true
x_min: 179
x_max: 219
y_min: 95
y_max: 135
page_id: climate
on_state:
then:
- script.execute:
id: climate_comfort_script
- sensor.template.publish:
id: climate_new_temperature
state: !lambda |-
return id(climate_comfort_temperature).state;
sensor:
- platform: rotary_encoder
id: touchscreen_rotary
internal: true
pin_a:
number: GPIO40
mode:
input: true
pullup: true
pin_b:
number: GPIO41
mode:
input: true
pullup: true
accuracy_decimals: 0
on_value:
- then:
- script.execute:
id: touchscreen_display_set_countdown
on_clockwise:
- then:
- script.execute:
id: climate_new_temperature_up_script
on_anticlockwise:
- then:
- script.execute:
id: climate_new_temperature_down_script
- platform: template
id: climate_new_temperature
name: Nouvelle temperature
- platform: homeassistant
id: climate_target_temperature
entity_id: climate.thermostat_principal
attribute: temperature
on_value:
then:
- component.update: touchscreen_lcd
- platform: homeassistant
id: climate_current_temperature
entity_id: climate.thermostat_principal
attribute: current_temperature
on_value:
then:
- component.update: touchscreen_lcd
- platform: homeassistant
id: climate_min_temperature
entity_id: climate.thermostat_principal
attribute: min_temp
on_value:
then:
- component.update: touchscreen_lcd
- platform: homeassistant
id: climate_max_temperature
entity_id: climate.thermostat_principal
attribute: max_temp
on_value:
then:
- component.update: touchscreen_lcd
text_sensor:
- platform: template
id: climate_mode
name: Mode du thermostat
on_value:
then:
- component.update: touchscreen_lcd
- platform: template
id: mode
name: Mode
on_value:
then:
- script.execute:
id: change_display_page
button:
- platform: template
id: climate_away_button
name: Mode Absent
on_press:
then:
- script.execute:
id: climate_away_script
- platform: template
id: climate_sleep_button
name: Mode Couché
on_press:
then:
- script.execute:
id: climate_sleep_script
- platform: template
id: climate_home_button
name: Mode Maison
on_press:
then:
- script.execute:
id: climate_home_script
- platform: template
id: climate_comfort_button
name: Mode Boost
on_press:
then:
- script.execute:
id: climate_comfort_script
light:
- platform: monochromatic
id: touchscreen_back_lighting
name: Retro eclairage
internal: true
output: touchscreen_back_lighting_output
default_transition_length: 500ms
output:
- id: touchscreen_back_lighting_output
platform: ledc
pin: GPIO09
max_power: 1
min_power: 0
````Preformatted text`
I make a 3d adaptateur for the french âleGrandâ switch plate.
And i print it with my 3d printer
A minor bug in that code I think is that when the current temperature is above the setpoint temperature (i.e. cooling rather than heating), the gap is not shown. The bar is only shown when the current temp is less than the target temp.
Iâm sure itâs an easy fix, but havenât looked at it in great detail - it took me a little while to figure out what was going on in the first place
Nope i have the same behavior than the home assistant climate.
I make a capture of my video when the target temperature is lesser then the actual temperature.
It was colours⊠have to remember to change the hex code when you tweak the colours
Anyone using this as kitchen timer?
Hi @Squall290586 ,
Thnx for the code
I got errors on the colors. Do you have someting in your config that you can share
(venv) [richard@X360-G8 ~]$ esphome run m5dial.yaml
INFO ESPHome 2024.2.2
INFO Reading configuration m5dial.yaml...
INFO Updating https://github.com/dgaust/esphome.git@gc9a01
INFO Updating https://github.com/esphome/esphome.git@pull/6096/head
INFO Detected timezone 'Europe/Amsterdam'
Failed config
display.gc9a01: [source m5dial.yaml:494]
platform: gc9a01
id: touchscreen_lcd
reset_pin:
number: 8
mode:
output: True
input: False
open_drain: False
pullup: False
pulldown: False
inverted: False
ignore_strapping_warning: False
drive_strength: 20.0
cs_pin:
number: 7
mode:
output: True
input: False
open_drain: False
pullup: False
pulldown: False
inverted: False
ignore_strapping_warning: False
drive_strength: 20.0
dc_pin:
number: 4
mode:
output: True
input: False
open_drain: False
pullup: False
pulldown: False
inverted: False
ignore_strapping_warning: False
drive_strength: 20.0
rotation: 90
update_interval: 1h
pages: [source m5dial.yaml:501]
- id: lights
Couldn't find ID 'activated_button_color'. Please check you have defined an ID with that name in your configuration.
lambda: !lambda |-
// Variables
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth / 2;
it
.image(
halfscreenwidth,
halfscreenheight,
lights_icon,
ImageAlign::CENTER,
(id(lights_switch).state) ? id(activated_button_color) : id(desactivated_button_color));
// Display the swith menu
it.filled_circle(halfscreenwidth -6, 225, 3);
it.circle(halfscreenwidth +6, 225, 3);
- [source m5dial.yaml:501]
id: climate
Couldn't find ID 'dark_orange'. Please check you have defined an ID with that name in your configuration.
lambda: !lambda |-
// Variables
float screenheight = it.get_height();
float screenwidth = it.get_width();
float halfscreenheight = screenheight / 2;
float halfscreenwidth = screenwidth / 2;
float currentTemperatureCircleAngle = (360 - id(climate_bottom_gauge_angle).state) * ((id(climate_current_temperature).state - id(climate_min_temperature).state) / (id(climate_max_temperature).state - id(climate_min_temperature).state));
float targetTemperatureCircleAngle = (360 - id(climate_bottom_gauge_angle).state) * ((id(climate_new_temperature).state - id(climate_min_temperature).state) / (id(climate_max_temperature).state - id(climate_min_temperature).state));
float startAngle = id(climate_bottom_gauge_angle).state / 2;
float firstStepAngle = (currentTemperatureCircleAngle < targetTemperatureCircleAngle) ? currentTemperatureCircleAngle : targetTemperatureCircleAngle;
float secondStepAngle = (currentTemperatureCircleAngle > targetTemperatureCircleAngle) ? firstStepAngle : targetTemperatureCircleAngle;
float endAngle = 360 - id(climate_bottom_gauge_angle).state;
// Display temperature arc circle
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle) * id(pi).state / 180),
5,
id(dark_orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + endAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + endAngle) * id(pi).state / 180),
5,
id(gray));
for (int i = startAngle; i <= startAngle + firstStepAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(dark_orange));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(dark_orange));
}
for (int i = startAngle + firstStepAngle; i <= startAngle + secondStepAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(orange));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(orange));
}
for (int i = startAngle + secondStepAngle; i <= startAngle + endAngle; i++) {
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
id(gray));
it
.filled_triangle(
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + i) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + i) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 5) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 5) * sin((90 + (i + 1)) * id(pi).state / 180),
halfscreenwidth + (halfscreenwidth - 15) * cos((90 + (i + 1)) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 15) * sin((90 + (i + 1)) * id(pi).state / 180),
id(gray));
}
// Current temperature round
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + currentTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + currentTemperatureCircleAngle) * id(pi).state / 180),
2,
id(white));
// Start of the second step
if (firstStepAngle < secondStepAngle) {
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + firstStepAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + firstStepAngle) * id(pi).state / 180),
5,
id(orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + firstStepAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + firstStepAngle) * id(pi).state / 180),
2,
id(dark_orange));
}
// Target temperature round
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
7,
id(orange));
it
.filled_circle(
halfscreenwidth + (halfscreenwidth - 10) * cos((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
halfscreenheight + (halfscreenheight - 10) * sin((90 + startAngle + targetTemperatureCircleAngle) * id(pi).state / 180),
5,
id(white));
// Dislay icons
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 - 1.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 - 1.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_away_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "away") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 - 0.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 - 0.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_sleep_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "sleep") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 + 0.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 + 0.5 * id(climate_icon_separation_angle).state) *id(pi).state / 180),
climate_home_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "home") == 0) ? id(activated_button_color) : id(desactivated_button_color));
it
.image(
halfscreenwidth + halfscreenwidth * id(climate_icon_distance_radius).state * cos((-90 + 1.5 * id(climate_icon_separation_angle).state) * id(pi).state / 180),
halfscreenheight + halfscreenheight * id(climate_icon_distance_radius).state * sin((-90 + 1.5 * id(climate_icon_separation_angle).state)*id(pi).state / 180),
climate_comfort_mode_icon,
ImageAlign::CENTER,
(strcmp(id(climate_mode).state.c_str(), "comfort") == 0) ? id(activated_button_color) : id(desactivated_button_color));
// Display temperature
it.printf(halfscreenwidth, halfscreenheight - id(climate_temperature_distance_from_center).state, id(roboto40), TextAlign::TOP_CENTER, "%2.1f", id(climate_new_temperature).state, id(white));
it.printf(halfscreenwidth, halfscreenheight - id(climate_temperature_distance_from_center).state + 40, id(roboto20), TextAlign::TOP_CENTER, "%2.1f", id(climate_current_temperature).state, id(white));
it
.image(
halfscreenwidth - 40,
halfscreenheight - id(climate_temperature_distance_from_center).state + 42,
climate_thermometer_icon,
ImageAlign::TOP_LEFT,
id(white));
it
.image(
halfscreenwidth + 20,
halfscreenheight - id(climate_temperature_distance_from_center).state + 42,
climate_celcius_icon,
ImageAlign::TOP_LEFT,
id(white));
it
.image(
halfscreenwidth,
halfscreenheight - id(climate_temperature_distance_from_center).state + 92,
climate_fire_icon,
ImageAlign::CENTER,
(id(climate_new_temperature).state > id(climate_current_temperature).state) ? id(red) : id(gray));
// Display the swith menu
it.circle(halfscreenwidth - 6, 225, 3);
it.filled_circle(halfscreenwidth + 6, 225, 3); [source m5dial.yaml:522]
auto_clear_enabled: True
width: 240
height: 240
offset_y: 0
offset_x: 0
eight_bit_color: False
Couldn't find ID 'dark_orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'dark_orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'gray'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'gray'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'dark_orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'orange'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'activated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'desactivated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'activated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'desactivated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'activated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'desactivated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'activated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'desactivated_button_color'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'white'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'red'. Please check you have defined an ID with that name in your configuration.
Couldn't find ID 'gray'. Please check you have defined an ID with that name in your configuration.
The warnings relate to color definitions, make sure you define colours and theyâre correctly referenced.
I added the colors. Now I have some other errors
m5dial.yaml: In lambda function:
m5dial.yaml:526:11: error: 'lights_icon' was not declared in this scope
m5dial.yaml:526:11: note: suggested alternative: 'lights'
m5dial.yaml: In lambda function:
m5dial.yaml:666:11: error: 'climate_away_mode_icon' was not declared in this scope
m5dial.yaml:666:11: note: suggested alternative: 'climate_away_button'
m5dial.yaml:673:11: error: 'climate_sleep_mode_icon' was not declared in this scope
Compiling .pioenvs/m5dial/lib445/ESPmDNS/ESPmDNS.cpp.o
m5dial.yaml:673:11: note: suggested alternative: 'climate_sleep_button'
m5dial.yaml:680:11: error: 'climate_home_mode_icon' was not declared in this scope
m5dial.yaml:680:11: note: suggested alternative: 'climate_home_button'
m5dial.yaml:687:11: error: 'climate_comfort_mode_icon' was not declared in this scope
m5dial.yaml:687:11: note: suggested alternative: 'climate_comfort_duration'
m5dial.yaml:698:11: error: 'climate_thermometer_icon' was not declared in this scope
m5dial.yaml:698:11: note: suggested alternative: 'climate_home_button'
m5dial.yaml:705:11: error: 'climate_celcius_icon' was not declared in this scope
m5dial.yaml:705:11: note: suggested alternative: 'climate_comfort_button'
m5dial.yaml:712:11: error: 'climate_fire_icon' was not declared in this scope
m5dial.yaml:712:11: note: suggested alternative: 'climate_home_button'
Compiling .pioenvs/m5dial/lib1c7/Update/HttpsOTAUpdate.cpp.o
Compiling .pioenvs/m5dial/lib1c7/Update/Updater.cpp.o
Compiling .pioenvs/m5dial/libf34/SPI/SPI.cpp.o
Compiling .pioenvs/m5dial/liba4d/Wire/Wire.cpp.o
Compiling .pioenvs/m5dial/FrameworkArduino/Esp.cpp.o
Archiving .pioenvs/m5dial/lib445/libESPmDNS.a
Archiving .pioenvs/m5dial/libf34/libSPI.a
Archiving .pioenvs/m5dial/lib405/libWiFi.a
Indexing .pioenvs/m5dial/lib445/libESPmDNS.a
Indexing .pioenvs/m5dial/libf34/libSPI.a
Indexing .pioenvs/m5dial/lib405/libWiFi.a
Compiling .pioenvs/m5dial/FrameworkArduino/FirmwareMSC.cpp.o
Compiling .pioenvs/m5dial/FrameworkArduino/FunctionalInterrupt.cpp.o
Compiling .pioenvs/m5dial/FrameworkArduino/HWCDC.cpp.o
Compiling .pioenvs/m5dial/FrameworkArduino/HardwareSerial.cpp.o
*** [.pioenvs/m5dial/src/main.cpp.o] Error 1
I would suggest if youâre trying to implement this, you investigate the errors yourself. Use it as a good learning opportunity.
The error messages are pretty clear on what the issue is.
Hello,
Iâm currently attempting to compile the YAML code for my M5Dial. Iâve referenced the icons using the image component and MDI reference.
Following the ESPHome instructions, Iâve installed Pillow and CairoSVG add-ons in my Python environment. However, Iâm encountering compilation errors due to the following:
no library called âcairoâ was found
no library called âlibcairo-2â was found
Iâve tried searching for a solution, but so far, I havenât had any luck. Does anyone have an idea how to fix this issue? Iâm using ESPHome in a Windows environment.
EDIT 12-03-2024
I overcome the problem of missing DLL in my esphome compilation toolchain downloading the icon bmp image locally and not usign MDI reference in the Yaml
Now the code compiles but the result on the M5dial is messed up and inconsistent
@Squall290586 Would you mind to share the complete code so I can play around and try to compile correclty ?
Thank you, Davide