Compare commits
18
Commits
1d795b09a8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332d9590d9 | ||
|
|
1130f3ffa6 | ||
|
|
cf804893d5 | ||
|
|
24e502867c | ||
|
|
d0785061c5 | ||
|
|
1eedfa1fab | ||
|
|
18ed74206a | ||
|
|
c1d0778496 | ||
|
|
196ade4c3a | ||
|
|
5610266a62 | ||
|
|
9dcdaadae6 | ||
|
|
80860ffc1d | ||
|
|
5a82ddb97d | ||
|
|
f9c0bbbd6a | ||
|
|
e949ebacc7 | ||
|
|
e5d4d6abf2 | ||
|
|
492566f6b5 | ||
|
|
d84bb62549 |
+1
-1
@@ -1 +1 @@
|
||||
2026.9.3
|
||||
2026.11.0
|
||||
+200
-77
@@ -28,18 +28,18 @@
|
||||
alias: TV Anti-Reflexion
|
||||
description: Schaltet Wohnzimmer hinten Ambient Light aus, um Reflexionen im TV
|
||||
zu verhindern.
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: media_player.lg_webos_smart_tv
|
||||
triggers:
|
||||
- entity_id: media_player.lg_webos_smart_tv
|
||||
from: 'off'
|
||||
to: 'on'
|
||||
condition: []
|
||||
action:
|
||||
- service: light.turn_off
|
||||
target:
|
||||
device_id: 6dcbd87b459412144bddc42af3ae8b83
|
||||
trigger: state
|
||||
conditions: []
|
||||
actions:
|
||||
- target:
|
||||
device_id: 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
data:
|
||||
transition: 2
|
||||
action: light.turn_off
|
||||
mode: single
|
||||
- id: '1623526683767'
|
||||
alias: Licht bei Sonnenaufgang ausschalten
|
||||
@@ -56,14 +56,14 @@
|
||||
area_id:
|
||||
- schlafzimmer
|
||||
device_id:
|
||||
- 53a27edf581fcf40f3bc8b253b1ee369
|
||||
- 4e8211f6c1ad515c79d7a26f3d7c9a27
|
||||
- c64e7c3dcda7f1c23e456959f2c60f39
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 98a9eef603f8c8ae6200c4834a3dbf4f
|
||||
- 32d15eec63fe68d5770813d47da53657
|
||||
- 9f42805af5b7e423023595390342b9ac
|
||||
- f39e4bf25a3bee97f237b0d7ddd64493
|
||||
- ca8b65e4a22153f953a88d7bb1efb36b
|
||||
- 2b27a605524f6617ed1f71f98e0eb74d
|
||||
- 2a5b866b0062c298950cb0bd03ec14eb
|
||||
- 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
- 23aeb31cedaf3ff0bf14d4a05fc779da
|
||||
- fb581090b15a027cf071b99d0ea00874
|
||||
- a7c56c0e328765addc33be2f375778bc
|
||||
entity_id: light.kajplats_e14_cws_globe_806lm
|
||||
data:
|
||||
transition: 2
|
||||
@@ -85,17 +85,39 @@
|
||||
area_id:
|
||||
- wohnzimmer
|
||||
- kuche
|
||||
- schlafzimmer
|
||||
- kinderzimmer
|
||||
entity_id:
|
||||
- light.awtrix_528bd4_matrix
|
||||
- light.awtrix_b8658c_matrix
|
||||
- light.schlafzimmer_bett
|
||||
- light.schlafzimmer_deckenlicht
|
||||
data: {}
|
||||
action: light.turn_off
|
||||
- type: turn_off
|
||||
device_id: 6d1be741876624a70ab5b01b54c6fd6f
|
||||
device_id: 40ea5b499584568460396dc9b920beca
|
||||
entity_id: switch.kuche_musik
|
||||
domain: switch
|
||||
- action: light.turn_on
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: light.schlafzimmeruhr_display
|
||||
data:
|
||||
rgb_color:
|
||||
- 224
|
||||
- 27
|
||||
- 36
|
||||
brightness_pct: 5
|
||||
- action: button.press
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: button.schlafzimmeruhr_clear_digit_colors
|
||||
data: {}
|
||||
- action: select.select_option
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: select.schlafzimmeruhr_effect
|
||||
data:
|
||||
option: None
|
||||
- action: lock.lock
|
||||
metadata: {}
|
||||
target:
|
||||
@@ -194,23 +216,23 @@
|
||||
alias: TV Anti-Reflexion (undo)
|
||||
description: Schaltet Wohnzimmer hinten Ambient Light wieder ein, wenn vorne auch
|
||||
(noch) eingeschaltet ist.
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: media_player.lg_webos_smart_tv
|
||||
triggers:
|
||||
- entity_id: media_player.lg_webos_smart_tv
|
||||
to: 'off'
|
||||
from: 'on'
|
||||
condition:
|
||||
trigger: state
|
||||
conditions:
|
||||
- condition: device
|
||||
type: is_on
|
||||
device_id: 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
device_id: 2a5b866b0062c298950cb0bd03ec14eb
|
||||
entity_id: light.wohnzimmer_vorne
|
||||
domain: light
|
||||
action:
|
||||
- service: light.turn_on
|
||||
data:
|
||||
actions:
|
||||
- data:
|
||||
transition: 2
|
||||
target:
|
||||
device_id: 6dcbd87b459412144bddc42af3ae8b83
|
||||
device_id: 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
action: light.turn_on
|
||||
mode: single
|
||||
- id: '1623941937228'
|
||||
alias: Licht im Schlafzimmer zur Schlafenszeit einschalten
|
||||
@@ -228,9 +250,24 @@
|
||||
state: home
|
||||
actions:
|
||||
- target:
|
||||
device_id: 68868390eda35e969ec60a13020f2407
|
||||
device_id: ddf95b1e837e69c677460491a2600cd4
|
||||
data: {}
|
||||
action: light.turn_on
|
||||
- action: select.select_option
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: select.schlafzimmeruhr_effect
|
||||
data:
|
||||
option: None
|
||||
- action: esphome.sleepingroom_clock_set_gradient
|
||||
data:
|
||||
red1: 255
|
||||
green1: 40
|
||||
blue1: 0
|
||||
red2: 60
|
||||
green2: 0
|
||||
blue2: 120
|
||||
angle: 0
|
||||
- data:
|
||||
cache: true
|
||||
media_player_entity_id: media_player.home_assistant_voice_0a7c6b_media_player
|
||||
@@ -276,7 +313,7 @@
|
||||
seconds: 0
|
||||
milliseconds: 0
|
||||
- target:
|
||||
device_id: 68868390eda35e969ec60a13020f2407
|
||||
device_id: ddf95b1e837e69c677460491a2600cd4
|
||||
data: {}
|
||||
action: light.turn_on
|
||||
- action: lock.unlock
|
||||
@@ -284,6 +321,25 @@
|
||||
target:
|
||||
entity_id: lock.flur_wohnungstur
|
||||
data: {}
|
||||
- action: light.turn_on
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: light.schlafzimmeruhr_display
|
||||
data:
|
||||
brightness_pct: 50
|
||||
enabled: true
|
||||
- action: select.select_option
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: select.schlafzimmeruhr_effect
|
||||
data:
|
||||
option: Color Cycle
|
||||
- action: select.select_option
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: select.schlafzimmeruhr_effect_spread
|
||||
data:
|
||||
option: Per LED
|
||||
mode: single
|
||||
- id: '1624820688449'
|
||||
alias: 'Anruf: Beim Klingeln grün blinken'
|
||||
@@ -406,10 +462,9 @@
|
||||
- id: '1628972104416'
|
||||
alias: Raumduft nach einer Stunde wieder ausschalten
|
||||
description: ''
|
||||
trigger:
|
||||
- platform: device
|
||||
type: turned_on
|
||||
device_id: 5a08ac4c3b3893b540a9934fa92dccfa
|
||||
triggers:
|
||||
- type: turned_on
|
||||
device_id: b29693c2517f4d6538da9fe7e5e5a262
|
||||
entity_id: switch.flur_raumduft
|
||||
domain: switch
|
||||
for:
|
||||
@@ -417,10 +472,11 @@
|
||||
minutes: 0
|
||||
seconds: 0
|
||||
milliseconds: 0
|
||||
condition: []
|
||||
action:
|
||||
trigger: device
|
||||
conditions: []
|
||||
actions:
|
||||
- type: turn_off
|
||||
device_id: 5a08ac4c3b3893b540a9934fa92dccfa
|
||||
device_id: b29693c2517f4d6538da9fe7e5e5a262
|
||||
entity_id: switch.flur_raumduft
|
||||
domain: switch
|
||||
mode: single
|
||||
@@ -439,7 +495,7 @@
|
||||
state: home
|
||||
actions:
|
||||
- type: turn_on
|
||||
device_id: 5a08ac4c3b3893b540a9934fa92dccfa
|
||||
device_id: b29693c2517f4d6538da9fe7e5e5a262
|
||||
entity_id: switch.flur_raumduft
|
||||
domain: switch
|
||||
- data: {}
|
||||
@@ -472,20 +528,20 @@
|
||||
data: {}
|
||||
action: light.turn_off
|
||||
- type: turn_off
|
||||
device_id: 6d1be741876624a70ab5b01b54c6fd6f
|
||||
device_id: 40ea5b499584568460396dc9b920beca
|
||||
entity_id: switch.kuche_musik
|
||||
domain: switch
|
||||
- type: turn_off
|
||||
device_id: 5a08ac4c3b3893b540a9934fa92dccfa
|
||||
device_id: b29693c2517f4d6538da9fe7e5e5a262
|
||||
entity_id: switch.flur_raumduft
|
||||
domain: switch
|
||||
- action: light.turn_off
|
||||
metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id:
|
||||
- 231cd7ec37e32690faf126105b03ee02
|
||||
- a6d24e7d93c114723bf525e9eac1c2df
|
||||
entity_id:
|
||||
- light.awtrix_b8658c_matrix
|
||||
- light.awtrix_528bd4_matrix
|
||||
- action: lock.lock
|
||||
metadata: {}
|
||||
target:
|
||||
@@ -521,8 +577,8 @@
|
||||
action: script.moodlight_orange_plasma
|
||||
- target:
|
||||
device_id:
|
||||
- 53a27edf581fcf40f3bc8b253b1ee369
|
||||
- 5abbb43e5d015ee75c7c900240b4329e
|
||||
- f39e4bf25a3bee97f237b0d7ddd64493
|
||||
data:
|
||||
brightness_pct: 70
|
||||
color_temp_kelvin: 2000
|
||||
@@ -885,6 +941,7 @@
|
||||
entity_id:
|
||||
- light.awtrix_b8658c_matrix
|
||||
- light.awtrix_528bd4_matrix
|
||||
- light.schlafzimmeruhr_display
|
||||
action: light.turn_on
|
||||
mode: single
|
||||
- id: '1700482951854'
|
||||
@@ -905,8 +962,8 @@
|
||||
action: script.moodlight_xmas
|
||||
- target:
|
||||
device_id:
|
||||
- 53a27edf581fcf40f3bc8b253b1ee369
|
||||
- 5abbb43e5d015ee75c7c900240b4329e
|
||||
- f39e4bf25a3bee97f237b0d7ddd64493
|
||||
data:
|
||||
brightness_pct: 70
|
||||
color_temp_kelvin: 2000
|
||||
@@ -933,15 +990,15 @@
|
||||
area_id:
|
||||
- schlafzimmer
|
||||
device_id:
|
||||
- 53a27edf581fcf40f3bc8b253b1ee369
|
||||
- 4e8211f6c1ad515c79d7a26f3d7c9a27
|
||||
- c64e7c3dcda7f1c23e456959f2c60f39
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 98a9eef603f8c8ae6200c4834a3dbf4f
|
||||
- 32d15eec63fe68d5770813d47da53657
|
||||
- 9f42805af5b7e423023595390342b9ac
|
||||
- 5abbb43e5d015ee75c7c900240b4329e
|
||||
- f39e4bf25a3bee97f237b0d7ddd64493
|
||||
- ca8b65e4a22153f953a88d7bb1efb36b
|
||||
- 2b27a605524f6617ed1f71f98e0eb74d
|
||||
- 2a5b866b0062c298950cb0bd03ec14eb
|
||||
- 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
- 23aeb31cedaf3ff0bf14d4a05fc779da
|
||||
- fb581090b15a027cf071b99d0ea00874
|
||||
- a7c56c0e328765addc33be2f375778bc
|
||||
data:
|
||||
transition: 2
|
||||
action: light.turn_off
|
||||
@@ -965,15 +1022,15 @@
|
||||
- data: {}
|
||||
target:
|
||||
device_id:
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 2a5b866b0062c298950cb0bd03ec14eb
|
||||
- 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
action: light.turn_on
|
||||
action_button_down_short:
|
||||
- data: {}
|
||||
target:
|
||||
device_id:
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- 5a0caeab0be8e10747c74dcf5dd9ced1
|
||||
- 2a5b866b0062c298950cb0bd03ec14eb
|
||||
action: light.turn_off
|
||||
action_button_left_short:
|
||||
- data: {}
|
||||
@@ -989,14 +1046,14 @@
|
||||
data: {}
|
||||
action_button_down_double:
|
||||
- target:
|
||||
device_id: 9f42805af5b7e423023595390342b9ac
|
||||
device_id: a7c56c0e328765addc33be2f375778bc
|
||||
data: {}
|
||||
action: light.turn_off
|
||||
action_button_up_double:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: 9f42805af5b7e423023595390342b9ac
|
||||
device_id: a7c56c0e328765addc33be2f375778bc
|
||||
action: light.turn_on
|
||||
button_up_double_press: true
|
||||
button_down_double_press: true
|
||||
@@ -1021,39 +1078,38 @@
|
||||
remote: bd97db2ae9b0104d50dc6a343315608b
|
||||
double_dot_single_press:
|
||||
- target:
|
||||
device_id:
|
||||
- 68868390eda35e969ec60a13020f2407
|
||||
device_id: ddf95b1e837e69c677460491a2600cd4
|
||||
data: {}
|
||||
action: light.turn_off
|
||||
double_dot_double_press:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: 3c86ddd39979139f29645308815c0271
|
||||
device_id: 824bb838dd09391e75dd7c366a571c4c
|
||||
action: light.turn_off
|
||||
single_dot_single_press:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: 68868390eda35e969ec60a13020f2407
|
||||
device_id: ddf95b1e837e69c677460491a2600cd4
|
||||
action: light.turn_on
|
||||
single_dot_double_press:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: 3c86ddd39979139f29645308815c0271
|
||||
device_id: 824bb838dd09391e75dd7c366a571c4c
|
||||
action: light.turn_on
|
||||
double_dot_long_press:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: b7c1c80b21406c5608e38aa0c7e7e439
|
||||
device_id: 64daf8ee59e890acd2930fa91bcdeb9c
|
||||
action: switch.turn_off
|
||||
single_dot_long_press:
|
||||
- metadata: {}
|
||||
data: {}
|
||||
target:
|
||||
device_id: b7c1c80b21406c5608e38aa0c7e7e439
|
||||
device_id: 64daf8ee59e890acd2930fa91bcdeb9c
|
||||
action: switch.turn_on
|
||||
- id: '1705488817426'
|
||||
alias: Let's Encrypt
|
||||
@@ -1071,17 +1127,17 @@
|
||||
- id: '1705787842132'
|
||||
alias: 'Luftbefeuchter: Zielwert'
|
||||
description: Leicht oberhalb des Wohnungsdurchschnitts
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
minutes: /10
|
||||
condition: []
|
||||
action:
|
||||
- service: humidifier.set_humidity
|
||||
metadata: {}
|
||||
triggers:
|
||||
- minutes: /10
|
||||
trigger: time_pattern
|
||||
conditions: []
|
||||
actions:
|
||||
- metadata: {}
|
||||
data:
|
||||
humidity: '{{ int(states(''sensor.wohnungsluftfeuchtigkeit'')) + 20 }}'
|
||||
target:
|
||||
entity_id: humidifier.badezimmer
|
||||
entity_id: humidifier.badezimmer_badezimmer_ventilator_absaugung
|
||||
action: humidifier.set_humidity
|
||||
mode: single
|
||||
- id: '1708188056710'
|
||||
alias: IKEA STYRBAR - Moodlight Wohnzimmer
|
||||
@@ -1112,14 +1168,14 @@
|
||||
domain: switch
|
||||
action_button_up_double:
|
||||
- type: turn_on
|
||||
device_id: 98a9eef603f8c8ae6200c4834a3dbf4f
|
||||
device_id: 23aeb31cedaf3ff0bf14d4a05fc779da
|
||||
entity_id: fdcff3852ecc17099c953da17eb4b8a3
|
||||
domain: light
|
||||
button_up_double_press: true
|
||||
button_down_double_press: true
|
||||
action_button_down_double:
|
||||
- type: turn_off
|
||||
device_id: 98a9eef603f8c8ae6200c4834a3dbf4f
|
||||
device_id: 23aeb31cedaf3ff0bf14d4a05fc779da
|
||||
entity_id: fdcff3852ecc17099c953da17eb4b8a3
|
||||
domain: light
|
||||
action_button_up_long: []
|
||||
@@ -1390,7 +1446,7 @@
|
||||
data:
|
||||
volume_level: 0.47
|
||||
- type: turn_off
|
||||
device_id: 81d56cc472abaec2a02702def5f9cf66
|
||||
device_id: f11b54371dd442568f1642fabf649116
|
||||
entity_id: a57a56132635ff48a6d2ae8df322b385
|
||||
domain: switch
|
||||
mode: single
|
||||
@@ -1403,7 +1459,7 @@
|
||||
conditions: []
|
||||
actions:
|
||||
- type: turn_on
|
||||
device_id: 81d56cc472abaec2a02702def5f9cf66
|
||||
device_id: f11b54371dd442568f1642fabf649116
|
||||
entity_id: a57a56132635ff48a6d2ae8df322b385
|
||||
domain: switch
|
||||
mode: single
|
||||
@@ -1491,3 +1547,70 @@
|
||||
entity_id: button.touchkio_raspberrypi_refresh
|
||||
data: {}
|
||||
mode: single
|
||||
- id: '1791126911003'
|
||||
alias: 'Schlafzimmeruhr: Außentemperatur anzeigen'
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.schlafzimmeruhr_button_2
|
||||
to:
|
||||
- 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: esphome.sleepingroom_clock_show_number
|
||||
data:
|
||||
value: '{{ states(''sensor.aussentemperatur'') | round(0) | int }}'
|
||||
unit: C
|
||||
duration: 10000
|
||||
mode: single
|
||||
- id: '1791127515667'
|
||||
alias: 'Schlafzimmeruhr: Sekunden anzeigen'
|
||||
description: ''
|
||||
triggers:
|
||||
- trigger: state
|
||||
entity_id:
|
||||
- binary_sensor.schlafzimmeruhr_button_1
|
||||
to:
|
||||
- 'on'
|
||||
conditions: []
|
||||
actions:
|
||||
- action: button.press
|
||||
metadata: {}
|
||||
target:
|
||||
entity_id: button.schlafzimmeruhr_show_seconds
|
||||
data: {}
|
||||
mode: single
|
||||
- id: energymonitor_backlight_workplace_presence
|
||||
alias: 'Energy Monitor: Display nach Arbeitsplatz-Anwesenheit schalten'
|
||||
description: Schaltet die Displaybeleuchtung beim Verlassen des Arbeitsplatzes aus
|
||||
und bei der Rückkehr wieder ein.
|
||||
triggers:
|
||||
- trigger: zone
|
||||
entity_id: person.marcus_scholz
|
||||
zone: zone.arbeitsplatz_handwerker_gewerbepark
|
||||
event: leave
|
||||
id: leave
|
||||
- trigger: zone
|
||||
entity_id: person.marcus_scholz
|
||||
zone: zone.arbeitsplatz_handwerker_gewerbepark
|
||||
event: enter
|
||||
id: enter
|
||||
conditions: []
|
||||
actions:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: leave
|
||||
sequence:
|
||||
- action: light.turn_off
|
||||
target:
|
||||
entity_id: light.energy_monitor_display_backlight
|
||||
- conditions:
|
||||
- condition: trigger
|
||||
id: enter
|
||||
sequence:
|
||||
- action: light.turn_on
|
||||
target:
|
||||
entity_id: light.energy_monitor_display_backlight
|
||||
mode: restart
|
||||
|
||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import time as time_
|
||||
from esphome.components.esp32 import include_builtin_idf_component
|
||||
from esphome.const import CONF_ID, CONF_NUMBER, PLATFORM_ESP32
|
||||
|
||||
CODEOWNERS = ["@misterblack1"]
|
||||
@@ -64,6 +65,9 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# The display scan uses GPTimer, which ESPHome excludes by default.
|
||||
include_builtin_idf_component("esp_driver_gptimer")
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor, i2c, sensor
|
||||
from esphome.const import CONF_ID, ENTITY_CATEGORY_DIAGNOSTIC
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||
|
||||
ns = cg.esphome_ns.namespace("luxe_ip5306")
|
||||
LuxeIP5306 = ns.class_("LuxeIP5306", cg.PollingComponent, i2c.I2CDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LuxeIP5306),
|
||||
cv.Required("available"): binary_sensor.binary_sensor_schema(
|
||||
device_class="connectivity", entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Required("charging"): binary_sensor.binary_sensor_schema(
|
||||
device_class="battery_charging"
|
||||
),
|
||||
cv.Required("full"): binary_sensor.binary_sensor_schema(),
|
||||
cv.Required("charge_allowed"): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Required("level"): sensor.sensor_schema(
|
||||
unit_of_measurement="%", accuracy_decimals=0, device_class="battery",
|
||||
state_class="measurement", entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
cv.Required("current_limit"): sensor.sensor_schema(
|
||||
unit_of_measurement="mA", accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("10s"))
|
||||
.extend(i2c.i2c_device_schema(0x75))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
for key in ("available", "charging", "full", "charge_allowed"):
|
||||
entity = await binary_sensor.new_binary_sensor(config[key])
|
||||
cg.add(getattr(var, f"set_{key}")(entity))
|
||||
for key in ("level", "current_limit"):
|
||||
entity = await sensor.new_sensor(config[key])
|
||||
cg.add(getattr(var, f"set_{key}")(entity))
|
||||
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::luxe_ip5306 {
|
||||
|
||||
// Optional IP5306-I2C on recent Luxe boards. Never changes power settings at boot.
|
||||
class LuxeIP5306 : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_available(binary_sensor::BinarySensor *s) { available_ = s; }
|
||||
void set_charging(binary_sensor::BinarySensor *s) { charging_ = s; }
|
||||
void set_full(binary_sensor::BinarySensor *s) { full_ = s; }
|
||||
void set_charge_allowed(binary_sensor::BinarySensor *s) { charge_allowed_ = s; }
|
||||
void set_level(sensor::Sensor *s) { level_ = s; }
|
||||
void set_current_limit(sensor::Sensor *s) { current_limit_ = s; }
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void setup() override { update(); }
|
||||
|
||||
void update() override {
|
||||
uint8_t control, charge, full, limit, gauge;
|
||||
// Address-only ACK probe, then individual register reads. No assumption
|
||||
// that the chip supports register auto-increment.
|
||||
if (write(nullptr, 0) != i2c::ERROR_OK ||
|
||||
!read_byte(0x00, &control) || !read_byte(0x70, &charge) ||
|
||||
!read_byte(0x71, &full) || !read_byte(0x24, &limit)) {
|
||||
unavailable_();
|
||||
return;
|
||||
}
|
||||
valid_ = true;
|
||||
charge_enabled_ = (control & 0x10) != 0;
|
||||
available_->publish_state(true);
|
||||
charge_allowed_->publish_state(charge_enabled_);
|
||||
const bool full_flag = (full & 0x08) != 0;
|
||||
full_->publish_state(full_flag);
|
||||
charging_->publish_state(charge_enabled_ && (charge & 0x08) && !full_flag);
|
||||
// Configured current, NOT measured current. Keep it separate from the
|
||||
// charging state: 0x70 alone cannot establish USB presence in every mode.
|
||||
current_limit_->publish_state(50 + 100 * (limit & 0x1F));
|
||||
// Gauge is optional: undocumented on some revisions. Failure/unknown
|
||||
// codes must never be presented as an empty battery.
|
||||
float percent = NAN;
|
||||
if (read_byte(0x78, &gauge)) {
|
||||
switch (gauge >> 4) {
|
||||
case 0x0: percent = 100; break;
|
||||
case 0x8: percent = 75; break;
|
||||
case 0xC: percent = 50; break;
|
||||
case 0xE: percent = 25; break;
|
||||
case 0xF: percent = 0; break;
|
||||
}
|
||||
}
|
||||
level_->publish_state(percent);
|
||||
}
|
||||
|
||||
optional<bool> charge_enabled() const {
|
||||
if (!valid_) return {};
|
||||
return charge_enabled_;
|
||||
}
|
||||
|
||||
void set_charge_enabled(bool enabled) {
|
||||
uint8_t before;
|
||||
// Read afresh even if the last poll succeeded. Modify only bit 4, leaving
|
||||
// boost, key behaviour and all reserved bits untouched.
|
||||
if (write(nullptr, 0) != i2c::ERROR_OK || !read_byte(0x00, &before)) {
|
||||
unavailable_();
|
||||
ESP_LOGW("luxe_ip5306", "Recharge command rejected: circuit unavailable");
|
||||
return;
|
||||
}
|
||||
const uint8_t after = enabled ? (before | 0x10) : (before & ~0x10);
|
||||
if (after != before && !write_byte(0x00, after)) {
|
||||
unavailable_();
|
||||
ESP_LOGW("luxe_ip5306", "Recharge command failed");
|
||||
return;
|
||||
}
|
||||
update(); // Read-back is authoritative; the switch is not optimistic.
|
||||
if (valid_ && charge_enabled_ != enabled)
|
||||
ESP_LOGW("luxe_ip5306", "Recharge command not confirmed by circuit");
|
||||
}
|
||||
|
||||
protected:
|
||||
void unavailable_() {
|
||||
valid_ = false;
|
||||
available_->publish_state(false);
|
||||
charging_->invalidate_state();
|
||||
full_->invalidate_state();
|
||||
charge_allowed_->invalidate_state();
|
||||
level_->publish_state(NAN);
|
||||
current_limit_->publish_state(NAN);
|
||||
// No mark_failed(): an older board must still play audio, and a device
|
||||
// temporarily asleep or disconnected must be detected on the next poll.
|
||||
}
|
||||
|
||||
binary_sensor::BinarySensor *available_{nullptr};
|
||||
binary_sensor::BinarySensor *charging_{nullptr};
|
||||
binary_sensor::BinarySensor *full_{nullptr};
|
||||
binary_sensor::BinarySensor *charge_allowed_{nullptr};
|
||||
sensor::Sensor *level_{nullptr};
|
||||
sensor::Sensor *current_limit_{nullptr};
|
||||
bool valid_{false};
|
||||
bool charge_enabled_{false};
|
||||
};
|
||||
} // namespace esphome::luxe_ip5306
|
||||
+177
-7
@@ -1,9 +1,11 @@
|
||||
substitutions:
|
||||
device_description: "M5Stack Core energy dashboard for Home Assistant grid, solar and consumer usage, with analog/digital clock and WireGuard"
|
||||
device_description: "M5Stack Core dashboard for Home Assistant energy and internet usage, with analog/digital clock and WireGuard"
|
||||
name: "energymonitor"
|
||||
friendly_name: "Energy Monitor"
|
||||
grid_power_gauge_max_w: "5000.0"
|
||||
balcony_solar_gauge_max_w: "800.0"
|
||||
internet_download_gauge_max_mbps: "1000.0"
|
||||
internet_upload_gauge_max_mbps: "1000.0"
|
||||
|
||||
esphome:
|
||||
comment: ${device_description}
|
||||
@@ -120,6 +122,11 @@ font:
|
||||
glyphsets:
|
||||
- GF_Latin_Kernel
|
||||
glyphs: ["äöüÄÖÜß"]
|
||||
- file: "fonts/Roboto-Bold.ttf"
|
||||
id: font_internet_value
|
||||
size: 22
|
||||
glyphsets:
|
||||
- GF_Latin_Kernel
|
||||
- file: "fonts/Roboto-Regular.ttf"
|
||||
id: font_device
|
||||
size: 13
|
||||
@@ -159,6 +166,27 @@ globals:
|
||||
type: int
|
||||
restore_value: true
|
||||
initial_value: '0'
|
||||
- id: internet_graph_view
|
||||
type: bool
|
||||
restore_value: true
|
||||
initial_value: 'false'
|
||||
# 361 ten-second samples span one hour; unfilled slots are never drawn.
|
||||
- id: internet_download_history
|
||||
type: std::array<float, 361>
|
||||
restore_value: false
|
||||
initial_value: 'std::array<float, 361>{}'
|
||||
- id: internet_upload_history
|
||||
type: std::array<float, 361>
|
||||
restore_value: false
|
||||
initial_value: 'std::array<float, 361>{}'
|
||||
- id: internet_history_next
|
||||
type: int
|
||||
restore_value: false
|
||||
initial_value: '0'
|
||||
- id: internet_history_count
|
||||
type: int
|
||||
restore_value: false
|
||||
initial_value: '0'
|
||||
|
||||
sensor:
|
||||
# WiFi signal strength
|
||||
@@ -258,14 +286,40 @@ sensor:
|
||||
id: microwave_power
|
||||
entity_id: sensor.waschetrockner_leistung
|
||||
internal: true
|
||||
# These Home Assistant template sensors already report decimal Mbps.
|
||||
- platform: homeassistant
|
||||
id: internet_download
|
||||
entity_id: sensor.internet_speed_in
|
||||
internal: true
|
||||
- platform: homeassistant
|
||||
id: internet_upload
|
||||
entity_id: sensor.internet_speed_out
|
||||
internal: true
|
||||
- platform: wireguard
|
||||
latest_handshake:
|
||||
name: 'WireGuard Latest Handshake'
|
||||
|
||||
interval:
|
||||
- interval: 10s
|
||||
then:
|
||||
- lambda: |-
|
||||
// Ignore log-only API clients when checking Home Assistant connectivity.
|
||||
const bool connected = id(api_server).is_connected_with_state_subscription();
|
||||
const int slot = id(internet_history_next);
|
||||
const float download = id(internet_download).state;
|
||||
const float upload = id(internet_upload).state;
|
||||
// Keep disconnected/invalid samples as gaps rather than false zero traffic.
|
||||
id(internet_download_history)[slot] = connected && id(internet_download).has_state()
|
||||
&& std::isfinite(download) && download >= 0.0f ? download : NAN;
|
||||
id(internet_upload_history)[slot] = connected && id(internet_upload).has_state()
|
||||
&& std::isfinite(upload) && upload >= 0.0f ? upload : NAN;
|
||||
id(internet_history_next) = (slot + 1) % 361;
|
||||
if (id(internet_history_count) < 361) id(internet_history_count)++;
|
||||
|
||||
script:
|
||||
- id: show_previous_page
|
||||
then:
|
||||
- lambda: id(page_index) = (id(page_index) + 3 - 1) % 3;
|
||||
- lambda: id(page_index) = (id(page_index) + 4 - 1) % 4;
|
||||
- component.update: tft
|
||||
- id: toggle_page_mode
|
||||
then:
|
||||
@@ -274,13 +328,15 @@ script:
|
||||
id(page_one_gauge_view) = !id(page_one_gauge_view);
|
||||
} else if (id(page_index) == 1) {
|
||||
id(device_usage_view) = (id(device_usage_view) + 1) % 2;
|
||||
} else {
|
||||
} else if (id(page_index) == 2) {
|
||||
id(clock_digital_view) = !id(clock_digital_view);
|
||||
} else if (id(page_index) == 3) {
|
||||
id(internet_graph_view) = !id(internet_graph_view);
|
||||
}
|
||||
- component.update: tft
|
||||
- id: show_next_page
|
||||
then:
|
||||
- lambda: id(page_index) = (id(page_index) + 1) % 3;
|
||||
- lambda: id(page_index) = (id(page_index) + 1) % 4;
|
||||
- component.update: tft
|
||||
|
||||
button:
|
||||
@@ -633,6 +689,117 @@ display:
|
||||
}
|
||||
}
|
||||
|
||||
if (id(page_index) == 3) {
|
||||
const Color download_color(143, 190, 223);
|
||||
const Color upload_color(255, 170, 40);
|
||||
const Color muted(42, 42, 42);
|
||||
const bool connected = id(api_server).is_connected_with_state_subscription();
|
||||
const float download = id(internet_download).state;
|
||||
const float upload = id(internet_upload).state;
|
||||
const bool download_valid = connected && id(internet_download).has_state()
|
||||
&& std::isfinite(download) && download >= 0.0f;
|
||||
const bool upload_valid = connected && id(internet_upload).has_state()
|
||||
&& std::isfinite(upload) && upload >= 0.0f;
|
||||
auto format_rate = [](float mbps) -> std::string {
|
||||
char text[32];
|
||||
if (mbps < 1.0f)
|
||||
snprintf(text, sizeof(text), "%.0f kbps", mbps * 1000.0f);
|
||||
else
|
||||
snprintf(text, sizeof(text), "%.1f Mbps", mbps);
|
||||
return std::string(text);
|
||||
};
|
||||
|
||||
if (!id(internet_graph_view)) {
|
||||
it.print(12, 10, id(font_small), Color::WHITE, "Internet");
|
||||
const int cx = 160, cy = 188;
|
||||
const float download_fraction = download_valid
|
||||
? fminf(download / ${internet_download_gauge_max_mbps}, 1.0f) : 0.0f;
|
||||
const float upload_fraction = upload_valid
|
||||
? fminf(upload / ${internet_upload_gauge_max_mbps}, 1.0f) : 0.0f;
|
||||
// Match the energy gauge: download outside, upload inside.
|
||||
it.filled_gauge(cx, cy, 140, 122, 100, muted);
|
||||
it.filled_gauge(cx, cy, 140, 122, static_cast<int>(download_fraction * 100), download_color);
|
||||
it.filled_gauge(cx, cy, 116, 98, 100, muted);
|
||||
it.filled_gauge(cx, cy, 116, 98, static_cast<int>(upload_fraction * 100), upload_color);
|
||||
it.print(cx, 102, id(font_small), Color::WHITE, TextAlign::TOP_CENTER, "Download");
|
||||
it.printf(cx, 120, id(font_internet_value), download_color, TextAlign::TOP_CENTER,
|
||||
"%s", download_valid ? format_rate(download).c_str() : "--");
|
||||
it.print(cx, 151, id(font_small), Color::WHITE, TextAlign::TOP_CENTER, "Upload");
|
||||
it.printf(cx, 169, id(font_internet_value), upload_color, TextAlign::TOP_CENTER,
|
||||
"%s", upload_valid ? format_rate(upload).c_str() : "--");
|
||||
it.printf(cx, 204, id(font_device), Color::WHITE, TextAlign::TOP_CENTER,
|
||||
"Max: DL %.0f / UL %.0f Mbps",
|
||||
${internet_download_gauge_max_mbps}, ${internet_upload_gauge_max_mbps});
|
||||
} else {
|
||||
it.print(12, 10, id(font_device), Color::WHITE, "Internet · 1 h");
|
||||
const int left = 28, right = 308, top = 62, zero = 122, bottom = 182;
|
||||
const int count = id(internet_history_count);
|
||||
// A shared symmetric scale keeps upload/download magnitudes comparable.
|
||||
float peak = 0.001f;
|
||||
for (int sample = 0; sample < count; sample++) {
|
||||
const int slot = (id(internet_history_next) - count + sample + 361) % 361;
|
||||
const float down = id(internet_download_history)[slot];
|
||||
const float up = id(internet_upload_history)[slot];
|
||||
if (std::isfinite(down) && down > peak) peak = down;
|
||||
if (std::isfinite(up) && up > peak) peak = up;
|
||||
}
|
||||
const float magnitude = powf(10.0f, floorf(log10f(peak)));
|
||||
const float normalized = peak / magnitude;
|
||||
const float maximum = magnitude * (normalized <= 1 ? 1 : normalized <= 2 ? 2
|
||||
: normalized <= 5 ? 5 : 10);
|
||||
const std::string scale = format_rate(maximum);
|
||||
it.print(left, 40, id(font_device), upload_color, "Upload");
|
||||
it.printf(right, 40, id(font_device), upload_color, TextAlign::TOP_RIGHT,
|
||||
"+%s", scale.c_str());
|
||||
it.print(left, 184, id(font_device), download_color, "Download");
|
||||
it.printf(right, 184, id(font_device), download_color, TextAlign::TOP_RIGHT,
|
||||
"-%s", scale.c_str());
|
||||
for (int minute = 0; minute <= 60; minute += 15) {
|
||||
const int x = left + minute * (right - left) / 60;
|
||||
it.line(x, top, x, bottom, muted);
|
||||
}
|
||||
it.line(left, top, right, top, muted);
|
||||
it.line(left, bottom, right, bottom, muted);
|
||||
it.line(left, zero, right, zero, Color(100, 100, 100));
|
||||
it.print(12, zero, id(font_device), Color::WHITE, TextAlign::CENTER, "0");
|
||||
it.print(left, 202, id(font_device), Color::WHITE, "-60 min");
|
||||
it.print((left + right) / 2, 202, id(font_device), Color::WHITE,
|
||||
TextAlign::TOP_CENTER, "-30 min");
|
||||
it.print(right, 202, id(font_device), Color::WHITE, TextAlign::TOP_RIGHT, "Jetzt");
|
||||
|
||||
int previous_x = 0, previous_down_y = 0, previous_up_y = 0;
|
||||
bool previous_down_valid = false, previous_up_valid = false;
|
||||
for (int sample = 0; sample < count; sample++) {
|
||||
const int slot = (id(internet_history_next) - count + sample + 361) % 361;
|
||||
// Right-align a partially filled buffer; do not invent pre-boot history.
|
||||
const int x = right - (count - 1 - sample) * (right - left) / 360;
|
||||
const float down = id(internet_download_history)[slot];
|
||||
const float up = id(internet_upload_history)[slot];
|
||||
const bool down_valid = std::isfinite(down);
|
||||
const bool up_valid = std::isfinite(up);
|
||||
const int down_y = down_valid ? zero + lroundf(down / maximum * (bottom - zero)) : zero;
|
||||
const int up_y = up_valid ? zero - lroundf(up / maximum * (zero - top)) : zero;
|
||||
if (down_valid) {
|
||||
it.line(previous_down_valid ? previous_x : x,
|
||||
previous_down_valid ? previous_down_y : down_y, x, down_y, download_color);
|
||||
}
|
||||
if (up_valid) {
|
||||
it.line(previous_up_valid ? previous_x : x,
|
||||
previous_up_valid ? previous_up_y : up_y, x, up_y, upload_color);
|
||||
}
|
||||
previous_x = x;
|
||||
previous_down_y = down_y;
|
||||
previous_up_y = up_y;
|
||||
previous_down_valid = down_valid;
|
||||
previous_up_valid = up_valid;
|
||||
}
|
||||
if (count == 0) {
|
||||
it.print(160, 90, id(font_small), Color::WHITE, TextAlign::TOP_CENTER,
|
||||
"Warte auf Messwerte");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Labels correspond to the left (A) and right (C) hardware buttons.
|
||||
it.printf(8, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_LEFT,
|
||||
"< Zurück");
|
||||
@@ -640,11 +807,14 @@ display:
|
||||
"Weiter >");
|
||||
if (id(page_index) == 0) {
|
||||
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
||||
"Ansicht · 1 / 3");
|
||||
"Ansicht · 1 / 4");
|
||||
} else if (id(page_index) == 1) {
|
||||
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
||||
"Geräte %d / 2", id(device_usage_view) + 1);
|
||||
} else {
|
||||
} else if (id(page_index) == 2) {
|
||||
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
||||
"%s · 3 / 3", id(clock_digital_view) ? "Analog" : "Digital");
|
||||
"%s · 3 / 4", id(clock_digital_view) ? "Analog" : "Digital");
|
||||
} else if (id(page_index) == 3) {
|
||||
it.printf(160, 239, id(font_small), Color::WHITE, TextAlign::BOTTOM_CENTER,
|
||||
"%s · 4 / 4", id(internet_graph_view) ? "Live" : "Verlauf");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
substitutions:
|
||||
startup_sound_file: https://github.com/RASPIAUDIO/esphomeLuxe/raw/refs/heads/main/wav/sounds_timer_finished.wav
|
||||
device_description: "Raspiaudio Muse Luxe Music player for Music Assistant using sendspin"
|
||||
name: "muse-luxe-music"
|
||||
friendly_name: "Raspiaudio Muse Luxe Music"
|
||||
|
||||
|
||||
esphome:
|
||||
comment: ${device_description}
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
min_version: 2026.8.2
|
||||
name_add_mac_suffix: false
|
||||
on_boot:
|
||||
priority: -100
|
||||
then:
|
||||
# GPIO21 commande la mise en sourdine de l'amplificateur de la Luxe.
|
||||
- output.turn_off: dac_mute
|
||||
# Laisse le mixer terminer une annonce avant la reprise d'un flux MA.
|
||||
- speaker_source.set_playlist_delay:
|
||||
id: external_media_player
|
||||
pipeline: announcement
|
||||
delay: 500ms
|
||||
- speaker_source.set_playlist_delay:
|
||||
id: external_media_player
|
||||
pipeline: media
|
||||
delay: 1500ms
|
||||
- script.execute: led_red
|
||||
project:
|
||||
name: raspiaudio.music-assistant
|
||||
version: "2026.9.3"
|
||||
|
||||
esp32:
|
||||
variant: esp32
|
||||
framework:
|
||||
type: esp-idf
|
||||
advanced:
|
||||
include_builtin_idf_components:
|
||||
- esp_http_client
|
||||
sram1_as_iram: true
|
||||
|
||||
logger:
|
||||
level: warn
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
# La lecture Sendspin doit continuer même sans client Home Assistant connecté.
|
||||
reboot_timeout: 0s
|
||||
on_client_connected:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return !id(boot_sound_played);
|
||||
then:
|
||||
- lambda: id(boot_sound_played) = true;
|
||||
# Laisse MA restaurer son groupe avant de décider si l'annonce peut jouer.
|
||||
- delay: 5s
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
using media_player::MediaPlayerState;
|
||||
return id(startup_sound_switch).state &&
|
||||
id(external_media_player).state != MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING &&
|
||||
id(external_media_player).state != MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||
then:
|
||||
- script.execute:
|
||||
id: play_sound
|
||||
priority: true
|
||||
sound_file: ready_sound
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
#use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
min_auth_mode: WPA2
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Raspiaudio Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
id: ota_esphome
|
||||
encryption:
|
||||
- platform: http_request
|
||||
id: ota_http_request
|
||||
|
||||
http_request:
|
||||
|
||||
captive_portal:
|
||||
|
||||
improv_serial:
|
||||
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: components
|
||||
components: [luxe_ip5306]
|
||||
# Initialisation spécifique du codec de la museLuxe (sorties différentielles
|
||||
# et horloge MCLK sur GPIO0). Sendspin et la chaîne audio sont désormais natifs.
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/RASPIAUDIO/esphomeLuxe
|
||||
ref: 192372ff59b560a74aa547dc26af609918fcc9d6
|
||||
components: [es8388]
|
||||
|
||||
i2c:
|
||||
sda: GPIO18
|
||||
scl: GPIO23
|
||||
frequency: 100kHz
|
||||
id: bus_a
|
||||
|
||||
es8388:
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_audio_bus
|
||||
i2s_lrclk_pin: GPIO25
|
||||
i2s_bclk_pin: GPIO5
|
||||
#####################
|
||||
# Internal Components
|
||||
output:
|
||||
- platform: gpio
|
||||
id: dac_mute
|
||||
pin:
|
||||
number: GPIO21
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: box_speaker
|
||||
i2s_audio_id: i2s_audio_bus
|
||||
i2s_dout_pin: GPIO26
|
||||
dac_type: external
|
||||
channel: stereo
|
||||
sample_rate: 48000
|
||||
bits_per_sample: 16bit
|
||||
buffer_duration: 300ms
|
||||
mclk_multiple: 384
|
||||
|
||||
- platform: mixer
|
||||
id: mixing_speaker
|
||||
output_speaker: box_speaker
|
||||
num_channels: 2
|
||||
source_speakers:
|
||||
- id: announcement_mixer_input
|
||||
- id: media_mixer_input
|
||||
|
||||
- platform: resampler
|
||||
id: media_resampling_input
|
||||
output_speaker: media_mixer_input
|
||||
sample_rate: 48000
|
||||
|
||||
- platform: resampler
|
||||
id: announcement_resampling_input
|
||||
output_speaker: announcement_mixer_input
|
||||
sample_rate: 48000
|
||||
|
||||
sendspin:
|
||||
id: sendspin_hub
|
||||
|
||||
audio_file:
|
||||
- id: ready_sound
|
||||
file: ${startup_sound_file}
|
||||
|
||||
media_source:
|
||||
- platform: sendspin
|
||||
id: sendspin_source
|
||||
buffer_size: 2000000
|
||||
- platform: audio_file
|
||||
id: file_source
|
||||
|
||||
media_player:
|
||||
- platform: sendspin
|
||||
id: sendspin_media_player
|
||||
name: museLuxe
|
||||
volume_increment: 2%
|
||||
- platform: speaker_source
|
||||
name: museLuxe Audio
|
||||
id: external_media_player
|
||||
internal: true
|
||||
|
||||
announcement_pipeline:
|
||||
speaker: announcement_resampling_input
|
||||
sources:
|
||||
- file_source
|
||||
format: FLAC
|
||||
num_channels: 1
|
||||
sample_rate: 48000
|
||||
media_pipeline:
|
||||
speaker: media_resampling_input
|
||||
sources:
|
||||
- sendspin_source
|
||||
format: FLAC
|
||||
num_channels: 2
|
||||
sample_rate: 48000
|
||||
|
||||
volume_increment: 0.05
|
||||
volume_min: 0.05
|
||||
volume_max: 0.95
|
||||
on_state:
|
||||
then:
|
||||
- lambda: |-
|
||||
using media_player::MediaPlayerState;
|
||||
auto state = id(external_media_player).state;
|
||||
if (
|
||||
state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING ||
|
||||
state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||
) {
|
||||
id(led_green).execute();
|
||||
} else {
|
||||
id(led_red).execute();
|
||||
}
|
||||
script:
|
||||
- id: update_battery_led
|
||||
mode: queued
|
||||
max_runs: 2
|
||||
then:
|
||||
- lambda: |-
|
||||
const bool critical = id(battery_critical).state;
|
||||
if (critical && (!id(led).remote_values.is_on() || id(led).get_effect_name() != "Battery alert")) {
|
||||
auto call = id(led).turn_on();
|
||||
call.set_effect("Battery alert");
|
||||
call.set_brightness(1.0f);
|
||||
call.perform();
|
||||
} else if (!critical && id(led).get_effect_name() == "Battery alert") {
|
||||
auto call = id(led).turn_on();
|
||||
call.set_effect("None");
|
||||
call.perform();
|
||||
}
|
||||
- id: play_sound
|
||||
parameters:
|
||||
priority: bool
|
||||
sound_file: string
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return priority;
|
||||
then:
|
||||
- media_player.stop:
|
||||
id: external_media_player
|
||||
announcement: true
|
||||
- lambda: |-
|
||||
if ( (id(external_media_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
|
||||
id(external_media_player)
|
||||
->make_call()
|
||||
.set_media_url("audio-file://" + sound_file)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
}
|
||||
- id: led_red
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
|
||||
- id: led_green
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: ip5306_charge_switch
|
||||
name: "Allow charging"
|
||||
entity_category: config
|
||||
icon: mdi:battery-charging
|
||||
restore_mode: DISABLED
|
||||
optimistic: false
|
||||
lambda: return id(battery_manager).charge_enabled();
|
||||
turn_on_action:
|
||||
- lambda: id(battery_manager).set_charge_enabled(true);
|
||||
turn_off_action:
|
||||
- lambda: id(battery_manager).set_charge_enabled(false);
|
||||
- platform: template
|
||||
id: startup_sound_switch
|
||||
name: Startup sound
|
||||
icon: "mdi:card-text-outline"
|
||||
entity_category: config
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
|
||||
globals:
|
||||
- id: boot_sound_played
|
||||
type: bool
|
||||
restore_value: no
|
||||
initial_value: "false"
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
id: battery_critical
|
||||
name: Battery low
|
||||
device_class: battery
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
if (!id(battery_percent).has_state() || !std::isfinite(id(battery_percent).state)) return {};
|
||||
const float percent = id(battery_percent).state;
|
||||
const float threshold = id(battery_alert_threshold).state;
|
||||
if (percent < threshold) return true;
|
||||
if (percent >= threshold + 5.0f) return false;
|
||||
return {};
|
||||
on_state:
|
||||
- script.execute: update_battery_led
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO12
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
output: false
|
||||
open_drain: false
|
||||
pullup: true
|
||||
pulldown: false
|
||||
drive_strength: 20.0
|
||||
name: Play or Pause
|
||||
on_press:
|
||||
then:
|
||||
- media_player.toggle: sendspin_media_player
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO19
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
name: Volume Up
|
||||
on_click:
|
||||
- media_player.volume_up:
|
||||
id: sendspin_media_player
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO32
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
name: Volume Down
|
||||
on_click:
|
||||
- media_player.volume_down:
|
||||
id: sendspin_media_player
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led
|
||||
name: LED light
|
||||
disabled_by_default: false
|
||||
entity_category: config
|
||||
pin: GPIO22
|
||||
default_transition_length: 0s
|
||||
chipset: WS2812
|
||||
num_leds: 1
|
||||
channel_colors: GRB
|
||||
# Conserve l'alerte après une commande HA ou un changement lecture/arrêt.
|
||||
on_state:
|
||||
- script.execute: update_battery_led
|
||||
effects:
|
||||
- addressable_lambda:
|
||||
name: "Battery alert"
|
||||
update_interval: 500ms
|
||||
lambda: |-
|
||||
static bool lit = false;
|
||||
if (initial_run) lit = false;
|
||||
lit = !lit;
|
||||
it.all() = lit ? current_color : Color::BLACK;
|
||||
- pulse:
|
||||
name: "Slow Pulse"
|
||||
transition_length: 250ms
|
||||
update_interval: 250ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
- pulse:
|
||||
name: "Fast Pulse"
|
||||
transition_length: 100ms
|
||||
update_interval: 100ms
|
||||
min_brightness: 50%
|
||||
max_brightness: 100%
|
||||
|
||||
sensor:
|
||||
- platform: adc
|
||||
pin: GPIO33
|
||||
id: battery_voltage
|
||||
name: Battery voltage
|
||||
icon: "mdi:battery-outline"
|
||||
update_interval: 15s
|
||||
accuracy_decimals: 3
|
||||
attenuation: 12db
|
||||
raw: true
|
||||
filters:
|
||||
- multiply: 0.00173913 # 2300 -> 4, for attenuation 11db, based on Olivier's code
|
||||
- exponential_moving_average:
|
||||
alpha: 0.2
|
||||
send_every: 2
|
||||
- delta: 0.002
|
||||
on_value:
|
||||
- sensor.template.publish:
|
||||
id: battery_percent
|
||||
state: !lambda return x;
|
||||
- platform: template
|
||||
id: battery_percent
|
||||
name: Battery level
|
||||
device_class: battery
|
||||
unit_of_measurement: "%"
|
||||
accuracy_decimals: 0
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
update_interval: never
|
||||
# Même estimation tension/charge que la Radio, après calibration Luxe.
|
||||
filters:
|
||||
- calibrate_polynomial:
|
||||
degree: 3
|
||||
datapoints:
|
||||
- 4.20 -> 100.0
|
||||
- 4.13 -> 97.1
|
||||
- 4.10 -> 94.2
|
||||
- 4.07 -> 88.4
|
||||
- 4.05 -> 82.7
|
||||
- 4.04 -> 76.9
|
||||
- 4.04 -> 71.1
|
||||
- 4.01 -> 65.3
|
||||
- 3.99 -> 59.5
|
||||
- 3.95 -> 53.8
|
||||
- 3.93 -> 48.0
|
||||
- 3.91 -> 42.2
|
||||
- 3.88 -> 36.4
|
||||
- 3.86 -> 30.6
|
||||
- 3.84 -> 24.9
|
||||
- 3.82 -> 19.1
|
||||
- 3.76 -> 13.3
|
||||
- 3.73 -> 10.4
|
||||
- 3.70 -> 7.5
|
||||
- 3.64 -> 4.6
|
||||
- 3.50 -> 1.7
|
||||
- 3.00 -> 0.0
|
||||
- lambda: return clamp(x, 0.0f, 100.0f);
|
||||
- platform: wifi_signal
|
||||
name: "museLuxe WiFi signal"
|
||||
update_interval: 10s
|
||||
|
||||
# Paramètre logiciel : conserve une hystérésis fixe de 5 points.
|
||||
number:
|
||||
- platform: template
|
||||
id: battery_alert_threshold
|
||||
name: "Low battery threshold"
|
||||
entity_category: config
|
||||
unit_of_measurement: "%"
|
||||
min_value: 5
|
||||
max_value: 50
|
||||
step: 1
|
||||
initial_value: 20
|
||||
restore_value: true
|
||||
optimistic: true
|
||||
mode: box
|
||||
|
||||
luxe_ip5306:
|
||||
id: battery_manager
|
||||
i2c_id: bus_a
|
||||
address: 0x75
|
||||
update_interval: 10s
|
||||
available:
|
||||
name: "IP5306 available"
|
||||
id: ip5306_available
|
||||
charging:
|
||||
name: "Charging"
|
||||
full:
|
||||
name: "Battery full"
|
||||
charge_allowed:
|
||||
name: "Charging enabled (hardware)"
|
||||
level:
|
||||
name: "IP5306 battery level"
|
||||
current_limit:
|
||||
name: "IP5306 charge current limit"
|
||||
@@ -165,6 +165,12 @@ wifi:
|
||||
fast_connect: on
|
||||
min_auth_mode: WPA2
|
||||
|
||||
# The panel uses the upper colon dot as its Wi-Fi connection indicator.
|
||||
on_connect:
|
||||
- lambda: id(panel)->set_online(true);
|
||||
on_disconnect:
|
||||
- lambda: id(panel)->set_online(false);
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Schlafzimmeruhr Fallback Hotspot"
|
||||
@@ -244,7 +250,7 @@ binary_sensor:
|
||||
id: button_2
|
||||
name: Button 2
|
||||
pin:
|
||||
number: GPIO5 # MCU socket pin 10, net S2, bottom button on the case
|
||||
number: GPIO7 # MCU socket pin 10, net S2, bottom button on the case
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
button:
|
||||
- name: Haustür öffnen
|
||||
unique_id: gdoor_haustuer_oeffnen
|
||||
icon: mdi:door-open
|
||||
command_topic: gdoor/bus_tx
|
||||
payload_press: '0200319A59A60000A1E2A1A494'
|
||||
qos: 0
|
||||
retain: false
|
||||
|
||||
sensor:
|
||||
# MQTT/RTL_433 (outdoor) sensors
|
||||
# Reliable sensor with good placement (good values) and good reception
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@
|
||||
- 'time'
|
||||
- 'date'
|
||||
|
||||
# SNMP (Juniper) router traffic sensor
|
||||
# SNMP (VyOS) cumulative traffic counters; one octet is one byte.
|
||||
- platform: snmp
|
||||
name: snmp_wan_in
|
||||
unique_id: '3303381540758'
|
||||
@@ -14,7 +14,7 @@
|
||||
# baseoid: .1.3.6.1.2.1.2.2.1.10.12
|
||||
baseoid: .1.3.6.1.2.1.31.1.1.1.6.12 # IF-MIB::ifHCInOctets.12
|
||||
version: 2c
|
||||
unit_of_measurement: "Octets"
|
||||
unit_of_measurement: "B"
|
||||
- platform: snmp
|
||||
name: snmp_wan_out
|
||||
unique_id: '1573258703922'
|
||||
@@ -24,17 +24,17 @@
|
||||
# baseoid: .1.3.6.1.2.1.2.2.1.16.12
|
||||
baseoid: .1.3.6.1.2.1.31.1.1.1.10.12 # IF-MIB::ifHCOutOctets.12
|
||||
version: 2c
|
||||
unit_of_measurement: "Octets"
|
||||
unit_of_measurement: "B"
|
||||
|
||||
- platform: derivative
|
||||
source: sensor.snmp_wan_in
|
||||
unit_time: s
|
||||
unit: B
|
||||
unit: B/s
|
||||
name: wan_in_derivative
|
||||
- platform: derivative
|
||||
source: sensor.snmp_wan_out
|
||||
unit_time: s
|
||||
unit: B
|
||||
unit: B/s
|
||||
name: wan_out_derivative
|
||||
|
||||
- platform: statistics
|
||||
|
||||
+20
-13
@@ -1,40 +1,47 @@
|
||||
# Internet traffic
|
||||
# Internet traffic: difference cumulative byte counters, not derivative rates (B/s).
|
||||
# Allow counters to reset when the router/interface restarts.
|
||||
internet_usage_in_monthly:
|
||||
source: sensor.wan_in_derivative
|
||||
source: sensor.snmp_wan_in
|
||||
name: Monthly internet traffic in
|
||||
unique_id: monthly_internet_traffic_in
|
||||
cycle: monthly
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
internet_usage_out_monthly:
|
||||
source: sensor.wan_out_derivative
|
||||
source: sensor.snmp_wan_out
|
||||
name: Monthly internet traffic out
|
||||
unique_id: monthly_internet_traffic_out
|
||||
cycle: monthly
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
internet_usage_in_daily:
|
||||
source: sensor.wan_in_derivative
|
||||
source: sensor.snmp_wan_in
|
||||
name: Daily internet traffic in
|
||||
unique_id: daily_internet_traffic_in
|
||||
cycle: daily
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
internet_usage_out_daily:
|
||||
source: sensor.wan_out_derivative
|
||||
source: sensor.snmp_wan_out
|
||||
name: Daily internet traffic out
|
||||
unique_id: daily_internet_traffic_out
|
||||
cycle: daily
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
internet_usage_in_hourly:
|
||||
source: sensor.wan_in_derivative
|
||||
source: sensor.snmp_wan_in
|
||||
name: Hourly internet traffic in
|
||||
unique_id: hourly_internet_traffic_in
|
||||
cycle: hourly
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
internet_usage_out_hourly:
|
||||
source: sensor.wan_out_derivative
|
||||
source: sensor.snmp_wan_out
|
||||
name: Hourly internet traffic out
|
||||
unique_id: hourly_internet_traffic_out
|
||||
cycle: hourly
|
||||
delta_values: True
|
||||
delta_values: false
|
||||
periodically_resetting: true
|
||||
|
||||
# Energy
|
||||
energy_import_daily:
|
||||
|
||||
Reference in New Issue
Block a user