Compare commits
135 Commits
4131b60c8f
...
main
Author | SHA1 | Date | |
---|---|---|---|
a947806618 | |||
439ead1047 | |||
20c86831f1 | |||
61b018270b | |||
911477d381 | |||
1ee475ee4a | |||
cae28341fe | |||
17cfa429ea | |||
791d7d504f | |||
631cdae751 | |||
06321730e0 | |||
5ac8ceacfe | |||
4981cbfa2e | |||
e5ddf02c34 | |||
ebf11021e2 | |||
72ce055810 | |||
8461f43bca | |||
ca599eab7a | |||
fef90d5a78 | |||
1f151a804e | |||
31da165db1 | |||
dd390001ee | |||
7993a9174a | |||
fe40743fd1 | |||
01bad587b8 | |||
4888393a12 | |||
05f8cc1af8 | |||
5c98baa24b | |||
490b00301b | |||
c3e813551b | |||
474805328b | |||
fd18934e97 | |||
5e1d197e7a | |||
ff59c494a7 | |||
c4d9c253f4 | |||
99007cda76 | |||
373675be4f | |||
90ed6df217 | |||
e1984a199d | |||
3fcca0c3e1 | |||
5ddbe9962f | |||
f05405bc75 | |||
5467789c71 | |||
7f9a366171 | |||
bf3b4e5e81 | |||
083cdbb857 | |||
32e9c3af4d | |||
a0d7950829 | |||
8cd5ae5283 | |||
574be34e36 | |||
0dfe1950c9 | |||
9def77c3f3 | |||
fb027e0c45 | |||
f744021925 | |||
daaec3cc60 | |||
683ffe7767 | |||
730d1cf338 | |||
27408f02dd | |||
624ee50d14 | |||
48d29a6585 | |||
04eff2e382 | |||
8f63ede1f0 | |||
d32328f1bb | |||
c15266fc20 | |||
9ec14848d5 | |||
87868f079c | |||
c0755f5add | |||
acb1c63483 | |||
565b1b1b81 | |||
392e8508d3 | |||
6985991d3a | |||
0162c1074f | |||
8a5ba47dfa | |||
db6a6e9e30 | |||
8cad164008 | |||
d8cb73ebf0 | |||
0ac87ad81c | |||
395bf7bb9c | |||
c0ebd9188d | |||
f36ea1b591 | |||
ee90402c8b | |||
479a1cb8e1 | |||
2cf58f3f34 | |||
0dff2d90ff | |||
92f95f7bae | |||
d5be7fea6b | |||
8a446a727b | |||
2d3c9cf674 | |||
ce9ad46e5b | |||
6482453a1d | |||
5f1f08b79e | |||
48338c9233 | |||
671c9d6b77 | |||
94f390ddf7 | |||
429dce72fe | |||
36a482de26 | |||
9e5a74a999 | |||
9ba761dcd3 | |||
b414af5686 | |||
eef9f31a94 | |||
a6b8e2d9e2 | |||
663f5d5daf | |||
fb4e6a9f41 | |||
cfcbdeea9d | |||
6fdacdc6e9 | |||
8b43bf239b | |||
1ade598c97 | |||
84bea6cc2b | |||
4d2c5b0cba | |||
0191bc13b7 | |||
6e48e18384 | |||
55e1a02544 | |||
a433286324 | |||
0d00747fe4 | |||
d17935a803 | |||
929d6b1830 | |||
a57178b5e1 | |||
883225a67e | |||
39a432eece | |||
c080d2ead3 | |||
abef860f2e | |||
40e18e493b | |||
e44ec663f0 | |||
0c2d11f7fa | |||
8a6e5b416e | |||
48c1b077ac | |||
adb050d0b8 | |||
c2e409c21e | |||
7e64d92101 | |||
28e88d9196 | |||
855b7bba03 | |||
e0e888870f | |||
0b8d524602 | |||
4e91c0e7f5 | |||
ac907aad64 |
@@ -1 +1 @@
|
||||
2023.2.0
|
||||
2025.9.0
|
3
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
automations_webhooks.yaml
|
||||
spotify.yaml
|
||||
calendars.yaml
|
||||
ics_calendars.yaml
|
||||
esphome/common/secrets.yaml
|
||||
esphome/secrets.yaml
|
||||
secrets.yaml
|
||||
@@ -9,3 +10,5 @@ tts/
|
||||
media/
|
||||
home-assistant.log*
|
||||
.storage/
|
||||
zigbee.db*
|
||||
*__pycache__*
|
||||
|
1211
automations.yaml
80
blueprints/automation/AWTRIX/awtrix_random_effect.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
blueprint:
|
||||
name: Awtrix random effect
|
||||
description: This blueprint allows you to select the effects, which should be randomly displayed on your Awtrix light
|
||||
domain: automation
|
||||
author: N1c093
|
||||
input:
|
||||
awtrix_light:
|
||||
name: Awtrix Display
|
||||
description: Select the target Awtrix display.
|
||||
selector:
|
||||
device:
|
||||
model: "AWTRIX Light"
|
||||
effect_list:
|
||||
name: Effects
|
||||
description: 'Select the effects which should randomly be displayed. See: https://blueforcer.github.io/awtrix-light/#/effects'
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- BrickBreaker
|
||||
- Fireworks
|
||||
- Radar
|
||||
- Snake
|
||||
- TheaterChase
|
||||
- SwirlOut
|
||||
- LookingEyes
|
||||
- Pacifica
|
||||
- PlasmaCloud
|
||||
- Checkerboard
|
||||
- PingPong
|
||||
- Ripple
|
||||
- TwinklingStars
|
||||
- ColorWaves
|
||||
- SwirlIn
|
||||
- Matrix
|
||||
- Plasma
|
||||
- MovingLine
|
||||
mode: list
|
||||
multiple: true
|
||||
duration:
|
||||
name: Effect duration
|
||||
description: Select how long each effect should be displayed.
|
||||
default: "10"
|
||||
selector:
|
||||
number:
|
||||
min: 1
|
||||
max: 999
|
||||
mode: box
|
||||
unit_of_measurement: seconds
|
||||
change_interval:
|
||||
name: Effect change interval
|
||||
description: 'Select how often (in minutes) the effect should change. Input must start with "/" Example: "/5"'
|
||||
default: "/5"
|
||||
selector:
|
||||
text:
|
||||
suffix: minutes
|
||||
|
||||
mode: queued
|
||||
|
||||
variables:
|
||||
device_id: !input awtrix_light
|
||||
awtrix_light: "{{ iif( device_attr(device_id, 'name_by_user') != none, device_attr(device_id, 'name_by_user'), device_attr(device_id, 'name') ) }}"
|
||||
effect_list: !input effect_list
|
||||
effect_random: "{{effect_list|random}}"
|
||||
duration: !input duration
|
||||
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
minutes: !input change_interval
|
||||
|
||||
action:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{awtrix_light}}/custom/effect"
|
||||
payload: |-
|
||||
{
|
||||
"effect": "{{ effect_random }}",
|
||||
"duration": "{{ duration }}"
|
||||
}
|
152
blueprints/automation/AWTRIX/solar_production.yaml
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
blueprint:
|
||||
name: AWTRIX Solar Energy Monitor
|
||||
description: >
|
||||
This blueprint will show the current solar energy received.
|
||||
|
||||
It uses a icons 54156 (solar-green), 50557 (solar-white-dyn), 50546 (solar-static) that you need to install.
|
||||
|
||||
domain: automation
|
||||
input:
|
||||
awtrix:
|
||||
name: AWTRIX Device
|
||||
description: Select the Awtrix light device
|
||||
selector:
|
||||
device:
|
||||
integration: mqtt
|
||||
manufacturer: Blueforcer
|
||||
model: AWTRIX Light
|
||||
multiple: true
|
||||
power_source:
|
||||
name: Power Sensor
|
||||
description: A sensor providing the current power received from your solar system.
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- sensor
|
||||
multiple: false
|
||||
threshold_high:
|
||||
name: Threshold for high solar production (W)
|
||||
description: The threshold above which the energy production of your solar system should be visualized as high. Input in Watts (W).
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 100000
|
||||
unit_of_measurement: Watt
|
||||
mode: slider
|
||||
default: 400
|
||||
threshold_low:
|
||||
name: Threshold for low solar production (W)
|
||||
description: The threshold below which the energy production of your solar system should be visualized as low. Input in Watts (W).
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 100000
|
||||
unit_of_measurement: Watt
|
||||
mode: slider
|
||||
default: 100
|
||||
skip_if_zero_watts:
|
||||
name: Hide solar production if at 0 Watts
|
||||
description: 'This will not show the solar energy production on your awtrix if the production is below 0 Watts.'
|
||||
selector:
|
||||
boolean:
|
||||
default: false
|
||||
skip_during_night_hours:
|
||||
name: Hide solar production during night time
|
||||
description: 'This will not show the solar energy production on your awtrix during night hours (as specified below).'
|
||||
selector:
|
||||
boolean:
|
||||
default: false
|
||||
night_starts_after_time:
|
||||
name: Night Time Start
|
||||
description: Set the start of the night time.
|
||||
default: 00:00:00
|
||||
selector:
|
||||
time: {}
|
||||
night_ends_after_time:
|
||||
name: Night Time End
|
||||
description: Set the end of the night time.
|
||||
default: 00:00:00
|
||||
selector:
|
||||
time: {}
|
||||
mode: single
|
||||
variables:
|
||||
device_ids: !input awtrix
|
||||
devices_topics: >-
|
||||
{%- macro get_device_topic(device_id) %}
|
||||
{{ states((device_entities(device_id) | select('search','device_topic') | list)[0]) }}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- set ns = namespace(devices=[]) %}
|
||||
{%- for device_id in device_ids %}
|
||||
{%- set device=get_device_topic(device_id)|replace(' ','') %}
|
||||
{% set ns.devices = ns.devices + [ device ~ '/custom/solar_power'] %}
|
||||
{%- endfor %}
|
||||
{{ ns.devices }}
|
||||
power_sensor: !input power_source
|
||||
power_level: >-
|
||||
{{ states[power_sensor].state | int(0) | abs }}
|
||||
threshold_low: !input threshold_low
|
||||
threshold_high: !input threshold_high
|
||||
power_level_icon: >-
|
||||
{%- if power_level > threshold_high %}{{54156}}{%- endif %}
|
||||
{%- if (power_level <= threshold_high) and (power_level > threshold_low) %}{{50557}}{%- endif %}
|
||||
{%- if power_level <= threshold_low %}{{50546}}{%- endif %}
|
||||
power_level_color: >-
|
||||
{%- if power_level > threshold_high %}{{"#04FE04"}}{%- endif %}
|
||||
{%- if (power_level <= threshold_high) and (power_level > threshold_low) %}{{"#FCFEFC"}}{%- endif %}
|
||||
{%- if power_level <= threshold_low %}{{"#FF4E1A"}}{%- endif %}
|
||||
power_level_text: >-
|
||||
{%- if power_level > 1000 %}{{ ((power_level | float(default=0)) / 1000) | round(1)}} kW{%- else %}{{power_level | round(0)}} W{%- endif %}
|
||||
skip_if_zero_watts: !input skip_if_zero_watts
|
||||
skip_during_night_hours: !input skip_during_night_hours
|
||||
payload: >-
|
||||
{"icon":"{{ power_level_icon }}", "text": "{{ power_level_text }}", "color": "{{ power_level_color }}"}
|
||||
night_start: !input night_starts_after_time
|
||||
night_end: !input night_ends_after_time
|
||||
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
minutes: "/1"
|
||||
|
||||
condition:
|
||||
action:
|
||||
- choose:
|
||||
- alias: "Skipping"
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: >
|
||||
{% set now_time = now().strftime("%H:%M") %}
|
||||
{% set night_start = night_start %}
|
||||
{% set night_end = night_end %}
|
||||
{{ (skip_during_night_hours and ((now_time < night_end) or (now_time > night_start))) or (skip_if_zero_watts and (power_level == 0)) }}
|
||||
sequence:
|
||||
# It is night time, skipping sending solar power data to Awtrix Light.
|
||||
- repeat:
|
||||
for_each: "{{ devices_topics }}"
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{ repeat.item }}"
|
||||
payload: '{}'
|
||||
- alias: "Not skipping"
|
||||
conditions:
|
||||
- condition: template
|
||||
value_template: >
|
||||
{% set now_time = now().strftime("%H:%M") %}
|
||||
{% set night_start = night_start %}
|
||||
{% set night_end = night_end %}
|
||||
{{ not((skip_during_night_hours and ((now_time < night_end) or (now_time > night_start))) or (skip_if_zero_watts and (power_level == 0))) }}
|
||||
sequence:
|
||||
- repeat:
|
||||
for_each: "{{ devices_topics }}"
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{ repeat.item }}"
|
||||
payload: >
|
||||
{{ payload }}
|
141
blueprints/automation/Brunas/awtrix_calendar_notifier.yaml
Normal file
@@ -0,0 +1,141 @@
|
||||
blueprint:
|
||||
name: AWTRIX Calendar Notifier
|
||||
description: 'This blueprint will print notification when calendar event happens.
|
||||
|
||||
[Google calendar integration](https://www.home-assistant.io/integrations/google)
|
||||
was used for initial testing. Other calendars might work too.
|
||||
|
||||
Any icons can be used to describe calendar events. Upload them to AWTRIX ICONS
|
||||
folder and make sure your event title is mapped to them in icon map setting of
|
||||
your automation. I''ve used trash bins of various colours to remind me about need
|
||||
to take appropriate bin to the street :).
|
||||
|
||||
Message is shown every 5 minutes in interval from configurable amount of hours
|
||||
before event start time till event start.
|
||||
|
||||
Inspired by awesome work of Jeeftor '
|
||||
domain: automation
|
||||
input:
|
||||
awtrix:
|
||||
name: AWTRIX Device
|
||||
description: Select the Awtrix light
|
||||
selector:
|
||||
device:
|
||||
integration: mqtt
|
||||
manufacturer: Blueforcer
|
||||
model: AWTRIX Light
|
||||
multiple: true
|
||||
app_name:
|
||||
name: Awtrix Application name
|
||||
description: This is the app name listed in the MQTT topic - it should be unique
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
default: calendar_notifier
|
||||
calendar:
|
||||
name: Calendar with schedule
|
||||
description: A calendar with schedule
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
message_attr_name:
|
||||
name: Name of calendar event message attribute
|
||||
description: This is the name of calendar event message attribute
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
default: message
|
||||
start_time_attr_name:
|
||||
name: Name of calendar event start time attribute
|
||||
description: This is the name of calendar event start time attribute
|
||||
selector:
|
||||
text:
|
||||
multiline: false
|
||||
default: start_time
|
||||
hours_before:
|
||||
name: Number of Hours before Event
|
||||
description: Number of hours to start notify before actual event
|
||||
selector:
|
||||
number:
|
||||
max: 96.0
|
||||
min: 0.0
|
||||
unit_of_measurement: hours
|
||||
mode: box
|
||||
step: 1.0
|
||||
default: 15
|
||||
icon_map:
|
||||
name: An event message-to-icon name map
|
||||
description: An event message-to-icon name map in JSON format
|
||||
selector:
|
||||
text:
|
||||
multiline: true
|
||||
default: '{"light green bin":"trash_light_green","green bin":"trash_green","blue
|
||||
bin":"trash_blue"}'
|
||||
duration:
|
||||
name: Duration (in seconds)
|
||||
description: Sets how long the app or notification should be displayed.
|
||||
default: '10'
|
||||
lifetime:
|
||||
name: Lifetime of the app (in seconds)
|
||||
description: Removes the custom app when there is no update after the given
|
||||
time in seconds. Keep this value higher than 59 seconds to get the AWTRIX
|
||||
app automatically deleted when disabling the automation.
|
||||
default: '70'
|
||||
push_icon:
|
||||
name: Icon Mode
|
||||
description: "Please select the pushIcon setting for the icon\n\n - `0` Icon
|
||||
doesn't move\n\n - `1` Icon moves with text and will not appear again\n\n
|
||||
\ - `2` Icon moves with text but appears again when the text starts to scroll
|
||||
again\n"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: Icon doesn't move (default)
|
||||
value: '0'
|
||||
- label: Icon moves with text and will not appear again
|
||||
value: '1'
|
||||
- label: Icon moves with text but appears again when the text starts to
|
||||
scroll again
|
||||
value: '2'
|
||||
custom_value: false
|
||||
multiple: false
|
||||
sort: false
|
||||
source_url: https://raw.githubusercontent.com/Brunas/HomeAssistant/master/blueprints/automation/awtrix_calendar_notifier.yaml
|
||||
mode: single
|
||||
variables:
|
||||
device_ids: !input awtrix
|
||||
app_name: !input app_name
|
||||
devices_topics: "{%- macro get_device_topic(device_id) %} {{- states((device_entities(device_id)
|
||||
| select('search','device_topic') | list)[0]) }} {%- endmacro %}\n{%- set ns =
|
||||
namespace(devices=[]) %} {%- for device_id in device_ids %}\n {%- set device=get_device_topic(device_id)|replace('
|
||||
','') %}\n {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ app_name]
|
||||
%}\n{%- endfor %} {{ ns.devices | reject('match','unavailable') | list}}"
|
||||
calendar: !input calendar
|
||||
message_attr_name: !input message_attr_name
|
||||
start_time_attr_name: !input start_time_attr_name
|
||||
hours_before: !input hours_before
|
||||
icon_map: !input icon_map
|
||||
icon: '{%- set icon_map_json = icon_map|from_json %} {{icon_map_json[state_attr(calendar,message_attr_name)]}}'
|
||||
duration: !input duration
|
||||
lifetime: !input lifetime
|
||||
push_icon: !input push_icon
|
||||
payload_internal: "{\"icon\":\"{{icon}}\",\n \"text\":\"{{state_attr(calendar,start_time_attr_name)}}
|
||||
{{state_attr(calendar,message_attr_name)}}\",\n \"pushIcon\":\"{{push_icon}}\",\n
|
||||
\"repeat\":1,\"textCase\":2,\"textOffset\":33,\"duration\":{{duration}},\"lifetime\":{{lifetime}}}"
|
||||
payload: '{{ iif(now() >= state_attr(calendar,start_time_attr_name)| as_datetime
|
||||
| default(now(), true)|as_local - timedelta(hours = hours_before), payload_internal,
|
||||
"{}") }}'
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
minutes: /5
|
||||
condition: []
|
||||
action:
|
||||
- repeat:
|
||||
for_each: '{{ devices_topics }}'
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item }}'
|
||||
payload: '{{payload}}'
|
426
blueprints/automation/EPMatt/ikea_e1743.yaml
Normal file
@@ -0,0 +1,426 @@
|
||||
blueprint:
|
||||
name: Controller - IKEA E1743 TRÅDFRI On/Off Switch & Dimmer
|
||||
description: "# Controller - IKEA E1743 TRÅDFRI On/Off Switch & Dimmer\n\nController
|
||||
automation for executing any kind of action triggered by the provided IKEA E1743
|
||||
TRÅDFRI On/Off Switch & Dimmer. Allows to optionally loop an action on a button
|
||||
long press.\nSupports deCONZ, ZHA, Zigbee2MQTT.\n\nAutomations created with this
|
||||
blueprint can be connected with one or more [Hooks](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/hooks)
|
||||
supported by this controller.\nHooks allow to easily create controller-based automations
|
||||
for interacting with media players, lights, covers and more.\nSee the list of
|
||||
[Hooks available for this controller](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/controllers/ikea_e1743#available-hooks)
|
||||
for additional details.\n\n\U0001F4D5 Full documentation regarding this blueprint
|
||||
is available [here](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/controllers/ikea_e1743).\n\n\U0001F680
|
||||
This blueprint is part of the **[Awesome HA Blueprints](https://epmatt.github.io/awesome-ha-blueprints)
|
||||
project**.\n\nℹ️ Version 2022.08.08\n"
|
||||
source_url: https://github.com/EPMatt/awesome-ha-blueprints/blob/main/blueprints/controllers/ikea_e1743/ikea_e1743.yaml
|
||||
domain: automation
|
||||
input:
|
||||
integration:
|
||||
name: (Required) Integration
|
||||
description: Integration used for connecting the remote with Home Assistant.
|
||||
Select one of the available values.
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- deCONZ
|
||||
- ZHA
|
||||
- Zigbee2MQTT
|
||||
custom_value: false
|
||||
multiple: false
|
||||
sort: false
|
||||
controller_device:
|
||||
name: (deCONZ, ZHA) Controller Device
|
||||
description: The controller device to use for the automation. Choose a value
|
||||
only if the remote is integrated with deCONZ, ZHA.
|
||||
default: ''
|
||||
selector:
|
||||
device: {}
|
||||
controller_entity:
|
||||
name: (Zigbee2MQTT) Controller Entity
|
||||
description: The action sensor of the controller to use for the automation.
|
||||
Choose a value only if the remote is integrated with Zigbee2MQTT.
|
||||
default: ''
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- sensor
|
||||
multiple: false
|
||||
helper_last_controller_event:
|
||||
name: (Required) Helper - Last Controller Event
|
||||
description: Input Text used to store the last event fired by the controller.
|
||||
You will need to manually create a text input entity for this, please read
|
||||
the blueprint Additional Notes for more info.
|
||||
default: ''
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- input_text
|
||||
multiple: false
|
||||
action_button_up_short:
|
||||
name: (Optional) Up button short press
|
||||
description: Action to run on short up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_long:
|
||||
name: (Optional) Up button long press
|
||||
description: Action to run on long up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_release:
|
||||
name: (Optional) Up button release
|
||||
description: Action to run on up button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_double:
|
||||
name: (Optional) Up button double press
|
||||
description: Action to run on double up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_short:
|
||||
name: (Optional) Down button short press
|
||||
description: Action to run on short down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_long:
|
||||
name: (Optional) Down button long press
|
||||
description: Action to run on long down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_release:
|
||||
name: (Optional) Down button release
|
||||
description: Action to run on down button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_double:
|
||||
name: (Optional) Down button double press
|
||||
description: Action to run on double down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_up_long_loop:
|
||||
name: (Optional) Up button long press - loop until release
|
||||
description: Loop the up button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_up_long_max_loop_repeats:
|
||||
name: (Optional) Up button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_down_long_loop:
|
||||
name: (Optional) Down button long press - loop until release
|
||||
description: Loop the down button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_down_long_max_loop_repeats:
|
||||
name: (Optional) Down button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_up_double_press:
|
||||
name: (Optional) Expose up button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the up button. Turn this on if you are providing an action for the up
|
||||
button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_down_double_press:
|
||||
name: (Optional) Expose down button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the down button. Turn this on if you are providing an action for the down
|
||||
button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
helper_double_press_delay:
|
||||
name: (Optional) Helper - Double Press delay
|
||||
description: Max delay between the first and the second button press for the
|
||||
double press event. Provide a value only if you are using a double press action.
|
||||
Increase this value if you notice that the double press action is not triggered
|
||||
properly.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 100.0
|
||||
max: 5000.0
|
||||
unit_of_measurement: milliseconds
|
||||
mode: box
|
||||
step: 10.0
|
||||
helper_debounce_delay:
|
||||
name: (Optional) Helper - Debounce delay
|
||||
description: Delay used for debouncing RAW controller events, by default set
|
||||
to 0. A value of 0 disables the debouncing feature. Increase this value if
|
||||
you notice custom actions or linked Hooks running multiple times when interacting
|
||||
with the device. When the controller needs to be debounced, usually a value
|
||||
of 100 is enough to remove all duplicate events.
|
||||
default: 0
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 1000.0
|
||||
unit_of_measurement: milliseconds
|
||||
mode: box
|
||||
step: 10.0
|
||||
variables:
|
||||
integration: !input integration
|
||||
button_up_long_loop: !input button_up_long_loop
|
||||
button_up_long_max_loop_repeats: !input button_up_long_max_loop_repeats
|
||||
button_up_double_press: !input button_up_double_press
|
||||
button_down_long_loop: !input button_down_long_loop
|
||||
button_down_long_max_loop_repeats: !input button_down_long_max_loop_repeats
|
||||
button_down_double_press: !input button_down_double_press
|
||||
helper_last_controller_event: !input helper_last_controller_event
|
||||
helper_double_press_delay: !input helper_double_press_delay
|
||||
helper_debounce_delay: !input helper_debounce_delay
|
||||
integration_id: '{{ integration | lower }}'
|
||||
adjusted_double_press_delay: '{{ [helper_double_press_delay - helper_debounce_delay,
|
||||
100] | max }}'
|
||||
actions_mapping:
|
||||
deconz:
|
||||
button_up_short:
|
||||
- '1002'
|
||||
button_up_long:
|
||||
- '1001'
|
||||
button_up_release:
|
||||
- '1003'
|
||||
button_down_short:
|
||||
- '2002'
|
||||
button_down_long:
|
||||
- '2001'
|
||||
button_down_release:
|
||||
- '2003'
|
||||
zha:
|
||||
button_up_short:
|
||||
- 'on'
|
||||
button_up_long:
|
||||
- move_with_on_off_0_83
|
||||
button_up_release:
|
||||
- stop
|
||||
button_down_short:
|
||||
- 'off'
|
||||
button_down_long:
|
||||
- move_1_83
|
||||
button_down_release:
|
||||
- stop
|
||||
zigbee2mqtt:
|
||||
button_up_short:
|
||||
- 'on'
|
||||
button_up_long:
|
||||
- brightness_move_up
|
||||
button_up_release:
|
||||
- brightness_stop
|
||||
button_down_short:
|
||||
- 'off'
|
||||
button_down_long:
|
||||
- brightness_move_down
|
||||
button_down_release:
|
||||
- brightness_stop
|
||||
button_up_short: '{{ actions_mapping[integration_id]["button_up_short"] }}'
|
||||
button_up_long: '{{ actions_mapping[integration_id]["button_up_long"] }}'
|
||||
button_up_release: '{{ actions_mapping[integration_id]["button_up_release"] }}'
|
||||
button_down_short: '{{ actions_mapping[integration_id]["button_down_short"] }}'
|
||||
button_down_long: '{{ actions_mapping[integration_id]["button_down_long"] }}'
|
||||
button_down_release: '{{ actions_mapping[integration_id]["button_down_release"]
|
||||
}}'
|
||||
integrations_with_prev_event_storage:
|
||||
- zha
|
||||
- zigbee2mqtt
|
||||
controller_entity: !input controller_entity
|
||||
controller_device: !input controller_device
|
||||
controller_id: '{% if integration_id=="zigbee2mqtt" %}{{controller_entity}}{% else
|
||||
%}{{controller_device}}{% endif %}'
|
||||
mode: restart
|
||||
max_exceeded: silent
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: state_changed
|
||||
event_data:
|
||||
entity_id: !input controller_entity
|
||||
- platform: event
|
||||
event_type:
|
||||
- deconz_event
|
||||
- zha_event
|
||||
event_data:
|
||||
device_id: !input controller_device
|
||||
condition:
|
||||
- condition: and
|
||||
conditions:
|
||||
- '{%- set trigger_action -%} {%- if integration_id == "zigbee2mqtt" -%} {{ trigger.event.data.new_state.state
|
||||
}} {%- elif integration_id == "deconz" -%} {{ trigger.event.data.event }} {%-
|
||||
elif integration_id == "zha" -%} {{ trigger.event.data.command }}{{"_" if trigger.event.data.args|length
|
||||
> 0}}{{ trigger.event.data.args|join("_") }} {%- endif -%} {%- endset -%} {{ trigger_action
|
||||
not in ["","None"] }}'
|
||||
- '{{ integration_id != "zigbee2mqtt" or trigger.event.data.new_state.state != trigger.event.data.old_state.state
|
||||
}}'
|
||||
action:
|
||||
- delay:
|
||||
milliseconds: !input helper_debounce_delay
|
||||
- variables:
|
||||
trigger_action: '{%- if integration_id == "zigbee2mqtt" -%} {{ trigger.event.data.new_state.state
|
||||
}} {%- elif integration_id == "deconz" -%} {{ trigger.event.data.event }} {%-
|
||||
elif integration_id == "zha" -%} {{ trigger.event.data.command }}{{"_" if trigger.event.data.args|length
|
||||
> 0}}{{ trigger.event.data.args|join("_") }} {%- endif -%}'
|
||||
trigger_delta: '{{ (as_timestamp(now()) - ((states(helper_last_controller_event)
|
||||
| from_json).t if helper_last_controller_event is not none and (states(helper_last_controller_event)
|
||||
| regex_match("^\{((\"a\": \".*\"|\"t\": \d+\.\d+)(, )?){2}\}$")) else as_timestamp("1970-01-01
|
||||
00:00:00"))) * 1000 }}'
|
||||
last_controller_event: '{{ (states(helper_last_controller_event) | from_json).a
|
||||
if helper_last_controller_event is not none and (states(helper_last_controller_event)
|
||||
| regex_match("^\{((\"a\": \".*\"|\"t\": \d+\.\d+)(, )?){2}\}$")) else "" }}'
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":trigger_action,"t":as_timestamp(now())} | to_json }}'
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in button_up_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_up_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_short
|
||||
- conditions: '{{ trigger_action | string in button_up_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_long
|
||||
- choose:
|
||||
- conditions: '{{ button_up_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_up_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_up_long
|
||||
default: !input action_button_up_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_up_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_up_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_release
|
||||
- conditions: '{{ trigger_action | string in button_down_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_down_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_short
|
||||
- conditions: '{{ trigger_action | string in button_down_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_long
|
||||
- choose:
|
||||
- conditions: '{{ button_down_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_down_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_down_long
|
||||
default: !input action_button_down_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_down_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_down_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_release
|
704
blueprints/automation/EPMatt/ikea_e2001_e2002.yaml
Normal file
@@ -0,0 +1,704 @@
|
||||
blueprint:
|
||||
name: Controller - IKEA E2001/E2002 STYRBAR Remote control
|
||||
description: "# Controller - IKEA E2001/E2002 STYRBAR Remote control\n\nController
|
||||
automation for executing any kind of action triggered by the provided IKEA E2001/E2002
|
||||
STYRBAR Remote control. Allows to optionally loop an action on a button long press.\nSupports
|
||||
deCONZ, ZHA, Zigbee2MQTT.\n\nAutomations created with this blueprint can be connected
|
||||
with one or more [Hooks](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/hooks)
|
||||
supported by this controller.\nHooks allow to easily create controller-based automations
|
||||
for interacting with media players, lights, covers and more.\nSee the list of
|
||||
[Hooks available for this controller](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/controllers/ikea_e2001_e2002#available-hooks)
|
||||
for additional details.\n\n\U0001F4D5 Full documentation regarding this blueprint
|
||||
is available [here](https://epmatt.github.io/awesome-ha-blueprints/docs/blueprints/controllers/ikea_e2001_e2002).\n\n\U0001F680
|
||||
This blueprint is part of the **[Awesome HA Blueprints](https://epmatt.github.io/awesome-ha-blueprints)
|
||||
project**.\n\nℹ️ Version 2022.08.08\n"
|
||||
source_url: https://github.com/EPMatt/awesome-ha-blueprints/blob/main/blueprints/controllers/ikea_e2001_e2002/ikea_e2001_e2002.yaml
|
||||
domain: automation
|
||||
input:
|
||||
integration:
|
||||
name: (Required) Integration
|
||||
description: Integration used for connecting the remote with Home Assistant.
|
||||
Select one of the available values.
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- deCONZ
|
||||
- ZHA
|
||||
- Zigbee2MQTT
|
||||
sort: false
|
||||
multiple: false
|
||||
custom_value: false
|
||||
controller_device:
|
||||
name: (deCONZ, ZHA) Controller Device
|
||||
description: The controller device to use for the automation. Choose a value
|
||||
only if the remote is integrated with deCONZ, ZHA.
|
||||
default: ''
|
||||
selector:
|
||||
device: {}
|
||||
controller_entity:
|
||||
name: (Zigbee2MQTT) Controller Entity
|
||||
description: The action sensor of the controller to use for the automation.
|
||||
Choose a value only if the remote is integrated with Zigbee2MQTT.
|
||||
default: ''
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- sensor
|
||||
multiple: false
|
||||
helper_last_controller_event:
|
||||
name: (Required) Helper - Last Controller Event
|
||||
description: Input Text used to store the last event fired by the controller.
|
||||
You will need to manually create a text input entity for this, please read
|
||||
the blueprint Additional Notes for more info.
|
||||
default: ''
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- input_text
|
||||
multiple: false
|
||||
action_button_left_short:
|
||||
name: (Optional) Left button short press
|
||||
description: Action to run on short left button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_left_long:
|
||||
name: (Optional) Left button long press
|
||||
description: Action to run on long left button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_left_release:
|
||||
name: (Optional) Left button release
|
||||
description: Action to run on left button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_left_double:
|
||||
name: (Optional) Left button double press
|
||||
description: Action to run on double left button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_right_short:
|
||||
name: (Optional) Right button short press
|
||||
description: Action to run on short right button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_right_long:
|
||||
name: (Optional) Right button long press
|
||||
description: Action to run on long right button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_right_release:
|
||||
name: (Optional) Right button release
|
||||
description: Action to run on right button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_right_double:
|
||||
name: (Optional) Right button double press
|
||||
description: Action to run on double right button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_short:
|
||||
name: (Optional) Up button short press
|
||||
description: Action to run on short up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_long:
|
||||
name: (Optional) Up button long press
|
||||
description: Action to run on long up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_release:
|
||||
name: (Optional) Up button release
|
||||
description: Action to run on up button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_up_double:
|
||||
name: (Optional) Up button double press
|
||||
description: Action to run on double up button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_short:
|
||||
name: (Optional) Down button short press
|
||||
description: Action to run on short down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_long:
|
||||
name: (Optional) Down button long press
|
||||
description: Action to run on long down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_release:
|
||||
name: (Optional) Down button release
|
||||
description: Action to run on down button release after long press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
action_button_down_double:
|
||||
name: (Optional) Down button double press
|
||||
description: Action to run on double down button press.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_left_long_loop:
|
||||
name: (Optional) Left button long press - loop until release
|
||||
description: Loop the left button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_left_long_max_loop_repeats:
|
||||
name: (Optional) Left button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_right_long_loop:
|
||||
name: (Optional) Right button long press - loop until release
|
||||
description: Loop the right button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_right_long_max_loop_repeats:
|
||||
name: (Optional) Right button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_up_long_loop:
|
||||
name: (Optional) Up button long press - loop until release
|
||||
description: Loop the up button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_up_long_max_loop_repeats:
|
||||
name: (Optional) Up button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_down_long_loop:
|
||||
name: (Optional) Down button long press - loop until release
|
||||
description: Loop the down button action until the button is released.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_down_long_max_loop_repeats:
|
||||
name: (Optional) Down button long press - Maximum loop repeats
|
||||
description: Maximum number of repeats for the custom action, when looping is
|
||||
enabled. Use it as a safety limit to prevent an endless loop in case the corresponding
|
||||
stop event is not received.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 5000.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
button_left_double_press:
|
||||
name: (Optional) Expose left button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the left button. Turn this on if you are providing an action for the left
|
||||
button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_right_double_press:
|
||||
name: (Optional) Expose right button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the right button. Turn this on if you are providing an action for the
|
||||
right button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_up_double_press:
|
||||
name: (Optional) Expose up button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the up button. Turn this on if you are providing an action for the up
|
||||
button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
button_down_double_press:
|
||||
name: (Optional) Expose down button double press event
|
||||
description: Choose whether or not to expose the virtual double press event
|
||||
for the down button. Turn this on if you are providing an action for the down
|
||||
button double press event.
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
helper_double_press_delay:
|
||||
name: (Optional) Helper - Double Press delay
|
||||
description: Max delay between the first and the second button press for the
|
||||
double press event. Provide a value only if you are using a double press action.
|
||||
Increase this value if you notice that the double press action is not triggered
|
||||
properly.
|
||||
default: 500
|
||||
selector:
|
||||
number:
|
||||
min: 100.0
|
||||
max: 5000.0
|
||||
unit_of_measurement: milliseconds
|
||||
mode: box
|
||||
step: 10.0
|
||||
helper_debounce_delay:
|
||||
name: (Optional) Helper - Debounce delay
|
||||
description: Delay used for debouncing RAW controller events, by default set
|
||||
to 0. A value of 0 disables the debouncing feature. Increase this value if
|
||||
you notice custom actions or linked Hooks running multiple times when interacting
|
||||
with the device. When the controller needs to be debounced, usually a value
|
||||
of 100 is enough to remove all duplicate events.
|
||||
default: 0
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 1000.0
|
||||
unit_of_measurement: milliseconds
|
||||
mode: box
|
||||
step: 10.0
|
||||
variables:
|
||||
integration: !input integration
|
||||
button_left_long_loop: !input button_left_long_loop
|
||||
button_left_long_max_loop_repeats: !input button_left_long_max_loop_repeats
|
||||
button_left_double_press: !input button_left_double_press
|
||||
button_right_long_loop: !input button_right_long_loop
|
||||
button_right_long_max_loop_repeats: !input button_right_long_max_loop_repeats
|
||||
button_right_double_press: !input button_right_double_press
|
||||
button_up_long_loop: !input button_up_long_loop
|
||||
button_up_long_max_loop_repeats: !input button_up_long_max_loop_repeats
|
||||
button_up_double_press: !input button_up_double_press
|
||||
button_down_long_loop: !input button_down_long_loop
|
||||
button_down_long_max_loop_repeats: !input button_down_long_max_loop_repeats
|
||||
button_down_double_press: !input button_down_double_press
|
||||
helper_last_controller_event: !input helper_last_controller_event
|
||||
helper_double_press_delay: !input helper_double_press_delay
|
||||
helper_debounce_delay: !input helper_debounce_delay
|
||||
integration_id: '{{ integration | lower }}'
|
||||
adjusted_double_press_delay: '{{ [helper_double_press_delay - helper_debounce_delay,
|
||||
100] | max }}'
|
||||
actions_mapping:
|
||||
deconz:
|
||||
button_left_short:
|
||||
- '3002'
|
||||
button_left_long:
|
||||
- '3001'
|
||||
button_left_release:
|
||||
- '3003'
|
||||
button_right_short:
|
||||
- '4002'
|
||||
button_right_long:
|
||||
- '4001'
|
||||
button_right_release:
|
||||
- '4003'
|
||||
button_up_short:
|
||||
- '1002'
|
||||
button_up_long:
|
||||
- '1001'
|
||||
button_up_release:
|
||||
- '1003'
|
||||
button_down_short:
|
||||
- '2002'
|
||||
button_down_long:
|
||||
- '2001'
|
||||
button_down_release:
|
||||
- '2003'
|
||||
zha:
|
||||
button_left_short:
|
||||
- press_257_13_0
|
||||
button_left_long:
|
||||
- hold_3329_0
|
||||
button_left_release:
|
||||
- release_1365
|
||||
button_right_short:
|
||||
- press_256_13_0
|
||||
button_right_long:
|
||||
- hold_3328_0
|
||||
button_right_release:
|
||||
- release_-27903
|
||||
button_up_short:
|
||||
- 'on'
|
||||
button_up_long:
|
||||
- move_with_on_off_0_83
|
||||
button_up_release:
|
||||
- stop
|
||||
button_down_short:
|
||||
- 'off'
|
||||
button_down_long:
|
||||
- move_1_83
|
||||
button_down_release:
|
||||
- stop
|
||||
zigbee2mqtt:
|
||||
button_left_short:
|
||||
- arrow_left_click
|
||||
button_left_long:
|
||||
- arrow_left_hold
|
||||
button_left_release:
|
||||
- arrow_left_release
|
||||
button_right_short:
|
||||
- arrow_right_click
|
||||
button_right_long:
|
||||
- arrow_right_hold
|
||||
button_right_release:
|
||||
- arrow_right_release
|
||||
button_up_short:
|
||||
- 'on'
|
||||
button_up_long:
|
||||
- brightness_move_up
|
||||
button_up_release:
|
||||
- brightness_stop
|
||||
button_down_short:
|
||||
- 'off'
|
||||
button_down_long:
|
||||
- brightness_move_down
|
||||
button_down_release:
|
||||
- brightness_stop
|
||||
button_left_short: '{{ actions_mapping[integration_id]["button_left_short"] }}'
|
||||
button_left_long: '{{ actions_mapping[integration_id]["button_left_long"] }}'
|
||||
button_left_release: '{{ actions_mapping[integration_id]["button_left_release"]
|
||||
}}'
|
||||
button_right_short: '{{ actions_mapping[integration_id]["button_right_short"] }}'
|
||||
button_right_long: '{{ actions_mapping[integration_id]["button_right_long"] }}'
|
||||
button_right_release: '{{ actions_mapping[integration_id]["button_right_release"]
|
||||
}}'
|
||||
button_up_short: '{{ actions_mapping[integration_id]["button_up_short"] }}'
|
||||
button_up_long: '{{ actions_mapping[integration_id]["button_up_long"] }}'
|
||||
button_up_release: '{{ actions_mapping[integration_id]["button_up_release"] }}'
|
||||
button_down_short: '{{ actions_mapping[integration_id]["button_down_short"] }}'
|
||||
button_down_long: '{{ actions_mapping[integration_id]["button_down_long"] }}'
|
||||
button_down_release: '{{ actions_mapping[integration_id]["button_down_release"]
|
||||
}}'
|
||||
integrations_with_prev_event_storage:
|
||||
- zha
|
||||
- zigbee2mqtt
|
||||
controller_entity: !input controller_entity
|
||||
controller_device: !input controller_device
|
||||
controller_id: '{% if integration_id=="zigbee2mqtt" %}{{controller_entity}}{% else
|
||||
%}{{controller_device}}{% endif %}'
|
||||
mode: restart
|
||||
max_exceeded: silent
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: state_changed
|
||||
event_data:
|
||||
entity_id: !input controller_entity
|
||||
- platform: event
|
||||
event_type:
|
||||
- deconz_event
|
||||
- zha_event
|
||||
event_data:
|
||||
device_id: !input controller_device
|
||||
condition:
|
||||
- condition: and
|
||||
conditions:
|
||||
- '{%- set trigger_action -%} {%- if integration_id == "zigbee2mqtt" -%} {{ trigger.event.data.new_state.state
|
||||
}} {%- elif integration_id == "deconz" -%} {{ trigger.event.data.event }} {%-
|
||||
elif integration_id == "zha" -%} {{ trigger.event.data.command }}{{"_" if trigger.event.data.args|length
|
||||
> 0}}{{ trigger.event.data.args|join("_") }} {%- endif -%} {%- endset -%} {{ trigger_action
|
||||
not in ["","None"] }}'
|
||||
- '{{ integration_id != "zigbee2mqtt" or trigger.event.data.new_state.state != trigger.event.data.old_state.state
|
||||
}}'
|
||||
action:
|
||||
- delay:
|
||||
milliseconds: !input helper_debounce_delay
|
||||
- variables:
|
||||
trigger_action: '{%- if integration_id == "zigbee2mqtt" -%} {{ trigger.event.data.new_state.state
|
||||
}} {%- elif integration_id == "deconz" -%} {{ trigger.event.data.event }} {%-
|
||||
elif integration_id == "zha" -%} {{ trigger.event.data.command }}{{"_" if trigger.event.data.args|length
|
||||
> 0}}{{ trigger.event.data.args|join("_") }} {%- endif -%}'
|
||||
trigger_delta: '{{ (as_timestamp(now()) - ((states(helper_last_controller_event)
|
||||
| from_json).t if helper_last_controller_event is not none and (states(helper_last_controller_event)
|
||||
| regex_match("^\{((\"a\": \".*\"|\"t\": \d+\.\d+)(, )?){2}\}$")) else as_timestamp("1970-01-01
|
||||
00:00:00"))) * 1000 }}'
|
||||
last_controller_event: '{{ (states(helper_last_controller_event) | from_json).a
|
||||
if helper_last_controller_event is not none and (states(helper_last_controller_event)
|
||||
| regex_match("^\{((\"a\": \".*\"|\"t\": \d+\.\d+)(, )?){2}\}$")) else "" }}'
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":trigger_action,"t":as_timestamp(now())} | to_json }}'
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in button_left_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_left_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_left_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_left_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_left_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_left_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_left_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_left_short
|
||||
- conditions: '{{ trigger_action | string in button_left_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_left_long
|
||||
- choose:
|
||||
- conditions: '{{ button_left_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_left_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_left_long
|
||||
default: !input action_button_left_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_left_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_left_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_left_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_left_release
|
||||
- conditions: '{{ trigger_action | string in button_right_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_right_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_right_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_right_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_right_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_right_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_right_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_right_short
|
||||
- conditions: '{{ trigger_action | string in button_right_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_right_long
|
||||
- choose:
|
||||
- conditions: '{{ button_right_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_right_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_right_long
|
||||
default: !input action_button_right_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_right_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_right_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_right_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_right_release
|
||||
- conditions: '{{ trigger_action | string in button_up_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_up_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_short
|
||||
- conditions: '{{ trigger_action | string in button_up_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_long
|
||||
- choose:
|
||||
- conditions: '{{ button_up_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_up_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_up_long
|
||||
default: !input action_button_up_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_up_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_up_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_up_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_up_release
|
||||
- conditions: '{{ trigger_action | string in button_down_short }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ button_down_double_press }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions: '{{ trigger_action | string in states(helper_last_controller_event)
|
||||
and trigger_delta | int <= helper_double_press_delay | int }}'
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: !input helper_last_controller_event
|
||||
value: '{{ {"a":"double_press","t":as_timestamp(now())} | to_json
|
||||
}}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_double
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_double
|
||||
default:
|
||||
- delay:
|
||||
milliseconds: '{{ adjusted_double_press_delay }}'
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_short
|
||||
default:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_short
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_short
|
||||
- conditions: '{{ trigger_action | string in button_down_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_long
|
||||
- choose:
|
||||
- conditions: '{{ button_down_long_loop }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: '{{ repeat.index < button_down_long_max_loop_repeats | int }}'
|
||||
sequence: !input action_button_down_long
|
||||
default: !input action_button_down_long
|
||||
- conditions:
|
||||
- '{{ trigger_action | string in button_down_release }}'
|
||||
- '{{ not integration_id in integrations_with_prev_event_storage or last_controller_event
|
||||
| string in button_down_long }}'
|
||||
sequence:
|
||||
- event: ahb_controller_event
|
||||
event_data:
|
||||
controller: '{{ controller_id }}'
|
||||
action: button_down_release
|
||||
- choose:
|
||||
- conditions: []
|
||||
sequence: !input action_button_down_release
|
315
blueprints/automation/RDG88/nightclock_awtrix.yaml
Normal file
@@ -0,0 +1,315 @@
|
||||
blueprint:
|
||||
name: AWTRIX Night Clock
|
||||
description: "## AWTRIX Night Clock\nThis blueprint provides a night clock mode
|
||||
for AWTRIX Light. It allows you to personalize various aspects of the clockface
|
||||
to suit your preferences.\n### Screenshot\n\n \n
|
||||
\ \n### Features\n- This blueprint features a night clock mode, which displays
|
||||
a customized color, and you have the possibility to disable app transitions, automatic
|
||||
brightness, and the color of the night clock.\n### Prerequisites\nAWTRIX v0.72\n"
|
||||
domain: automation
|
||||
input:
|
||||
awtrix:
|
||||
name: AWTRIX Light
|
||||
description: Select the Awtrix light
|
||||
selector:
|
||||
device:
|
||||
filter:
|
||||
- integration: mqtt
|
||||
manufacturer: Blueforcer
|
||||
model: AWTRIX Light
|
||||
multiple: true
|
||||
sleep_time:
|
||||
name: Night mode time
|
||||
description: At what time does the clock need to activate night mode?
|
||||
selector:
|
||||
time: {}
|
||||
default: '23:00:00'
|
||||
wake_up_time:
|
||||
name: Day mode time
|
||||
description: At what time does the clock need activate day mode?
|
||||
selector:
|
||||
time: {}
|
||||
default: 08:00:00
|
||||
sleep_settings_atrans:
|
||||
name: Enable automatic app transition in night mode.
|
||||
description: 'This setting allows you to enable or disable automatic transition
|
||||
of apps in night mode.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
sleep_settings_bri:
|
||||
name: Night mode brightness setting
|
||||
description: 'What is the brightness for night mode?
|
||||
|
||||
'
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 255.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
default: '1'
|
||||
sleep_settings_abri:
|
||||
name: Enable automatic brightness when the night mode is active.
|
||||
description: 'This setting allows you to enable or disable automatic brightening
|
||||
in night mode.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
sleep_settings_color:
|
||||
name: Color setting for the night mode clock
|
||||
description: This setting allows you to change the color of the night mode clock.
|
||||
selector:
|
||||
color_rgb: {}
|
||||
default:
|
||||
- 255
|
||||
- 0
|
||||
- 0
|
||||
sleep_weekday_bar:
|
||||
name: Enable the weekday bar for the night mode clock
|
||||
description: 'This setting allows you to enable or disable the weekday bar for
|
||||
the night mode clock.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
sleep_time_format:
|
||||
name: Select the time format for the night mode clock
|
||||
description: 'Select the time format for the night mode clock
|
||||
|
||||
'
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: '13:30:45'
|
||||
value: '%H:%M:%S'
|
||||
- label: '1:30:45'
|
||||
value: '%l:%M:%S'
|
||||
- label: '13:30'
|
||||
value: '%H:%M'
|
||||
- label: 13:30 with blinking colon
|
||||
value: '%H %M'
|
||||
- label: '1:30'
|
||||
value: '%l:%M'
|
||||
- label: 1:30 with blinking colon
|
||||
value: '%l %M'
|
||||
- label: 1:30 PM
|
||||
value: '%l:%M %p'
|
||||
- label: 1:30 PM with blinking colon
|
||||
value: '%l %M %p'
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: '%H:%M'
|
||||
wakeup_settings_atrans:
|
||||
name: Enable automatic app transitions in day mode?
|
||||
description: 'This setting allows you to enable or disable the automatic transition
|
||||
of apps in day mode.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: true
|
||||
wakeup_settings_bri:
|
||||
name: Day mode brightness setting (1-255)
|
||||
description: 'This setting allows you to adjust the brightness for day mode,
|
||||
pick a value between 1 and 255.
|
||||
|
||||
'
|
||||
selector:
|
||||
number:
|
||||
min: 1.0
|
||||
max: 255.0
|
||||
mode: slider
|
||||
step: 1.0
|
||||
default: '127'
|
||||
wakeup_settings_abri:
|
||||
name: Enable automatic brightness in day mode
|
||||
description: 'This setting allows you to enable or disable automatic brightening
|
||||
in day mode.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
wakeup_settings_color:
|
||||
name: Color setting for the day mode clock
|
||||
description: This setting allows you to change the color of the day mode clock.
|
||||
selector:
|
||||
color_rgb: {}
|
||||
default:
|
||||
- 255
|
||||
- 255
|
||||
- 255
|
||||
wakeup_settings_calendar_color:
|
||||
name: Color setting for the day mode calendar
|
||||
description: This setting allows you to change the color of the day mode calendar.
|
||||
selector:
|
||||
color_rgb: {}
|
||||
default:
|
||||
- 255
|
||||
- 0
|
||||
- 0
|
||||
wakeup_settings_calendar_text_color:
|
||||
name: Color setting for the day mode calendar text
|
||||
description: This settings allows you to change the color of the day mode text
|
||||
in the calendar.
|
||||
selector:
|
||||
color_rgb: {}
|
||||
default:
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
wakeup_weekday_bar:
|
||||
name: Enable the weekday bar for the day mode clock
|
||||
description: 'This setting allows you to enable or disable the weekday bar for
|
||||
the day mode clock.
|
||||
|
||||
'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: false
|
||||
wakeup_calendar_style:
|
||||
name: Select the time/calendar style for the day mode clock
|
||||
description: "Select the time/calendar style\n\n 
|
||||
`Style 0` \n\n ---\n\n 
|
||||
`Style 1` \n\n ---\n\n 
|
||||
`Style 2` \n\n ---\n\n 
|
||||
`Style 3` \n\n ---\n\n 
|
||||
`Style 4`\n"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: Style 0
|
||||
value: '0'
|
||||
- label: Style 1
|
||||
value: '1'
|
||||
- label: Style 2
|
||||
value: '2'
|
||||
- label: Style 3
|
||||
value: '3'
|
||||
- label: Style 4
|
||||
value: '4'
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: '1'
|
||||
wakeup_time_format:
|
||||
name: Select the time format for the day mode clock
|
||||
description: 'Select the time format for the day mode clock
|
||||
|
||||
'
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: '13:30:45'
|
||||
value: '%H:%M:%S'
|
||||
- label: '1:30:45'
|
||||
value: '%l:%M:%S'
|
||||
- label: '13:30'
|
||||
value: '%H:%M'
|
||||
- label: 13:30 with blinking colon
|
||||
value: '%H %M'
|
||||
- label: '1:30'
|
||||
value: '%l:%M'
|
||||
- label: 1:30 with blinking colon
|
||||
value: '%l %M'
|
||||
- label: 1:30 PM
|
||||
value: '%l:%M %p'
|
||||
- label: 1:30 PM with blinking colon
|
||||
value: '%l %M %p'
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: '%H:%M'
|
||||
source_url: https://raw.githubusercontent.com/RDG88/Homeassistant_Blueprints/main/nightclock_awtrix.yaml
|
||||
variables:
|
||||
device_ids: !input awtrix
|
||||
wake_up_time: !input wake_up_time
|
||||
sleep_time: !input sleep_time
|
||||
wakeup_weekday_bar: !input wakeup_weekday_bar
|
||||
sleep_weekday_bar: !input sleep_weekday_bar
|
||||
wakeup_settings_atrans: !input wakeup_settings_atrans
|
||||
wakeup_settings_color: !input wakeup_settings_color
|
||||
sleep_settings_atrans: !input sleep_settings_atrans
|
||||
sleep_settings_color: !input sleep_settings_color
|
||||
sleep_settings_abri: !input sleep_settings_abri
|
||||
sleep_settings_bri: !input sleep_settings_bri
|
||||
sleep_time_format: !input sleep_time_format
|
||||
wakeup_settings_bri: !input wakeup_settings_bri
|
||||
wakeup_settings_abri: !input wakeup_settings_abri
|
||||
wakeup_calendar_style: !input wakeup_calendar_style
|
||||
wakeup_settings_calendar_color: !input wakeup_settings_calendar_color
|
||||
wakeup_settings_calendar_text_color: !input wakeup_settings_calendar_text_color
|
||||
wakeup_time_format: !input wakeup_time_format
|
||||
awtrix_devices: "{%- set ns = namespace(awtrix = []) -%} {%- for device_id in device_ids
|
||||
-%}\n {%- set device_name = iif(device_attr(device_id, 'name_by_user') != none,
|
||||
device_attr(device_id, 'name_by_user'), device_attr(device_id, 'name')) -%}\n
|
||||
\ {%- set entity = expand(device_entities(device_id)) | select('search', 'device_topic')
|
||||
| map(attribute='entity_id') | first -%}\n {%- set topic = states(entity) -%}\n
|
||||
\ {% set ns.awtrix = ns.awtrix + [{\"device\": device_name, \"entity\": entity,
|
||||
\"topic\": topic}] -%}\n{%- endfor -%} {{ ns.awtrix }}"
|
||||
payload_sleep_switch: "{\n \"name\": \"time\"\n}"
|
||||
payload_sleep_settings: "{ \"ATRANS\": {{ sleep_settings_atrans | lower }}, \n \"BRI\":
|
||||
{{ sleep_settings_bri }}, \n \"ABRI\": {{ sleep_settings_abri | lower }},\n \"TMODE\":
|
||||
0,\n \"TFORMAT\": \"{{ sleep_time_format }}\",\n \"WD\": {{ sleep_weekday_bar
|
||||
| lower }},\n \"TIME_COL\": {{ sleep_settings_color }}\n}"
|
||||
payload_wakeup_settings: "{ \"ATRANS\": {{ wakeup_settings_atrans | lower }}, \n
|
||||
\ \"BRI\": {{ wakeup_settings_bri }}, \n \"ABRI\": {{ wakeup_settings_abri |
|
||||
lower }},\n \"CCOL\": {{ wakeup_settings_calendar_color }},\n \"CTCOL\": {{
|
||||
wakeup_settings_calendar_text_color }},\n \"TMODE\": {{ wakeup_calendar_style
|
||||
}},\n \"TFORMAT\": \"{{ wakeup_time_format }}\",\n \"WD\": {{ wakeup_weekday_bar
|
||||
| lower }},\n \"TIME_COL\": {{ wakeup_settings_color }}\n}"
|
||||
trigger:
|
||||
- platform: time
|
||||
at: !input wake_up_time
|
||||
id: wakeup_timer
|
||||
- platform: time
|
||||
at: !input sleep_time
|
||||
id: sleep_timer
|
||||
condition: []
|
||||
action:
|
||||
- repeat:
|
||||
for_each: '{{ awtrix_devices }}'
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ states(repeat.item.entity) not in [''unavailable'',
|
||||
''unknown''] }}
|
||||
|
||||
'
|
||||
sequence:
|
||||
- if:
|
||||
- condition: trigger
|
||||
id:
|
||||
- wakeup_timer
|
||||
then:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item.topic ~ ''/settings''}}'
|
||||
payload: '{{ payload_wakeup_settings }}'
|
||||
- if:
|
||||
- condition: trigger
|
||||
id:
|
||||
- sleep_timer
|
||||
then:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item.topic ~ ''/settings''}}'
|
||||
payload: '{{ payload_sleep_settings }}'
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item.topic ~ ''/switch''}}'
|
||||
payload: '{{ payload_sleep_switch }}'
|
110
blueprints/automation/cecche/ikea-somrig-remote-e2213-zha.yaml
Normal file
@@ -0,0 +1,110 @@
|
||||
blueprint:
|
||||
name: ZHA - IKEA Somrig remote dot-buttons control
|
||||
description: Fully customisable dot-buttons, with options for single, double and
|
||||
long press for each one.
|
||||
domain: automation
|
||||
input:
|
||||
remote:
|
||||
name: Remote
|
||||
description: IKEA Somrig remote to use
|
||||
selector:
|
||||
device:
|
||||
integration: zha
|
||||
manufacturer: IKEA of Sweden
|
||||
model: SOMRIG shortcut button
|
||||
multiple: false
|
||||
single_dot_single_press:
|
||||
name: Single dot (Single press)
|
||||
description: Action to run on single dot press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
single_dot_double_press:
|
||||
name: Single dot (Double press)
|
||||
description: Action to run on single dot double press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
single_dot_long_press:
|
||||
name: Single dot (Long press)
|
||||
description: Action to run on single dot long press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
single_dot_long_release:
|
||||
name: Single dot (Release after long press)
|
||||
description: Action to run on releasing after a long press on the single dot
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
double_dot_single_press:
|
||||
name: Double dot (Single press)
|
||||
description: Action to run on double dot press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
double_dot_double_press:
|
||||
name: Double dot (Double press)
|
||||
description: Action to run on double dot double press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
double_dot_long_press:
|
||||
name: Double dot (Long press)
|
||||
description: Action to run on double dot long press
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
double_dot_long_release:
|
||||
name: Double dot (Release after long press)
|
||||
description: Action to run on releasing after a long press on the double dot
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
source_url: https://community.home-assistant.io/t/ikea-somrig-remote-e2213-zha/668671
|
||||
mode: single
|
||||
max_exceeded: silent
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: zha_event
|
||||
event_data:
|
||||
device_id: !input remote
|
||||
action:
|
||||
- variables:
|
||||
command: '{{ trigger.event.data.command }}'
|
||||
cluster_id: '{{ trigger.event.data.cluster_id }}'
|
||||
endpoint_id: '{{ trigger.event.data.endpoint_id }}'
|
||||
args: '{{ trigger.event.data.args }}'
|
||||
- choose:
|
||||
- conditions:
|
||||
- '{{ command == ''short_release'' }}'
|
||||
- '{{ endpoint_id == 1 }}'
|
||||
sequence: !input single_dot_single_press
|
||||
- conditions:
|
||||
- '{{ command == ''multi_press_complete'' }}'
|
||||
- '{{ endpoint_id == 1 }}'
|
||||
sequence: !input single_dot_double_press
|
||||
- conditions:
|
||||
- '{{ command == ''long_press'' }}'
|
||||
- '{{ endpoint_id == 1 }}'
|
||||
sequence: !input single_dot_long_press
|
||||
- conditions:
|
||||
- '{{ command == ''long_release'' }}'
|
||||
- '{{ endpoint_id == 1 }}'
|
||||
sequence: !input single_dot_long_release
|
||||
- conditions:
|
||||
- '{{ command == ''short_release'' }}'
|
||||
- '{{ endpoint_id == 2 }}'
|
||||
sequence: !input double_dot_single_press
|
||||
- conditions:
|
||||
- '{{ command == ''multi_press_complete'' }}'
|
||||
- '{{ endpoint_id == 2 }}'
|
||||
sequence: !input double_dot_double_press
|
||||
- conditions:
|
||||
- '{{ command == ''long_press'' }}'
|
||||
- '{{ endpoint_id == 2 }}'
|
||||
sequence: !input double_dot_long_press
|
||||
- conditions:
|
||||
- '{{ command == ''long_release'' }}'
|
||||
- '{{ endpoint_id == 2 }}'
|
||||
sequence: !input double_dot_long_release
|
677
blueprints/automation/jeeftor/awtrix_weatherflow.yaml
Normal file
@@ -0,0 +1,677 @@
|
||||
blueprint:
|
||||
name: "AWTRIX Weather ⛈️ + Forecast + \U0001F315️"
|
||||
description: "\nThis is somewhat of a mega-weather blueprint with moon phase support.
|
||||
However for it work correctly you will need a variety of different things setup.
|
||||
It was initially designed to use in partnership with a personal weather station
|
||||
however it seems to work fine with OpenWeather as well or any other provider that
|
||||
offers an hourly forecast.\n\n\n\n\n\nThis
|
||||
blueprint will publish to two separate topics. `jeef_weather` for the weather
|
||||
report and `jeef_weather_sun` if its near sunrise/set\n## ⚠️ REQUIREMENTS ⚠️\nFor
|
||||
this blueprint to work you MUST have a few things pre-setup. \n### Moon Integration
|
||||
\U0001F315️\n .------.\n ( I MOON ) ..\n `------' .' /\n O
|
||||
\ / ;\n o i OO\n C `-. Make sure you've\n |
|
||||
\ <-' enabled\n ( ,--. the MOON Sensor\n V
|
||||
\ \\_)\n \\ :\n `._\\. \n\n\nThe moon integration
|
||||
is required. You can add it via the [moon](https://www.home-assistant.io/integrations/moon/)
|
||||
page or just by [clicking here](https://my.home-assistant.io/redirect/config_flow_start?domain=moon)\n###
|
||||
Moon Rise/Set Sensor \U0001F315️ ⏲️\n\n M\n (X)\n // \\\\
|
||||
\ Lets use a GeoLocation to find\n // \\\\ out the Moon Rise / Set\n
|
||||
\ // \\\\ TIMES\n // \\\\\n / \\\n\nAs Home
|
||||
Assistant doesn't _currently_ provide moon rise/set times you will need to get
|
||||
this from some api. You can use the [ipgeolocation](https://app.ipgeolocation.io)
|
||||
API.\nTo do so you will need to create an account and extract your `API_KEY`.
|
||||
Additionally you need your `LAT` and `LON`.\nThen you can add a [REST](https://www.home-assistant.io/integrations/sensor.rest/)
|
||||
sensor to your `configuraiton.yaml` file like the one here:\n\n resource: https://api.ipgeolocation.io/astronomy?lat=<LAT>&long=<LON>&apiKey=<API_KEY>\n
|
||||
\ name: ip_geo_location\n scan_interval: 300\n value_template: \"OK\"\n
|
||||
\ json_attributes:\n - moonrise\n - moonset\n - moon_altitude\n\n###
|
||||
Icons\nYou can call my custom script which will prompt you for an Awtrix device
|
||||
and then upload the required icons:\n \n (If you have windows I don't know if
|
||||
this will work)\n\n bash -c \"$(curl -fsSL https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/icons/upload_icon.sh)\"\n\n_This
|
||||
blueprint ~will~ may be updated as new features_\n
|
||||
https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/blueprints/automation/awtrix_weatherflow.yaml\n"
|
||||
domain: automation
|
||||
input:
|
||||
awtrix:
|
||||
name: AWTRIX Device
|
||||
description: Select the Awtrix light
|
||||
selector:
|
||||
device:
|
||||
integration: mqtt
|
||||
manufacturer: Blueforcer
|
||||
model: AWTRIX Light
|
||||
multiple: true
|
||||
forecast_var:
|
||||
name: Hourly Forecast
|
||||
description: "Select a sensor that provides an Hourly forecast (not a daily
|
||||
one)\nThis integration has been tested with:\n\n - HACS [Weatherflow](https://github.com/briis/hass-weatherflow)
|
||||
integration \n \n - HomeAssistant [Openweather](https://www.home-assistant.io/integrations/openweathermap/)\n"
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- domain:
|
||||
- weather
|
||||
multiple: false
|
||||
hours_to_show:
|
||||
name: Forecast Hours to Show
|
||||
description: 'How many hours of forecast do you wish to show along the bottom
|
||||
of the display
|
||||
|
||||
'
|
||||
selector:
|
||||
number:
|
||||
max: 24.0
|
||||
min: 0.0
|
||||
unit_of_measurement: hours
|
||||
mode: box
|
||||
step: 1.0
|
||||
default: 12
|
||||
forecast_temp_field:
|
||||
name: Temperature Attributes
|
||||
description: "Once you've selected your hourly forecast you will need to identify
|
||||
which attributes in the forecast provides a temperature value. \n\n - If
|
||||
you are using [Weatherflow](https://github.com/briis/hass-weatherflow) you
|
||||
may be able to select from either `feels_like` or `temperature`\n\n - In
|
||||
[Openweather](https://www.home-assistant.io/integrations/openweathermap/)
|
||||
you only have access to `temperature`\n"
|
||||
selector:
|
||||
text: {}
|
||||
default: feels_like
|
||||
temp_digits:
|
||||
name: Temp Digits
|
||||
description: 'By default we will round the temp to the nearest whole-number.
|
||||
If you want percisions you can change this to 1 or 2 in order to see more
|
||||
decimalm places.
|
||||
|
||||
'
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 2.0
|
||||
step: 1.0
|
||||
mode: box
|
||||
unit_of_measurement: Decimal places
|
||||
default: 0
|
||||
temp_suffix:
|
||||
name: Temperature suffix
|
||||
description: "How do you want to display the temperature\nIf you live in a country
|
||||
with the following flags:\n\U0001F1FA\U0001F1F8️\U0001F1F5\U0001F1F7️\U0001F1F5\U0001F1FC️\U0001F1E7\U0001F1FF️\U0001F1F0\U0001F1FE️\U0001F1EB\U0001F1F2️\U0001F1F2\U0001F1ED️\U0001F1FB\U0001F1EE️\U0001F1EC\U0001F1FA️\nYou
|
||||
probbaly use Farenheit.\nEverybody else in the \U0001F5FA️ seems to rock the
|
||||
Metric System"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: None
|
||||
value: ''
|
||||
- label: °
|
||||
value: °
|
||||
- label: °F
|
||||
value: °F
|
||||
- label: °C
|
||||
value: °C
|
||||
- label: F
|
||||
value: F
|
||||
- label: C
|
||||
value: C
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: °
|
||||
current_temp_var:
|
||||
name: The current outside temperature
|
||||
description: "Select a sensor either from a PWS or a forecast that provides
|
||||
the current outside temperature you wish to display:\n\n - `sensor.openweathermap_feels_like_temperature`\n"
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
- sensor
|
||||
multiple: false
|
||||
default: sensor.weatherflow_air_temperature
|
||||
color_matrix_json:
|
||||
name: Color Matrix
|
||||
description: "The `Color Matrix` will control colors map to temperature ranges
|
||||
on the display. The format of this map is **JSON** \nHere you can enter a
|
||||
temperature to color mapping. \n> Please note the format is *JSON*,\n \n\nSome
|
||||
possible mappings are:\n#### USA: Farenheit 0-100 (Based on NOAA scale from
|
||||
0-100)\n\n\n {\"0\": \"#FEC4FF\",\"10\": \"#D977DF\",\"20\": \"#9545BC\",\"30\":
|
||||
\"#4B379C\",\"40\": \"#31B8DB\",\"50\": \"#31DB8B\",\"60\": \"#6ED228\",\"70\":
|
||||
\"#FFFF28\",\"80\": \"#F87E27\",\"90\": \"#CF3927\",\"100\": \"#A12527\"}\n\n\n####
|
||||
EURO: -12°c to -38°c based on USA NOAA Colors \n\n {\"-12\": \"#D977DF\",\"-6\":
|
||||
\"#9545BC\",\"-1\": \"#4B379C\",\"0\": \"#FEC4FF\",\"4\": \"#31B8DB\",\"10\":
|
||||
\"#31DB8B\",\"15\": \"#6ED228\",\"21\": \"#FFFF28\",\"27\": \"#F87E27\",\"32\":
|
||||
\"#CF3927\",\"38\": \"#A12527\"}\n"
|
||||
selector:
|
||||
text:
|
||||
multiline: true
|
||||
default: "{\n \"0\": \"#FEC4FF\",\n \"10\": \"#D977DF\",\n \"20\": \"#9545BC\",\n
|
||||
\ \"30\": \"#4B379C\",\n \"40\": \"#31B8DB\",\n \"50\": \"#31DB8B\",\n \"60\":
|
||||
\"#6ED228\",\n \"70\": \"#FFFF28\",\n \"80\": \"#F87E27\",\n \"90\": \"#CF3927\",\n
|
||||
\ \"100\": \"#A12527\"\n}\n"
|
||||
moon:
|
||||
name: Moon Phase Sensor
|
||||
description: "\U0001F311️\U0001F312️\U0001F313️\U0001F314️\U0001F316️\U0001F317️\U0001F318️\nTo
|
||||
setup a moon sensor see here: https://www.home-assistant.io/integrations/moon/\nor
|
||||
just [clicking here](https://my.home-assistant.io/redirect/config_flow_start?domain=moon)\n"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- integration: moon
|
||||
moon_rise_set:
|
||||
name: Moon Riese/Set Sensor
|
||||
description: "As Home Assistant doesn't provide moon rise/set times you will
|
||||
need to get this from some api. In my personal setup I use [ipgeolocation](https://app.ipgeolocation.io)
|
||||
as my api.\nYou can create a custom REST sensor as follows:\n``` sensor: -
|
||||
platform: rest\n resource: https://api.ipgeolocation.io/astronomy?lat=<LAT>&long=-<LON>&apiKey=<API_KEY>\n
|
||||
\ name: ip_geo_location\n scan_interval: 300\n value_template: \"OK\"\n
|
||||
\ json_attributes:\n - moonrise\n - moonset\n - moon_altitude\n```\n"
|
||||
selector:
|
||||
entity:
|
||||
multiple: false
|
||||
filter:
|
||||
- integration: rest
|
||||
when_show_moon:
|
||||
name: When should the moon be displayed
|
||||
description: "Some people are really into the moon \U0001F43A️ and they are
|
||||
called Wearwolves or maybe Astronomers. \n\nPlease select how and when you
|
||||
want the moon displayed\n\nBy selecting `Always show moon` the moon will always
|
||||
be drawn to the right of the display. Otherwise the moon will only be drawn
|
||||
if its risen depending on the option selected.\n### NOTE:\n\n At Brightness
|
||||
values less than 29 the greys of the moon will render green on the clock.\n"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: Never show moon
|
||||
value: never
|
||||
- label: Always show moon
|
||||
value: always
|
||||
- label: Only show moon if its risen
|
||||
value: risen
|
||||
- label: Only show moon if risen + night
|
||||
value: night
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: night
|
||||
use_moon_clear_night:
|
||||
name: Swap Clear Night for Moon
|
||||
description: '
|
||||
|
||||
The default case is for the moon to be drawn to the right-side of the clock,
|
||||
however, you have the option if this is selected to repalce the `clear_night`
|
||||
icon with the moon icon. This will only swap icons if the moon is currently
|
||||
being displayed.
|
||||
|
||||
- 
|
||||
- `full_moon`
|
||||
|
||||
- 
|
||||
- `waning_gibbous`
|
||||
|
||||
- 
|
||||
- `last_quarter`
|
||||
|
||||
- 
|
||||
- `waning_crescent`
|
||||
|
||||
- 
|
||||
- `new_moon`
|
||||
|
||||
- 
|
||||
- `waxing_crescent`
|
||||
|
||||
- 
|
||||
- `first_quarter`
|
||||
|
||||
- 
|
||||
- `waxing_gibbous`
|
||||
|
||||
If you wish to use a different icon please enter its text in the box to the
|
||||
right'
|
||||
selector:
|
||||
boolean: {}
|
||||
default: true
|
||||
use_moon_sunny_night:
|
||||
name: Swap Sunny + Night for the Moon
|
||||
description: Some weather integrations may not correctly implement the `clear-night`
|
||||
weather state. In that case you can use this option to automatically swap
|
||||
out the moon for if you have night + sunny
|
||||
selector:
|
||||
boolean: {}
|
||||
default: true
|
||||
show_sun_rise_set:
|
||||
name: ☀️ Show Sunrise/Sunset
|
||||
description: "Prior to both sunrise and sunset times offer a message about pending
|
||||
sun transitional state.\n\n :\n `. ; .'\n
|
||||
\ `. .-'''-. .'\n ;' __ _;'\n / '_ _`\\
|
||||
\ TURN ME ON!\n | _( a ( a |\n ''''| (_) > |``````\n
|
||||
\ \\ \\ / /\n `. `--'.'\n .' `-,,,-' `.\n
|
||||
\ .' : `.\n :\n\n\n_You can change the icons
|
||||
for sun rise/set way down below._\n"
|
||||
selector:
|
||||
boolean: {}
|
||||
default: true
|
||||
sun_event_minute_threshold:
|
||||
name: "Sun Time Prior \U0001F570️"
|
||||
description: "This value controls when to show sunrise/set notifications. \n\nIf
|
||||
the sunrise will occur in `50` minutes and this value is set to `60` it will
|
||||
show, however if this value is only `30` it won't show."
|
||||
selector:
|
||||
number:
|
||||
min: 5.0
|
||||
max: 1440.0
|
||||
unit_of_measurement: min
|
||||
step: 1.0
|
||||
mode: slider
|
||||
default: 30
|
||||
sun_time_type:
|
||||
name: Sun Time Type
|
||||
description: "When showing a notification about sun rise/set it can offer 3
|
||||
different time formats:\n\n - Relative Time: `12 min`\n - Actual Time:
|
||||
\ `8:31 pm` or `22:31`\n"
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- Relative
|
||||
- Actual
|
||||
sort: false
|
||||
custom_value: false
|
||||
multiple: false
|
||||
default: Actual
|
||||
sun_time_format:
|
||||
name: Actual Time Format
|
||||
description: "If you are using actual time you can enter a STRFTIME format string
|
||||
here for the time. Some options would be:\n\n - `%H%M` which would render
|
||||
`0529`\n \n - `%-I%M%p` which woudl render `529AM`\n - `%-I%:M%p` which
|
||||
woudl render `5:29AM`\n\n\n\n For details see https://strftime.org/\n"
|
||||
selector:
|
||||
text:
|
||||
type: text
|
||||
multiline: false
|
||||
default: '%-I%M%p'
|
||||
message_duration_forecast:
|
||||
name: Forecast Duration ⏱️
|
||||
description: How long should the forecast message remain on the screen(in seconds). *If
|
||||
you select `0` it will use the Global App Time*
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 300.0
|
||||
unit_of_measurement: sec
|
||||
step: 1.0
|
||||
mode: slider
|
||||
default: 30
|
||||
message_duration_riseset:
|
||||
name: Sun Rise/Set Duration ⏱️
|
||||
description: How long should the sunrise sunset message remain on the screen(in
|
||||
seconds). *If you select `0` it will use the Global App Time*
|
||||
selector:
|
||||
number:
|
||||
min: 0.0
|
||||
max: 300.0
|
||||
unit_of_measurement: sec
|
||||
step: 1.0
|
||||
mode: slider
|
||||
default: 30
|
||||
icon_clear_night:
|
||||
name: Icon for clear-night
|
||||
description: "\nThe default clear_night icon is: \n\n 
|
||||
- `53383`\n"
|
||||
selector:
|
||||
text: {}
|
||||
default: w-clear-night
|
||||
icon_cloudy:
|
||||
name: Icon for cloudy
|
||||
description: 'This is the icon ID which maps to the weather state: `cloudy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-cloudy
|
||||
icon_exceptional:
|
||||
name: Icon for exceptional
|
||||
description: 'This is the icon ID which maps to the weather state: `exceptional`
|
||||
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-exceptional
|
||||
icon_fog:
|
||||
name: Icon for fog
|
||||
description: 'This is the icon ID which maps to the weather state: `fog`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-fog
|
||||
icon_hail:
|
||||
name: Icon for hail
|
||||
description: 'This is the icon ID which maps to the weather state: `hail` (IF
|
||||
YOU HAVE A BETTER ONE PLEASE LET ME KNOW)
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-hail
|
||||
icon_lightning:
|
||||
name: Icon for lightning
|
||||
description: 'This is the icon ID which maps to the weather state: `lightning`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-lightning
|
||||
icon_lightning_rainy:
|
||||
name: Icon for lightning-rainy
|
||||
description: 'This is the icon ID which maps to the weather state: `lightning-rainy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-lightning-rainy
|
||||
icon_partlycloudy:
|
||||
name: Icon for partlycloudy
|
||||
description: "This is the icon ID which maps to the weather state: `partlycloudy`\n
|
||||
\n\n"
|
||||
selector:
|
||||
text: {}
|
||||
default: w-partlycloudy
|
||||
icon_pouring:
|
||||
name: Icon for pouring
|
||||
description: 'This is the default icon which maps to the weather state: `pouring`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-pouring
|
||||
icon_rainy:
|
||||
name: Icon for rainy
|
||||
description: 'This is the default icon which maps to the weather state: `rainy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-rainy
|
||||
icon_snowy:
|
||||
name: Icon for snowy
|
||||
description: 'This is the icon ID which maps to the weather state: `snowy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-snowy
|
||||
icon_snowy_rainy:
|
||||
name: Icon for snowy-rainy
|
||||
description: 'This is the icon ID which maps to the weather state: `snowy-rainy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-snowy-rainy
|
||||
icon_sunny:
|
||||
name: Icon for sunny
|
||||
description: 'This is the icon ID which maps to the weather state: `sunny`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-sunny
|
||||
icon_windy:
|
||||
name: Icon for windy
|
||||
description: 'This is the icon ID which maps to the weather state: `windy`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-windy
|
||||
icon_windy_variant:
|
||||
name: Icon for windy-variant
|
||||
description: 'This is the icon ID which maps to the weather state: `windy-variant`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-windy-variant
|
||||
icon_sunrise:
|
||||
name: Icon for sunrise
|
||||
description: 'This is the icon ID which maps to the `sunrise`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-sunrise
|
||||
icon_sunset:
|
||||
name: Icon for sunset
|
||||
description: 'This is the icon ID which maps to the `sunset`
|
||||
|
||||

|
||||
|
||||
'
|
||||
selector:
|
||||
text: {}
|
||||
default: w-sunset
|
||||
source_url: https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/blueprints/automation/awtrix_weatherflow.yaml
|
||||
mode: restart
|
||||
variables:
|
||||
device_ids: !input awtrix
|
||||
app_topic: jeef_weather
|
||||
devices_topics: "{%- macro get_device_topic(device_id) %} {{ states((device_entities(device_id)
|
||||
| select('search','device_topic') | list)[0]) }} {%- endmacro %}\n{%- set ns =
|
||||
namespace(devices=[]) %} {%- for device_id in device_ids %}\n {%- set device=get_device_topic(device_id)|replace('
|
||||
','') %}\n {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ app_topic]
|
||||
%}\n{%- endfor %} {{ ns.devices | reject('match','unavailable') | list}}"
|
||||
forecast_var: !input forecast_var
|
||||
forecast: '{{state_attr(forecast_var,''forecast'')}}'
|
||||
weather: '{{states(forecast_var)}}'
|
||||
hours_to_show: !input hours_to_show
|
||||
moon: !input moon
|
||||
moon_phase: '{{states(moon)}}'
|
||||
moon_times: !input moon_rise_set
|
||||
moon_rise: '{{state_attr(moon_times,''moonrise'')}}'
|
||||
moon_set: '{{state_attr(moon_times,''moonset'')}}'
|
||||
moon_alt: '{{state_attr(moon_times,''moon_altitude'')}}'
|
||||
moon_risen: '{{moon_alt > 0}}'
|
||||
when_show_moon: !input when_show_moon
|
||||
show_moon: '{%- if when_show_moon == ''always'' %} True {%- elif when_show_moon
|
||||
== ''never'' %} False {%- elif when_show_moon == ''risen'' %} {{moon_risen}} {%-
|
||||
else %} {{state_attr(''sun.sun'', ''elevation'') < 0 and moon_risen}} {%- endif
|
||||
%}'
|
||||
message_duration: !input message_duration_forecast
|
||||
message_duration_riseset: !input message_duration_riseset
|
||||
current_temp_var: !input current_temp_var
|
||||
temp_digits: !input temp_digits
|
||||
temp_suffix: !input temp_suffix
|
||||
current_temp: '{{states(current_temp_var)}}'
|
||||
temp_text: "{%- macro round_and_set_temp(temp_var, temp_suffix, digits=0) -%} {%-
|
||||
if has_value(temp_var) -%}\n {{ states(temp_var) | round(digits) ~ temp_suffix}}
|
||||
\n{%- else -%} ?? {%- endif -%} {%- endmacro -%} {{ round_and_set_temp(current_temp_var,
|
||||
temp_suffix, temp_digits)}}"
|
||||
forecast_temp_field: !input forecast_temp_field
|
||||
text_available_width: '{%- if show_moon %}16{%- else %}24{%- endif %}
|
||||
|
||||
'
|
||||
text_len: "{%- macro get_text_len(string) %} {%- set length = namespace(value=0)
|
||||
%} {%- for char in string %}\n {%- if char.isdigit() %}\n {%- set length.value
|
||||
= length.value + 3 %}\n {%- elif char == '°' %}\n {%- set length.value = length.value
|
||||
+ 2 %}\n {%- elif char == '.' %}\n {%- set length.value = length.value + 1
|
||||
%}\n {%- elif char in ['-','C','F'] %}\n {%- set length.value = length.value
|
||||
+ 3 %}\n {%- else %}\n {%- set length.value = length.value + 1 %}\n {%- endif
|
||||
%}\n {%- if not loop.last %}\n {%- set length.value = length.value + 1 %}{%-
|
||||
endif -%}\n{%- endfor -%} {{ length.value }} {%- endmacro %}\n{{get_text_len(temp_text)}}"
|
||||
text_x: '{{8 + ((text_available_width - text_len)/2)}}'
|
||||
sun_event_minute_threshold: !input sun_event_minute_threshold
|
||||
sun_time_type: !input sun_time_type
|
||||
sun_time_format: !input sun_time_format
|
||||
icon_sunrise: !input icon_sunrise
|
||||
icon_sunset: !input icon_sunset
|
||||
show_sun_rise_set: !input show_sun_rise_set
|
||||
sun_next_event: '{%- set rise = state_attr(''sun.sun'',''next_rising'') %} {%- set
|
||||
set = state_attr(''sun.sun'',''next_setting'') %} {%- set ts_rise = rise |as_timestamp
|
||||
%} {%- set ts_set = set |as_timestamp %} {{ iif(ts_set < ts_rise,''sunset'',''sunrise'')
|
||||
}}'
|
||||
sun_min_until_next_event: '{%- set rise = state_attr(''sun.sun'',''next_rising'')
|
||||
%} {%- set set = state_attr(''sun.sun'',''next_setting'') %} {%- set ts_rise =
|
||||
rise |as_timestamp %} {%- set ts_set = set |as_timestamp %} {{ iif(sun_next_event
|
||||
== ''sunrise'',(ts_rise - utcnow()|as_timestamp) / 60,(ts_set - utcnow()|as_timestamp)
|
||||
/ 60) | round(0) }}'
|
||||
sun_next_str: "{%- set rise = state_attr('sun.sun','next_rising') %} {%- set set
|
||||
= state_attr('sun.sun','next_setting') %} {%- set ts_rise = rise |as_timestamp
|
||||
%} {%- set ts_set = set |as_timestamp %} {%- if sun_time_type == 'Actual' %}\n
|
||||
\ {{ iif(sun_next_event == 'sunrise',(ts_rise | as_datetime | as_local).strftime(sun_time_format),
|
||||
\ (ts_set | as_datetime | as_local).strftime(sun_time_format)) }}\n{%- else %}
|
||||
{#- relative time #}\n {% set hours = sun_min_until_next_event // 60 %}\n {%
|
||||
set remaining_minutes = sun_min_until_next_event % 60 %}\n\n {% if hours == 0
|
||||
%}\n {{ remaining_minutes }} min\n {% else %}\n [\n {\"t\":\"{{hours}}\",
|
||||
\"c\":\"#ffffff\"},\n {\"t\":\"h\", \"c\":\"#9c9d97\"},\n {\"t\":\"{{remaining_minutes}}\",
|
||||
\"c\":\"#ffffff\"},\n {\"t\":\"m\", \"c\":\"#9c9d97\"}\n ]\n {% endif
|
||||
%}\n \n{%- endif %}"
|
||||
sun_event_icon: '{{ iif(sun_next_event == ''sunrise'', icon_sunrise, icon_sunset)
|
||||
}}'
|
||||
sun_event_payload: '{"icon":"{{sun_event_icon}}", "text":"{{sun_next_str}}", "duration":
|
||||
{{message_duration_riseset}}}'
|
||||
sun_payload: '{%- if show_sun_rise_set %} {{ iif(sun_event_minute_threshold >= sun_min_until_next_event,
|
||||
sun_event_payload, "{}") }} {%- else %} {} {%- endif %}'
|
||||
icon_clear_night: !input icon_clear_night
|
||||
use_moon_clear_night: !input use_moon_clear_night
|
||||
use_moon_sunny_night: !input use_moon_sunny_night
|
||||
icon_cloudy: !input icon_cloudy
|
||||
icon_exceptional: !input icon_exceptional
|
||||
icon_fog: !input icon_fog
|
||||
icon_hail: !input icon_hail
|
||||
icon_lightning: !input icon_lightning
|
||||
icon_lightning_rainy: !input icon_lightning_rainy
|
||||
icon_partlycloudy: !input icon_partlycloudy
|
||||
icon_pouring: !input icon_pouring
|
||||
icon_rainy: !input icon_rainy
|
||||
icon_snowy: !input icon_snowy
|
||||
icon_snowy_rainy: !input icon_snowy_rainy
|
||||
icon_sunny: !input icon_sunny
|
||||
icon_windy: !input icon_windy
|
||||
icon_windy_variant: !input icon_windy_variant
|
||||
clear_night_dict: "{{ dict({\n 'full_moon': '2314',\n 'waning_gibbous': '2315',\n
|
||||
\ 'last_quarter': '2316',\n 'waning_crescent': '2317',\n 'new_moon': '2318',\n
|
||||
\ 'waxing_crescent': '2319',\n 'first_quarter': '2320',\n 'waxing_gibbous':
|
||||
'2321'}) }}"
|
||||
color_matrix_json: !input color_matrix_json
|
||||
color_dict: "{% set b = color_matrix_json | from_json %} {%- set ns = namespace(tuples=[])
|
||||
%} {%- for k,v in b | items -%}\n {%- set key = k|float -%}\n {%- set ns.tuples
|
||||
= ns.tuples + [(key,v)] %} \n{% endfor %} {{ dict.from_keys(ns.tuples) }}"
|
||||
icon_dict: '{{ dict({''clear-night'': icon_clear_night, ''cloudy'': icon_cloudy,
|
||||
''exceptional'': icon_exceptional, ''fog'': icon_fog, ''hail'': icon_hail, ''lightning'':
|
||||
icon_lightning, ''lightning-rainy'': icon_lightning_rainy, ''partlycloudy'': icon_partlycloudy,
|
||||
''pouring'': icon_pouring, ''rainy'': icon_rainy, ''snowy'': icon_snowy, ''snowy-rainy'':
|
||||
icon_snowy_rainy, ''sunny'': icon_sunny, ''windy'': icon_windy, ''windy-variant'':
|
||||
icon_windy_variant})}}'
|
||||
icon: "{%- if ((weather == 'clear_night') and use_moon_clear_night) %}\n {{clear_night_dict[moon_phase]}}\n{%-
|
||||
elif (sun_next_event == 'sunrise') and use_moon_sunny_night and (weather == 'sunny')
|
||||
-%}\n \n{%- else %}\n {{ icon_dict[weather] }}\n{%- endif %}\n"
|
||||
moon_data: "{%- macro draw_moon(phase,x=22,y=0) %}\n {%- if phase == 'first_quarter'
|
||||
\ %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,3355443,3355443,14079702,14079702,0,0,0,3355443,3355443,3355443,15790320,14079702,14079702,0,3355443,3355443,3355443,3355443,13355979,13355979,14079702,14079702,3355443,3355443,1644825,3355443,13355979,15790320,15790320,14079702,3355443,3355443,1644825,3355443,15790320,15790320,15790320,14079702,3355443,3355443,3355443,3355443,15790320,13355979,14079702,14079702,0,3355443,3355443,3355443,15790320,14079702,14079702,0,0,0,3355443,3355443,14079702,14079702,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'full_moon' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,14079702,14079702,14079702,14079702,0,0,0,14079702,14079702,15790320,15790320,14079702,14079702,0,14079702,14079702,15790320,15790320,11974326,11974326,14079702,14079702,14079702,15790320,11974326,15790320,11974326,15790320,15790320,14079702,14079702,15790320,11974326,15790320,15790320,15790320,15790320,14079702,14079702,14079702,15790320,15790320,15790320,11974326,14079702,14079702,0,14079702,14079702,11974326,15790320,14079702,14079702,0,0,0,14079702,14079702,14079702,14079702,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'last_quarter' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,14079702,14079702,3487029,3487029,0,0,0,14079702,14079702,15790320,3487029,3487029,3487029,0,14079702,14079702,15790320,15790320,1907997,1907997,3487029,3487029,14079702,15790320,13553358,15790320,1907997,3487029,3487029,3487029,14079702,15790320,13553358,15790320,3487029,3487029,3487029,3487029,14079702,14079702,15790320,15790320,3487029,1907997,3487029,3487029,0,14079702,14079702,13553358,3487029,3487029,3487029,0,0,0,14079702,14079702,3487029,3487029,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'new_moon' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,2763306,2763306,2763306,2763306,0,0,0,2763306,2763306,2763306,2763306,2763306,2763306,0,2763306,2763306,2763306,2763306,1842204,1842204,2763306,2763306,2763306,2763306,1842204,2763306,1842204,2763306,2763306,2763306,2763306,2763306,1842204,2763306,2763306,2763306,2763306,2763306,2763306,2763306,2763306,2763306,2763306,1842204,2763306,2763306,0,2763306,2763306,1842204,2763306,2763306,2763306,0,0,0,2763306,2763306,2763306,2763306,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'waning_crescent' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,14079702,14079702,2763306,2763306,0,0,0,14079702,14079702,2763306,2763306,2763306,2763306,0,14079702,14079702,2763306,2763306,1842204,1842204,2763306,2763306,14079702,15790320,1842204,2763306,1842204,2763306,2763306,2763306,14079702,15790320,1842204,2763306,2763306,2763306,2763306,2763306,14079702,14079702,2763306,2763306,2763306,1842204,2763306,2763306,0,14079702,14079702,1842204,2763306,2763306,2763306,0,0,0,14079702,14079702,2763306,2763306,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'waning_gibbous' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,14079702,14079702,3552822,3552822,0,0,0,14079702,14079702,15790320,15790320,3552822,3552822,0,14079702,14079702,15790320,15790320,13421772,13421772,3552822,3552822,14079702,15790320,13421772,15790320,13421772,15790320,3552822,3552822,14079702,15790320,13421772,15790320,15790320,15790320,3552822,3552822,14079702,14079702,15790320,15790320,15790320,13421772,3552822,3552822,0,14079702,14079702,13421772,15790320,3552822,3552822,0,0,0,14079702,14079702,3552822,3552822,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'waxing_crescent' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,3355443,3355443,14079702,14079702,0,0,0,3355443,3355443,3355443,3355443,14079702,14079702,0,3355443,3355443,3355443,3355443,1644825,1644825,14079702,14079702,3355443,3355443,1644825,3355443,1644825,3355443,15790320,14079702,3355443,3355443,1644825,3355443,3355443,3355443,15790320,14079702,3355443,3355443,3355443,3355443,3355443,1644825,14079702,14079702,0,3355443,3355443,3355443,3355443,14079702,14079702,0,0,0,3355443,3355443,14079702,14079702,0,0]]}\n
|
||||
\ {%- endif %}\n {%- if phase == 'waxing_gibbous' %}\n {\"db\":[{{x}},{{y}},8,8,[0,0,3355443,3355443,14079702,14079702,0,0,0,3355443,3355443,14079702,15790320,14079702,14079702,0,3355443,3355443,15790320,15790320,12763842,12763842,14079702,14079702,3355443,3355443,12763842,15790320,12763842,15790320,15790320,14079702,3355443,3355443,12763842,15790320,15790320,15790320,15790320,14079702,3355443,3355443,15790320,15790320,15790320,12763842,14079702,14079702,0,3355443,3355443,12763842,15790320,14079702,14079702,0,0,0,3355443,3355443,14079702,14079702,0,0]]}\n
|
||||
\ {%- endif %}\n{%- endmacro %}\n{%- if weather == 'clear-night' and use_moon_clear_night
|
||||
-%} {{draw_moon(moon_phase,0,0)}} {%- elif(sun_next_event == 'sunrise') and use_moon_sunny_night
|
||||
and (weather == 'sunny') -%} {{draw_moon(moon_phase,0,0)}} {%- else -%} {{draw_moon(moon_phase,23,0)}}
|
||||
{%- endif -%}\n"
|
||||
payload: "{%- macro interpolate(dictionary, x) -%}\n \n {%- set sorted_keys =
|
||||
dictionary|dictsort -%}\n {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first
|
||||
-%}\n {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last
|
||||
-%}\n\n {#- Key matches x exactly -#}\n {%- if above is defined and dictionary[above]
|
||||
== x -%}\n {%- set value = dictionary[above] -%}\n {{ value }}\n {%- elif
|
||||
below is defined and dictionary[below] == x -%}\n {%- set value = dictionary[below]
|
||||
-%}\n {{ value }}\n {#- Interpolation between two values -#}\n {%- elif below
|
||||
is defined and above is defined -%}\n {%- set lower_value = dictionary[below]
|
||||
-%}\n {%- set upper_value = dictionary[above] -%}\n {%- set lower_rgb =
|
||||
lower_value[1:] -%}\n {%- set upper_rgb = upper_value[1:] -%}\n\n {%- set
|
||||
lower_r = lower_rgb[0:2]|int(base=16) -%}\n {%- set lower_g = lower_rgb[2:4]|int(base=16)
|
||||
-%}\n {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}\n\n {%- set upper_r
|
||||
= upper_rgb[0:2]|int(base=16) -%}\n {%- set upper_g = upper_rgb[2:4]|int(base=16)
|
||||
-%}\n {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}\n\n {%- set interpolation_factor
|
||||
= (x - below) / (above - below) -%}\n {%- set interpolated_r = ((1 - interpolation_factor)
|
||||
* lower_r + interpolation_factor * upper_r)|int -%}\n {%- set interpolated_g
|
||||
= ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int
|
||||
-%}\n {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor
|
||||
* upper_b)|int -%}\n\n {%- set interpolated_hex = '#' ~ '%02X' % interpolated_r
|
||||
~ '%02X' % interpolated_g ~ '%02X' % interpolated_b -%}\n {{ interpolated_hex
|
||||
}}\n {#- Only below key available -#}\n {%- elif below is defined -%}\n {%-
|
||||
set value = dictionary[below] -%}\n {{ value }}\n {#- Only above key available
|
||||
-#}\n {%- elif above is defined -%}\n {%- set value = dictionary[above] -%}\n
|
||||
\ {{ value }}\n {#- No matching keys available -#}\n {%- else -%}\n No
|
||||
matching key found.\n {%- endif -%}\n{%- endmacro -%}\n{#- Define macro to get
|
||||
length of the forecast} {%- macro str_len(str) %} {%- if '.' in str %} {%- set
|
||||
char_count = (str | length) -1 %}{{char_count * 3 + 1 + char_count}} {%- else
|
||||
%} {%- set char_count = (str | length) %}{{char_count * 3 + (char_count - 1)}}
|
||||
{%- endif %} {%- endmacro %}\n{#- Define a macro to draw out the forecast lines#}
|
||||
{%- macro draw_forecast_lines(x,hours,height) %}\n {%- for hour in range(hours)
|
||||
%}\n {%- if height == 0 %}\n {\"dp\": [{{x+hour}},7,\"{{interpolate(color_dict,
|
||||
forecast[hour][forecast_temp_field]) }}\"]}\n {%- else %}\n {\"dl\": [{{x+hour}},7,{{x+hour}},{{7
|
||||
- height}},\"{{interpolate(color_dict, forecast[hour][forecast_temp_field]) }}\"]}\n
|
||||
\ {%- endif %}\n {%- if hour+1 != hours %},{%endif%}\n {%- endfor %}\n{%-
|
||||
endmacro %}\n\n{# Define the color mapping dictionary #} { \"draw\": [\n {%-
|
||||
if hours_to_show > 0 %}\n {{draw_forecast_lines(8,hours_to_show,0)}}\n {%- endif
|
||||
%}\n {%- if current_temp != 'unavailable' -%}\n ,{\"dt\":[{{text_x}},1,\"{{temp_text}}\",\"{{interpolate(color_dict,
|
||||
current_temp | float)}}\"]}\n {%- else -%}\n {\"dt\":\"err\"}\n {%- endif -%}\n
|
||||
\n {% if show_moon %}\n ,{{moon_data}}\n {% endif %}\n], \"icon\": \"{{icon}}\",
|
||||
\"duration\": {{message_duration}}, \"pushIcon\": 2, \"lifetime\": 120, \"lifetimeMode\":1,
|
||||
\"weather\": \"{{weather}}\" }\n"
|
||||
trigger:
|
||||
- platform: time_pattern
|
||||
seconds: /5
|
||||
- platform: state
|
||||
entity_id: !input forecast_var
|
||||
id: Changes
|
||||
enabled: true
|
||||
condition: []
|
||||
action:
|
||||
- repeat:
|
||||
for_each: '{{ devices_topics }}'
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item }}'
|
||||
payload: '{{payload}}
|
||||
|
||||
'
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: '{{ repeat.item ~ ''_sun''}} '
|
||||
payload: '{{sun_payload}}
|
||||
|
||||
'
|
@@ -0,0 +1,179 @@
|
||||
blueprint:
|
||||
source_url: https://github.com/niro1987/homeassistant-config/blob/main/blueprints/automation/niro1987/zha_ikea_tradfri_styrbar_color.yaml
|
||||
name: ZHA - IKEA TRADFRI - STYRBAR - Color Lights
|
||||
description: This automation simulates the use of the IKEA TRADFRI STYRBAR remote
|
||||
control connected through ZHA.
|
||||
domain: automation
|
||||
input:
|
||||
remote:
|
||||
name: IKEA TRADFRI remote control
|
||||
description: Select the remote control you wish to use.
|
||||
selector:
|
||||
device:
|
||||
filter:
|
||||
- integration: zha
|
||||
manufacturer: IKEA of Sweden
|
||||
model: Remote Control N2
|
||||
multiple: false
|
||||
light:
|
||||
name: Light
|
||||
description: Select the light entity you wish to control.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- domain:
|
||||
- light
|
||||
multiple: false
|
||||
speed:
|
||||
name: Speed
|
||||
description: The speed in which to update the light when the button is held.
|
||||
selector:
|
||||
number:
|
||||
min: 100.0
|
||||
max: 1000.0
|
||||
step: 100.0
|
||||
unit_of_measurement: milliseconds
|
||||
mode: slider
|
||||
default: 500
|
||||
mode: restart
|
||||
max_exceeded: silent
|
||||
variables:
|
||||
var_light: !input light
|
||||
var_speed: !input speed
|
||||
trigger:
|
||||
- platform: event
|
||||
event_type: zha_event
|
||||
event_data:
|
||||
device_id: !input remote
|
||||
action:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "on" }}'
|
||||
- condition: state
|
||||
entity_id: !input light
|
||||
state: 'off'
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
brightness: 254
|
||||
hs_color:
|
||||
- 38.222
|
||||
- 52.941
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "move_with_on_off" }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: []
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
brightness_step_pct: 10
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- delay:
|
||||
milliseconds: !input speed
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "off" }}'
|
||||
sequence:
|
||||
- service: light.turn_off
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "move" }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: []
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
brightness_step_pct: -10
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- delay:
|
||||
milliseconds: !input speed
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "press" }}'
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.args == [257,13,0] }}'
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
hs_color:
|
||||
- '{{ state_attr(var_light, "hs_color")[0] }}'
|
||||
- "{% if state_attr(var_light, \"hs_color\")[1] - 20 < 0 %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[1] - 20 + 100 }}\n{% else %}\n {{ state_attr(var_light, \"hs_color\")[1]
|
||||
- 20 }}\n{% endif %}"
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "hold" }}'
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.args == [3329,0] }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: []
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
hs_color:
|
||||
- '{{ state_attr(var_light, "hs_color")[0] }}'
|
||||
- "{% if state_attr(var_light, \"hs_color\")[1] - 10 < 0 %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[1] - 10 + 100 }}\n{% else %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[1] - 10 }}\n{% endif %}"
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- delay:
|
||||
milliseconds: !input speed
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "press" }}'
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.args == [256,13,0] }}'
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
hs_color:
|
||||
- "{% if state_attr(var_light, \"hs_color\")[0] + 18 > 360 %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[0] + 18 - 360 }}\n{% else %}\n {{ state_attr(var_light, \"hs_color\")[0]
|
||||
+ 18 }}\n{% endif %}"
|
||||
- '{{ state_attr(var_light, "hs_color")[1] }}'
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.command == "hold" }}'
|
||||
- condition: template
|
||||
value_template: '{{ trigger.event.data.args == [3328,0] }}'
|
||||
sequence:
|
||||
- repeat:
|
||||
while: []
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: !input light
|
||||
data:
|
||||
hs_color:
|
||||
- "{% if state_attr(var_light, \"hs_color\")[0] + 18 > 360 %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[0] + 18 - 360 }}\n{% else %}\n {{ state_attr(var_light,
|
||||
\"hs_color\")[0] + 18 }}\n{% endif %}"
|
||||
- '{{ state_attr(var_light, "hs_color")[1] }}'
|
||||
transition: '{{ (var_speed / 1000)|float }}'
|
||||
- delay:
|
||||
milliseconds: !input speed
|
||||
default: []
|
@@ -0,0 +1,170 @@
|
||||
blueprint:
|
||||
name: Awtrix current playing song
|
||||
description: Shows the title and artist of your current playing song on Awtrix.
|
||||
domain: automation
|
||||
author: N1c093
|
||||
input:
|
||||
awtrix_light:
|
||||
name: Awtrix Display
|
||||
description: Select the target Awtrix display.
|
||||
selector:
|
||||
device:
|
||||
model: "AWTRIX Light"
|
||||
media_player:
|
||||
name: Media Player Entity
|
||||
description: Select your Media Player.
|
||||
selector:
|
||||
entity:
|
||||
filter:
|
||||
- domain: media_player
|
||||
multiple: false
|
||||
icon_in:
|
||||
name: Icon
|
||||
description: Enter the Icon Name or ID of the icon.
|
||||
selector:
|
||||
text:
|
||||
default: ""
|
||||
push_icon:
|
||||
name: Push Icon
|
||||
description: Icon behavior
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: "Icon doesn't move"
|
||||
value: "0"
|
||||
- label: "Icon moves with text and will not appear again"
|
||||
value: "1"
|
||||
- label: "Icon moves with text but appears again when the text starts"
|
||||
value: "2"
|
||||
mode: dropdown
|
||||
default: "2"
|
||||
repeat_text:
|
||||
name: Repeat
|
||||
description: Select how how often the text should be repeated.
|
||||
default: "2"
|
||||
selector:
|
||||
text:
|
||||
text_case:
|
||||
name: Text Case
|
||||
description: Select how you would like your text to display.
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: "Use global setting"
|
||||
value: "0"
|
||||
- label: "Force Uppercase"
|
||||
value: "1"
|
||||
- label: "Show as the media player reports it"
|
||||
value: "2"
|
||||
mode: dropdown
|
||||
default: "0"
|
||||
display_type:
|
||||
name: Custom App/Notification
|
||||
description: Select if you want the information as a single notification or an custom app.
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- label: "Notification"
|
||||
value: "0"
|
||||
- label: "Custom App"
|
||||
value: "1"
|
||||
mode: dropdown
|
||||
default: "1"
|
||||
background_color:
|
||||
name: Background Color
|
||||
description: Select the Background color
|
||||
selector:
|
||||
color_rgb:
|
||||
default: [0, 0, 0]
|
||||
text_color:
|
||||
name: Text Color
|
||||
description: Select the Text color.
|
||||
selector:
|
||||
color_rgb:
|
||||
default: [255, 255, 255]
|
||||
show_rainbow:
|
||||
name: Rainbow Colors
|
||||
description: Should the notification be shown in Rainbow colors?
|
||||
selector:
|
||||
boolean:
|
||||
default: false
|
||||
|
||||
|
||||
mode: queued
|
||||
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: !input media_player
|
||||
attribute: media_title
|
||||
from:
|
||||
- platform: state
|
||||
entity_id: !input media_player
|
||||
from:
|
||||
|
||||
variables:
|
||||
device_id: !input awtrix_light
|
||||
awtrix_light: "{{ iif( device_attr(device_id, 'name_by_user') != none, device_attr(device_id, 'name_by_user'), device_attr(device_id, 'name') ) }}"
|
||||
repeat_text: !input repeat_text
|
||||
show_rainbow: !input show_rainbow
|
||||
push_icon: !input push_icon
|
||||
icon_in: !input icon_in
|
||||
background_color: !input background_color
|
||||
text_color: !input text_color
|
||||
text_case: !input text_case
|
||||
media_player: !input media_player
|
||||
display_type: !input display_type
|
||||
all_text: "{{state_attr(media_player, 'media_title')}} - {{state_attr(media_player, 'media_artist')}}"
|
||||
|
||||
|
||||
|
||||
action:
|
||||
if:
|
||||
- condition: state
|
||||
entity_id: !input media_player
|
||||
state: playing
|
||||
then:
|
||||
if: "{{ display_type == '1' }}"
|
||||
then:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{awtrix_light}}/custom/mediaplayer"
|
||||
payload: |-
|
||||
{
|
||||
"text": "{{ all_text }}",
|
||||
"icon": "{{ icon_in }}",
|
||||
"background": {{ background_color }},
|
||||
"color": {{ text_color }},
|
||||
"textCase": {{ text_case }},
|
||||
"pushIcon": {{ push_icon }},
|
||||
"rainbow": {{ iif(show_rainbow, "true", "false") }},
|
||||
"repeat": {{ repeat_text }}
|
||||
}
|
||||
else:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{awtrix_light}}/notify"
|
||||
payload: |-
|
||||
{
|
||||
"text": "{{ all_text }}",
|
||||
"icon": "{{ icon_in }}",
|
||||
"background": {{ background_color }},
|
||||
"color": {{ text_color }},
|
||||
"textCase": {{ text_case }},
|
||||
"pushIcon": {{ push_icon }},
|
||||
"rainbow": {{ iif(show_rainbow, "true", "false") }},
|
||||
"repeat": {{ repeat_text }}
|
||||
}
|
||||
else:
|
||||
- if: "{{ display_type == '1' }}"
|
||||
then:
|
||||
- service: mqtt.publish
|
||||
data:
|
||||
qos: 0
|
||||
retain: false
|
||||
topic: "{{awtrix_light}}/custom/mediaplayer"
|
||||
payload: |-
|
||||
{}
|
@@ -0,0 +1,27 @@
|
||||
blueprint:
|
||||
name: Invert a binary sensor
|
||||
description: Creates a binary_sensor which holds the inverted value of a reference binary_sensor
|
||||
domain: template
|
||||
source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml
|
||||
input:
|
||||
reference_entity:
|
||||
name: Binary sensor to be inverted
|
||||
description: The binary_sensor which needs to have its value inverted
|
||||
selector:
|
||||
entity:
|
||||
domain: binary_sensor
|
||||
variables:
|
||||
reference_entity: !input reference_entity
|
||||
binary_sensor:
|
||||
state: >
|
||||
{% if states(reference_entity) == 'on' %}
|
||||
off
|
||||
{% elif states(reference_entity) == 'off' %}
|
||||
on
|
||||
{% else %}
|
||||
{{ states(reference_entity) }}
|
||||
{% endif %}
|
||||
# delay_on: not_used in this example
|
||||
# delay_off: not_used in this example
|
||||
# auto_off: not_used in this example
|
||||
availability: "{{ states(reference_entity) not in ('unknown', 'unavailable') }}"
|
@@ -11,17 +11,13 @@ http:
|
||||
tts:
|
||||
- platform: picotts_remote
|
||||
language: "de-DE"
|
||||
- platform: google_translate
|
||||
language: "de"
|
||||
- platform: marytts
|
||||
language: "de"
|
||||
|
||||
# Include modules
|
||||
group: !include groups.yaml
|
||||
automation: !include automations.yaml
|
||||
automation webhooks: !include automations_webhooks.yaml
|
||||
script: !include scripts.yaml
|
||||
scene: !include scenes.yaml
|
||||
notify: !include notify.yaml
|
||||
|
||||
# Enable additional integrations
|
||||
# Enable 'wake_on_lan' intrgration
|
||||
@@ -36,38 +32,11 @@ sensor: !include sensors.yaml
|
||||
utility_meter: !include utility_meters.yaml
|
||||
|
||||
# MQTT sensors
|
||||
mqtt: !include mqtt.yaml
|
||||
mqtt: !include mqtt.yaml
|
||||
mqtt_statestream: !include mqtt_statestream.yaml
|
||||
|
||||
# Template sensors
|
||||
template:
|
||||
- sensor:
|
||||
- name: "Line Power Total"
|
||||
unit_of_measurement: "W"
|
||||
icon: mdi:flash
|
||||
unique_id: power.total
|
||||
state: >
|
||||
{% set phase_a = states('sensor.line_power_channel_a_power') | float %}
|
||||
{% set phase_b = states('sensor.line_power_channel_b_power') | float %}
|
||||
{% set phase_c = states('sensor.line_power_channel_c_power') | float %}
|
||||
{{ (phase_a + phase_b + phase_c) | round(1) }}
|
||||
- sensor:
|
||||
- name: "Leckstrom"
|
||||
unit_of_measurement: "W"
|
||||
icon: mdi:flash
|
||||
state: >
|
||||
{% set total = states('sensor.line_power_total') | float %}
|
||||
{% set raumduft = states('sensor.flur_raumduft_power') | float %}
|
||||
{% set keller = states('sensor.keller_power') | float %}
|
||||
{% set anrichte = states('sensor.kuche_anrichte_power') | float %}
|
||||
{% set musik = states('sensor.kuche_musik_power') | float %}
|
||||
{% set bett = states('sensor.schlafzimmer_bett_power') | float %}
|
||||
{% set heimkino_sz = states('sensor.schlafzimmer_heimkino_power') | float %}
|
||||
{% set deko = states('sensor.schreibtisch_deko_power') | float %}
|
||||
{% set schreibtisch = states('sensor.schreibtisch_power') | float %}
|
||||
{% set serverraum = states('sensor.serverraum_power') | float %}
|
||||
{% set heimkino_wz = states('sensor.wohnzimmer_heimkino_power') | float %}
|
||||
{% set spieleschrank = states('sensor.wohnzimmer_spieleschrank_power') | float %}
|
||||
{{ (total - raumduft - keller - musik - bett - heimkino_sz - deko - schreibtisch - serverraum - heimkino_wz - spieleschrank) | round(1) }}
|
||||
template: !include template.yaml
|
||||
|
||||
# calendar integration
|
||||
calendar: !include calendars.yaml
|
||||
@@ -75,34 +44,19 @@ calendar: !include calendars.yaml
|
||||
# DB-recorder configuration
|
||||
recorder: !include recorder.yaml
|
||||
|
||||
# Home Assistant InfluxDB integration
|
||||
influxdb:
|
||||
host: a0d7b954-influxdb
|
||||
port: 8086
|
||||
database: homeassistant
|
||||
username: homeassistant
|
||||
password: !secret influx_pass
|
||||
max_retries: 3
|
||||
default_measurement: state
|
||||
|
||||
# Enable Bluetooth
|
||||
bluetooth:
|
||||
|
||||
# Bluetooth Low Energy tracker
|
||||
device_tracker:
|
||||
- platform: bluetooth_le_tracker
|
||||
track_new_devices: true
|
||||
|
||||
# Configure MPD addon as media player
|
||||
media_player:
|
||||
platform: mpd
|
||||
host: 192.168.122.48
|
||||
scan_interval: 1
|
||||
track_new_devices: false
|
||||
|
||||
generic_hygrostat:
|
||||
- name: Badezimmer
|
||||
unique_id: '3728344225387'
|
||||
humidifier: fan.badezimmer_ventilator
|
||||
target_sensor: sensor.badezimmer_luftfeuchtigkeit
|
||||
target_sensor: sensor.bathroom_badezimmer_luftfeuchtigkeit
|
||||
min_humidity: 30
|
||||
max_humidity: 70
|
||||
target_humidity: 50
|
||||
@@ -117,4 +71,3 @@ generic_hygrostat:
|
||||
away_humidity: 60
|
||||
away_fixed: true
|
||||
sensor_stale_duration: 00:15:00
|
||||
|
||||
|
76
custom_components/hochwasserportal/__init__.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""The Länderübergreifendes Hochwasser Portal integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from lhpapi import HochwasserPortalAPI, LHPError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_ADD_UNAVAILABLE,
|
||||
CONF_PEGEL_IDENTIFIER,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .coordinator import HochwasserPortalCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up a config entry."""
|
||||
pegel_identifier: str = entry.data[CONF_PEGEL_IDENTIFIER]
|
||||
|
||||
# Initialize the API and coordinator.
|
||||
try:
|
||||
api = await hass.async_add_executor_job(HochwasserPortalAPI, pegel_identifier)
|
||||
coordinator = HochwasserPortalCoordinator(hass, api)
|
||||
except LHPError as err:
|
||||
LOGGER.exception("Setup of %s failed: %s", pegel_identifier, err)
|
||||
return False
|
||||
|
||||
# No need to refresh via the following line because api runs
|
||||
# update during init automatically
|
||||
# await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
LOGGER.debug(
|
||||
"Migrating %s from version %s.%s",
|
||||
config_entry.title,
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
if config_entry.version > 1:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
if config_entry.version == 1:
|
||||
new = {**config_entry.data}
|
||||
if config_entry.minor_version < 2:
|
||||
new[CONF_ADD_UNAVAILABLE] = True # Behaviour as in 1.1
|
||||
config_entry.minor_version = 2
|
||||
hass.config_entries.async_update_entry(config_entry, data=new)
|
||||
|
||||
LOGGER.debug(
|
||||
"Migration of %s to version %s.%s successful",
|
||||
config_entry.title,
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
85
custom_components/hochwasserportal/config_flow.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Config flow for the hochwasserportal integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from lhpapi import HochwasserPortalAPI, LHPError, get_all_stations
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.selector import (
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_ADD_UNAVAILABLE, CONF_PEGEL_IDENTIFIER, DOMAIN, LOGGER
|
||||
|
||||
|
||||
class HochwasserPortalConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the config flow for the hochwasserportal integration."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict = {}
|
||||
|
||||
if user_input is not None:
|
||||
pegel_identifier = user_input[CONF_PEGEL_IDENTIFIER]
|
||||
|
||||
# Validate pegel identifier using the API
|
||||
try:
|
||||
api = await self.hass.async_add_executor_job(
|
||||
HochwasserPortalAPI, pegel_identifier
|
||||
)
|
||||
LOGGER.debug(
|
||||
"%s (%s): Successfully added!",
|
||||
api.ident,
|
||||
api.name,
|
||||
)
|
||||
except LHPError as err:
|
||||
LOGGER.exception("Setup of %s failed: %s", pegel_identifier, err)
|
||||
errors["base"] = "invalid_identifier"
|
||||
|
||||
if not errors:
|
||||
# Set the unique ID for this config entry.
|
||||
await self.async_set_unique_id(f"{DOMAIN}_{pegel_identifier.lower()}")
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(title=f"{api.name}", data=user_input)
|
||||
|
||||
stations_dict = await self.hass.async_add_executor_job(get_all_stations)
|
||||
LOGGER.debug(
|
||||
"%i stations found on Github",
|
||||
len(stations_dict),
|
||||
)
|
||||
stations = [SelectOptionDict(value="---", label="")]
|
||||
stations.extend(
|
||||
SelectOptionDict(value=k, label=f"{v} ({k})")
|
||||
for k, v in stations_dict.items()
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
errors=errors,
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PEGEL_IDENTIFIER): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=stations,
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
sort=True,
|
||||
custom_value=True,
|
||||
)
|
||||
),
|
||||
vol.Required(CONF_ADD_UNAVAILABLE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
)
|
46
custom_components/hochwasserportal/const.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Constants for the Länderübergreifendes Hochwasser Portal integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.const import Platform
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
DOMAIN: Final = "hochwasserportal"
|
||||
|
||||
CONF_PEGEL_IDENTIFIER: Final = "pegel_identifier"
|
||||
CONF_ADD_UNAVAILABLE: Final = "add_unavailable"
|
||||
|
||||
ATTR_DATA_PROVIDERS: Final[dict[str, str]] = {
|
||||
"BB": "LfU Brandenburg",
|
||||
"BE": "SenMVKU Berlin",
|
||||
"BW": "LUBW Baden-Württemberg",
|
||||
"BY": "LfU Bayern",
|
||||
"HB": "SUKW Bremen",
|
||||
"HE": "HLNUG",
|
||||
"HH": "LSBG Hamburg",
|
||||
"MV": "LUNG Mecklenburg-Vorpommern",
|
||||
"NI": "NLWKN",
|
||||
"NW": "LANUV Nordrhein-Westfalen",
|
||||
"RP": "Luf Rheinland-Pfalz",
|
||||
"SH": "Luf Schleswig-Holstein",
|
||||
"SL": "LUA Saarland",
|
||||
"SN": "LfULG Sachsen",
|
||||
"ST": "Land Sachsen-Anhalt",
|
||||
"TH": "TLUBN",
|
||||
}
|
||||
ATTR_LAST_UPDATE: Final = "last_update"
|
||||
ATTR_URL: Final = "url"
|
||||
ATTR_HINT: Final = "hint"
|
||||
|
||||
LEVEL_SENSOR: Final = "level"
|
||||
STAGE_SENSOR: Final = "stage"
|
||||
FLOW_SENSOR: Final = "flow"
|
||||
|
||||
DEFAULT_SCAN_INTERVAL: Final = timedelta(minutes=15)
|
||||
|
||||
PLATFORMS: Final[list[Platform]] = [Platform.SENSOR]
|
32
custom_components/hochwasserportal/coordinator.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Data coordinator for the hochwasserportal integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from lhpapi import HochwasserPortalAPI, LHPError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER
|
||||
|
||||
|
||||
class HochwasserPortalCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Custom coordinator for the hochwasserportal integration."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: HochwasserPortalAPI) -> None:
|
||||
"""Initialize the hochwasserportal coordinator."""
|
||||
super().__init__(
|
||||
hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL
|
||||
)
|
||||
|
||||
self.api = api
|
||||
LOGGER.debug("%s", repr(self.api))
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Get the latest data from the hochwasserportal API."""
|
||||
try:
|
||||
await self.hass.async_add_executor_job(self.api.update)
|
||||
LOGGER.debug("%s", repr(self.api))
|
||||
except LHPError as err:
|
||||
LOGGER.exception("Update of %s failed: %s", self.api.ident, err)
|
||||
return False
|
14
custom_components/hochwasserportal/manifest.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"domain": "hochwasserportal",
|
||||
"name": "Länderübergreifendes Hochwasser Portal",
|
||||
"codeowners": ["@stephan192"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://github.com/stephan192/hochwasserportal",
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/stephan192/hochwasserportal/issues",
|
||||
"loggers": ["hochwasserportal"],
|
||||
"requirements": ["lhpapi==1.0.5"],
|
||||
"version": "1.0.3"
|
||||
}
|
148
custom_components/hochwasserportal/sensor.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""Platform for sensor integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from lhpapi import HochwasserPortalAPI
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfLength
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
ATTR_DATA_PROVIDERS,
|
||||
ATTR_HINT,
|
||||
ATTR_LAST_UPDATE,
|
||||
ATTR_URL,
|
||||
CONF_ADD_UNAVAILABLE,
|
||||
DOMAIN,
|
||||
FLOW_SENSOR,
|
||||
LEVEL_SENSOR,
|
||||
LOGGER,
|
||||
STAGE_SENSOR,
|
||||
)
|
||||
from .coordinator import HochwasserPortalCoordinator
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class HochwasserPortalSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes HochwasserPortal sensor entity."""
|
||||
|
||||
value_fn: Callable[[HochwasserPortalAPI], int | float | None]
|
||||
available_fn: Callable[[HochwasserPortalAPI], bool]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[HochwasserPortalSensorEntityDescription, ...] = (
|
||||
HochwasserPortalSensorEntityDescription(
|
||||
key=LEVEL_SENSOR,
|
||||
translation_key=LEVEL_SENSOR,
|
||||
icon="mdi:waves",
|
||||
native_unit_of_measurement=UnitOfLength.CENTIMETERS,
|
||||
device_class=None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda api: api.level,
|
||||
available_fn=lambda api: api.level is not None,
|
||||
),
|
||||
HochwasserPortalSensorEntityDescription(
|
||||
key=STAGE_SENSOR,
|
||||
translation_key=STAGE_SENSOR,
|
||||
icon="mdi:waves-arrow-up",
|
||||
native_unit_of_measurement=None,
|
||||
device_class=None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda api: api.stage,
|
||||
available_fn=lambda api: api.stage is not None,
|
||||
),
|
||||
HochwasserPortalSensorEntityDescription(
|
||||
key=FLOW_SENSOR,
|
||||
translation_key=FLOW_SENSOR,
|
||||
icon="mdi:waves-arrow-right",
|
||||
native_unit_of_measurement="m³/s",
|
||||
device_class=None,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda api: api.flow,
|
||||
available_fn=lambda api: api.flow is not None,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up entities from config entry."""
|
||||
coordinator: HochwasserPortalCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
HochwasserPortalSensor(coordinator, entry, description)
|
||||
for description in SENSOR_TYPES
|
||||
if description.available_fn(coordinator.api)
|
||||
or entry.data.get(CONF_ADD_UNAVAILABLE, False)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class HochwasserPortalSensor(
|
||||
CoordinatorEntity[HochwasserPortalCoordinator], SensorEntity
|
||||
):
|
||||
"""Sensor representation."""
|
||||
|
||||
entity_description: HochwasserPortalSensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: HochwasserPortalCoordinator,
|
||||
entry: ConfigEntry,
|
||||
description: HochwasserPortalSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.api = coordinator.api
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, entry.entry_id)},
|
||||
name=f"{entry.title}",
|
||||
configuration_url=self.api.url,
|
||||
manufacturer=f"{ATTR_DATA_PROVIDERS[self.api.ident[:2]]}",
|
||||
model=f"{self.api.ident}",
|
||||
)
|
||||
self._attr_attribution = (
|
||||
f"Data provided by {ATTR_DATA_PROVIDERS[self.api.ident[:2]]}"
|
||||
)
|
||||
LOGGER.debug("Setting up sensor: %s", self._attr_unique_id)
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.api)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
data = {}
|
||||
if self.api.last_update is not None:
|
||||
data[ATTR_LAST_UPDATE] = self.api.last_update
|
||||
if self.api.url is not None:
|
||||
data[ATTR_URL] = self.api.url
|
||||
if self.api.hint is not None:
|
||||
data[ATTR_HINT] = self.api.hint
|
||||
if bool(data):
|
||||
return data
|
||||
return None
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Could the device be accessed during the last update call."""
|
||||
return self.entity_description.available_fn(self.api)
|
33
custom_components/hochwasserportal/string.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "To identify the pegel, the pegel ID is required.",
|
||||
"data": {
|
||||
"pegel_identifier": "Pegel ID",
|
||||
"add_unavailable": "Add unavailable entities anyway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_identifier": "The specified pegel identifier is invalid."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Pegel is already configured.",
|
||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"level": {
|
||||
"name": "Level"
|
||||
},
|
||||
"stage": {
|
||||
"name": "Stage"
|
||||
},
|
||||
"flow": {
|
||||
"name": "Flow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
custom_components/hochwasserportal/translations/de.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Um den Pegel zu identifizieren, ist die Pegel-ID erforderlich.",
|
||||
"data": {
|
||||
"pegel_identifier": "Pegel ID",
|
||||
"add_unavailable": "Nicht verfügbare Entitäten trotzdem hinzufügen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_identifier": "Der angegebene Pegel ist ungültig."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Pegel bereits konfiguriert.",
|
||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"level": {
|
||||
"name": "Pegelstand"
|
||||
},
|
||||
"stage": {
|
||||
"name": "Warnstufe"
|
||||
},
|
||||
"flow": {
|
||||
"name": "Abfluss"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
custom_components/hochwasserportal/translations/en.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "To identify the pegel, the pegel ID is required.",
|
||||
"data": {
|
||||
"pegel_identifier": "Pegel ID",
|
||||
"add_unavailable": "Add unavailable entities anyway"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_identifier": "The specified pegel identifier is invalid."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "Pegel is already configured.",
|
||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"level": {
|
||||
"name": "Level"
|
||||
},
|
||||
"stage": {
|
||||
"name": "Stage"
|
||||
},
|
||||
"flow": {
|
||||
"name": "Flow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
260
custom_components/ics_calendar/__init__.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""ics Calendar for Home Assistant."""
|
||||
|
||||
import logging
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_EXCLUDE,
|
||||
CONF_INCLUDE,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PREFIX,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_ACCEPT_HEADER,
|
||||
CONF_ADV_CONNECT_OPTS,
|
||||
CONF_CALENDARS,
|
||||
CONF_CONNECTION_TIMEOUT,
|
||||
CONF_DAYS,
|
||||
CONF_DOWNLOAD_INTERVAL,
|
||||
CONF_INCLUDE_ALL_DAY,
|
||||
CONF_OFFSET_HOURS,
|
||||
CONF_PARSER,
|
||||
CONF_REQUIRES_AUTH,
|
||||
CONF_SET_TIMEOUT,
|
||||
CONF_SUMMARY_DEFAULT,
|
||||
CONF_SUMMARY_DEFAULT_DEFAULT,
|
||||
CONF_USER_AGENT,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS: list[Platform] = [Platform.CALENDAR]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
# pylint: disable=no-value-for-parameter
|
||||
vol.Optional(CONF_CALENDARS, default=[]): vol.All(
|
||||
cv.ensure_list,
|
||||
vol.Schema(
|
||||
[
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): vol.Url(),
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(
|
||||
CONF_INCLUDE_ALL_DAY, default=False
|
||||
): cv.boolean,
|
||||
vol.Optional(
|
||||
CONF_USERNAME, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_PASSWORD, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_PARSER, default="rie"
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_PREFIX, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_DAYS, default=1
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_DOWNLOAD_INTERVAL, default=15
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_USER_AGENT, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_EXCLUDE, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_INCLUDE, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_OFFSET_HOURS, default=0
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_ACCEPT_HEADER, default=""
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_CONNECTION_TIMEOUT, default=300
|
||||
): cv.positive_float,
|
||||
vol.Optional(
|
||||
CONF_SUMMARY_DEFAULT,
|
||||
default=CONF_SUMMARY_DEFAULT_DEFAULT,
|
||||
): cv.string,
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
STORAGE_KEY = DOMAIN
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 0
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up calendars."""
|
||||
_LOGGER.debug("Setting up ics_calendar component")
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
if DOMAIN in config and config[DOMAIN]:
|
||||
_LOGGER.debug("discovery.load_platform called")
|
||||
discovery.load_platform(
|
||||
hass=hass,
|
||||
component=PLATFORMS[0],
|
||||
platform=DOMAIN,
|
||||
discovered=config[DOMAIN],
|
||||
hass_config=config,
|
||||
)
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_configuration",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="YAML_Warning",
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"YAML configuration of ics_calendar is deprecated and will be "
|
||||
"removed in ics_calendar v5.0.0. Your configuration items have "
|
||||
"been imported. Please remove them from your configuration.yaml "
|
||||
"file."
|
||||
)
|
||||
|
||||
config_entry = _async_find_matching_config_entry(hass)
|
||||
if not config_entry:
|
||||
if config[DOMAIN].get("calendars"):
|
||||
for calendar in config[DOMAIN].get("calendars"):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=dict(calendar),
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
# update entry with any changes
|
||||
if config[DOMAIN].get("calendars"):
|
||||
for calendar in config[DOMAIN].get("calendars"):
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, data=dict(calendar)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def _async_find_matching_config_entry(hass):
|
||||
for entry in hass.config_entries.async_entries(DOMAIN):
|
||||
if entry.source == SOURCE_IMPORT:
|
||||
return entry
|
||||
return None
|
||||
|
||||
|
||||
async def async_migrate_entry(hass, entry: ConfigEntry):
|
||||
"""Migrate old config entry."""
|
||||
# Don't downgrade entries
|
||||
if entry.version > STORAGE_VERSION_MAJOR:
|
||||
return False
|
||||
|
||||
if entry.version == STORAGE_VERSION_MAJOR:
|
||||
new_data = {**entry.data}
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data=new_data,
|
||||
minor_version=STORAGE_VERSION_MINOR,
|
||||
version=STORAGE_VERSION_MAJOR,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Implement async_setup_entry."""
|
||||
full_data: dict = add_missing_defaults(entry)
|
||||
hass.config_entries.async_update_entry(entry=entry, data=full_data)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = full_data
|
||||
await hass.config_entries.async_forward_entry_setups(entry, ["calendar"])
|
||||
return True
|
||||
|
||||
|
||||
def add_missing_defaults(
|
||||
entry: ConfigEntry,
|
||||
) -> dict:
|
||||
"""Initialize missing data."""
|
||||
data = {
|
||||
CONF_NAME: "",
|
||||
CONF_URL: "",
|
||||
CONF_ADV_CONNECT_OPTS: False,
|
||||
CONF_SET_TIMEOUT: False,
|
||||
CONF_REQUIRES_AUTH: False,
|
||||
CONF_INCLUDE_ALL_DAY: False,
|
||||
CONF_REQUIRES_AUTH: False,
|
||||
CONF_USERNAME: "",
|
||||
CONF_PASSWORD: "",
|
||||
CONF_PARSER: "rie",
|
||||
CONF_PREFIX: "",
|
||||
CONF_DAYS: 1,
|
||||
CONF_DOWNLOAD_INTERVAL: 15,
|
||||
CONF_USER_AGENT: "",
|
||||
CONF_EXCLUDE: "",
|
||||
CONF_INCLUDE: "",
|
||||
CONF_OFFSET_HOURS: 0,
|
||||
CONF_ACCEPT_HEADER: "",
|
||||
CONF_CONNECTION_TIMEOUT: 300.0,
|
||||
CONF_SUMMARY_DEFAULT: CONF_SUMMARY_DEFAULT_DEFAULT,
|
||||
}
|
||||
data.update(entry.data)
|
||||
|
||||
if CONF_USERNAME in entry.data or CONF_PASSWORD in entry.data:
|
||||
data[CONF_REQUIRES_AUTH] = True
|
||||
if (
|
||||
CONF_USER_AGENT in entry.data
|
||||
or CONF_ACCEPT_HEADER in entry.data
|
||||
or CONF_CONNECTION_TIMEOUT in entry.data
|
||||
):
|
||||
data[CONF_ADV_CONNECT_OPTS] = True
|
||||
if CONF_CONNECTION_TIMEOUT in entry.data:
|
||||
data[CONF_SET_TIMEOUT] = True
|
||||
|
||||
return data
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
entry, PLATFORMS
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
381
custom_components/ics_calendar/calendar.py
Normal file
@@ -0,0 +1,381 @@
|
||||
"""Support for ICS Calendar."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Optional
|
||||
|
||||
# import homeassistant.helpers.config_validation as cv
|
||||
# import voluptuous as vol
|
||||
from homeassistant.components.calendar import (
|
||||
ENTITY_ID_FORMAT,
|
||||
CalendarEntity,
|
||||
CalendarEvent,
|
||||
extract_offset,
|
||||
is_offset_reached,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_EXCLUDE,
|
||||
CONF_INCLUDE,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PREFIX,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util.dt import now as hanow
|
||||
|
||||
from .calendardata import CalendarData
|
||||
from .const import (
|
||||
CONF_ACCEPT_HEADER,
|
||||
CONF_CALENDARS,
|
||||
CONF_CONNECTION_TIMEOUT,
|
||||
CONF_DAYS,
|
||||
CONF_DOWNLOAD_INTERVAL,
|
||||
CONF_INCLUDE_ALL_DAY,
|
||||
CONF_OFFSET_HOURS,
|
||||
CONF_PARSER,
|
||||
CONF_SET_TIMEOUT,
|
||||
CONF_SUMMARY_DEFAULT,
|
||||
CONF_USER_AGENT,
|
||||
DOMAIN,
|
||||
)
|
||||
from .filter import Filter
|
||||
from .getparser import GetParser
|
||||
from .parserevent import ParserEvent
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
OFFSET = "!!"
|
||||
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the calendar in background."""
|
||||
hass.async_create_task(
|
||||
_async_setup_entry_bg_task(hass, config_entry, async_add_entities)
|
||||
)
|
||||
|
||||
|
||||
async def _async_setup_entry_bg_task(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the calendar."""
|
||||
data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
device_id = f"{data[CONF_NAME]}"
|
||||
entity = ICSCalendarEntity(
|
||||
hass,
|
||||
generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass),
|
||||
hass.data[DOMAIN][config_entry.entry_id],
|
||||
config_entry.entry_id,
|
||||
)
|
||||
async_add_entities([entity])
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
):
|
||||
"""Set up ics_calendar platform.
|
||||
|
||||
:param hass: Home Assistant object
|
||||
:type hass: HomeAssistant
|
||||
:param config: Config information for the platform
|
||||
:type config: ConfigType
|
||||
:param add_entities: Callback to add entities to HA
|
||||
:type add_entities: AddEntitiesCallback
|
||||
:param discovery_info: Config information for the platform
|
||||
:type discovery_info: DiscoveryInfoType | None, optional
|
||||
"""
|
||||
_LOGGER.debug("Setting up ics calendars")
|
||||
if discovery_info is not None:
|
||||
_LOGGER.debug(
|
||||
"setup_platform: ignoring discovery_info, already imported!"
|
||||
)
|
||||
# calendars: list = discovery_info.get(CONF_CALENDARS)
|
||||
calendars = []
|
||||
else:
|
||||
_LOGGER.debug("setup_platform: discovery_info is None")
|
||||
calendars: list = config.get(CONF_CALENDARS)
|
||||
|
||||
calendar_devices = []
|
||||
for calendar in calendars:
|
||||
device_data = {
|
||||
CONF_NAME: calendar.get(CONF_NAME),
|
||||
CONF_URL: calendar.get(CONF_URL),
|
||||
CONF_INCLUDE_ALL_DAY: calendar.get(CONF_INCLUDE_ALL_DAY),
|
||||
CONF_USERNAME: calendar.get(CONF_USERNAME),
|
||||
CONF_PASSWORD: calendar.get(CONF_PASSWORD),
|
||||
CONF_PARSER: calendar.get(CONF_PARSER),
|
||||
CONF_PREFIX: calendar.get(CONF_PREFIX),
|
||||
CONF_DAYS: calendar.get(CONF_DAYS),
|
||||
CONF_DOWNLOAD_INTERVAL: calendar.get(CONF_DOWNLOAD_INTERVAL),
|
||||
CONF_USER_AGENT: calendar.get(CONF_USER_AGENT),
|
||||
CONF_EXCLUDE: calendar.get(CONF_EXCLUDE),
|
||||
CONF_INCLUDE: calendar.get(CONF_INCLUDE),
|
||||
CONF_OFFSET_HOURS: calendar.get(CONF_OFFSET_HOURS),
|
||||
CONF_ACCEPT_HEADER: calendar.get(CONF_ACCEPT_HEADER),
|
||||
CONF_CONNECTION_TIMEOUT: calendar.get(CONF_CONNECTION_TIMEOUT),
|
||||
}
|
||||
device_id = f"{device_data[CONF_NAME]}"
|
||||
entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
|
||||
calendar_devices.append(
|
||||
ICSCalendarEntity(hass, entity_id, device_data)
|
||||
)
|
||||
|
||||
add_entities(calendar_devices)
|
||||
|
||||
|
||||
class ICSCalendarEntity(CalendarEntity):
|
||||
"""A CalendarEntity for an ICS Calendar."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
device_data,
|
||||
unique_id: str = None,
|
||||
):
|
||||
"""Construct ICSCalendarEntity.
|
||||
|
||||
:param entity_id: Entity id for the calendar
|
||||
:type entity_id: str
|
||||
:param device_data: dict describing the calendar
|
||||
:type device_data: dict
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
"Initializing calendar: %s with URL: %s, uniqueid: %s",
|
||||
device_data[CONF_NAME],
|
||||
device_data[CONF_URL],
|
||||
unique_id,
|
||||
)
|
||||
self.data = ICSCalendarData(hass, device_data)
|
||||
self.entity_id = entity_id
|
||||
self._attr_unique_id = f"ICSCalendar.{unique_id}"
|
||||
self._event = None
|
||||
self._attr_name = device_data[CONF_NAME]
|
||||
self._last_call = None
|
||||
|
||||
@property
|
||||
def event(self) -> Optional[CalendarEvent]:
|
||||
"""Return the current or next upcoming event or None.
|
||||
|
||||
:return: The current event as a dict
|
||||
:rtype: dict
|
||||
"""
|
||||
return self._event
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Indicate if the calendar should be polled.
|
||||
|
||||
If the last call to update or get_api_events was not within the minimum
|
||||
update time, then async_schedule_update_ha_state(True) is also called.
|
||||
:return: True
|
||||
:rtype: boolean
|
||||
"""
|
||||
this_call = hanow()
|
||||
if (
|
||||
self._last_call is None
|
||||
or (this_call - self._last_call) > MIN_TIME_BETWEEN_UPDATES
|
||||
):
|
||||
self._last_call = this_call
|
||||
self.async_schedule_update_ha_state(True)
|
||||
return True
|
||||
|
||||
async def async_get_events(
|
||||
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all events in a specific time frame.
|
||||
|
||||
:param hass: Home Assistant object
|
||||
:type hass: HomeAssistant
|
||||
:param start_date: The first starting date to consider
|
||||
:type start_date: datetime
|
||||
:param end_date: The last starting date to consider
|
||||
:type end_date: datetime
|
||||
"""
|
||||
_LOGGER.debug(
|
||||
"%s: async_get_events called; calling internal.", self.name
|
||||
)
|
||||
return await self.data.async_get_events(start_date, end_date)
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the current or next event."""
|
||||
await self.data.async_update()
|
||||
self._event: CalendarEvent | None = self.data.event
|
||||
self._attr_extra_state_attributes = {
|
||||
"offset_reached": (
|
||||
is_offset_reached(
|
||||
self._event.start_datetime_local, self.data.offset
|
||||
)
|
||||
if self._event
|
||||
else False
|
||||
)
|
||||
}
|
||||
|
||||
async def async_create_event(self, **kwargs: Any):
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_delete_event(
|
||||
self,
|
||||
uid: str,
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
async def async_update_event(
|
||||
self,
|
||||
uid: str,
|
||||
event: dict[str, Any],
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise error, this is a read-only calendar."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class ICSCalendarData: # pylint: disable=R0902
|
||||
"""Class to use the calendar ICS client object to get next event."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, device_data):
|
||||
"""Set up how we are going to connect to the URL.
|
||||
|
||||
:param device_data Information about the calendar
|
||||
"""
|
||||
self.name = device_data[CONF_NAME]
|
||||
self._days = device_data[CONF_DAYS]
|
||||
self._offset_hours = device_data[CONF_OFFSET_HOURS]
|
||||
self.include_all_day = device_data[CONF_INCLUDE_ALL_DAY]
|
||||
self._summary_prefix: str = device_data[CONF_PREFIX]
|
||||
self._summary_default: str = device_data[CONF_SUMMARY_DEFAULT]
|
||||
self.parser = GetParser.get_parser(device_data[CONF_PARSER])
|
||||
self.parser.set_filter(
|
||||
Filter(device_data[CONF_EXCLUDE], device_data[CONF_INCLUDE])
|
||||
)
|
||||
self.offset = None
|
||||
self.event = None
|
||||
self._hass = hass
|
||||
|
||||
self._calendar_data = CalendarData(
|
||||
get_async_client(hass),
|
||||
_LOGGER,
|
||||
{
|
||||
"name": self.name,
|
||||
"url": device_data[CONF_URL],
|
||||
"min_update_time": timedelta(
|
||||
minutes=device_data[CONF_DOWNLOAD_INTERVAL]
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
self._calendar_data.set_headers(
|
||||
device_data[CONF_USERNAME],
|
||||
device_data[CONF_PASSWORD],
|
||||
device_data[CONF_USER_AGENT],
|
||||
device_data[CONF_ACCEPT_HEADER],
|
||||
)
|
||||
|
||||
if device_data.get(CONF_SET_TIMEOUT):
|
||||
self._calendar_data.set_timeout(
|
||||
device_data[CONF_CONNECTION_TIMEOUT]
|
||||
)
|
||||
|
||||
async def async_get_events(
|
||||
self, start_date: datetime, end_date: datetime
|
||||
) -> list[CalendarEvent]:
|
||||
"""Get all events in a specific time frame.
|
||||
|
||||
:param start_date: The first starting date to consider
|
||||
:type start_date: datetime
|
||||
:param end_date: The last starting date to consider
|
||||
:type end_date: datetime
|
||||
"""
|
||||
event_list: list[ParserEvent] = []
|
||||
if await self._calendar_data.download_calendar():
|
||||
_LOGGER.debug("%s: Setting calendar content", self.name)
|
||||
self.parser.set_content(self._calendar_data.get())
|
||||
try:
|
||||
event_list = self.parser.get_event_list(
|
||||
start=start_date,
|
||||
end=end_date,
|
||||
include_all_day=self.include_all_day,
|
||||
offset_hours=self._offset_hours,
|
||||
)
|
||||
except: # pylint: disable=W0702
|
||||
_LOGGER.error(
|
||||
"async_get_events: %s: Failed to parse ICS!",
|
||||
self.name,
|
||||
exc_info=True,
|
||||
)
|
||||
event_list: list[ParserEvent] = []
|
||||
|
||||
for event in event_list:
|
||||
event.summary = self._summary_prefix + event.summary
|
||||
if not event.summary:
|
||||
event.summary = self._summary_default
|
||||
# Since we skipped the validation code earlier, invoke it now,
|
||||
# before passing the object outside this component
|
||||
event.validate()
|
||||
|
||||
return event_list
|
||||
|
||||
async def async_update(self):
|
||||
"""Get the current or next event."""
|
||||
_LOGGER.debug("%s: Update was called", self.name)
|
||||
parser_event: ParserEvent | None = None
|
||||
if await self._calendar_data.download_calendar():
|
||||
_LOGGER.debug("%s: Setting calendar content", self.name)
|
||||
self.parser.set_content(self._calendar_data.get())
|
||||
try:
|
||||
parser_event: ParserEvent | None = self.parser.get_current_event(
|
||||
include_all_day=self.include_all_day,
|
||||
now=hanow(),
|
||||
days=self._days,
|
||||
offset_hours=self._offset_hours,
|
||||
)
|
||||
except: # pylint: disable=W0702
|
||||
_LOGGER.error(
|
||||
"update: %s: Failed to parse ICS!", self.name, exc_info=True
|
||||
)
|
||||
if parser_event is not None:
|
||||
_LOGGER.debug(
|
||||
"%s: got event: %s; start: %s; end: %s; all_day: %s",
|
||||
self.name,
|
||||
parser_event.summary,
|
||||
parser_event.start,
|
||||
parser_event.end,
|
||||
parser_event.all_day,
|
||||
)
|
||||
(summary, offset) = extract_offset(parser_event.summary, OFFSET)
|
||||
parser_event.summary = self._summary_prefix + summary
|
||||
if not parser_event.summary:
|
||||
parser_event.summary = self._summary_default
|
||||
self.offset = offset
|
||||
# Invoke validation here, since it was skipped when creating the
|
||||
# ParserEvent
|
||||
parser_event.validate()
|
||||
self.event: CalendarEvent = parser_event
|
||||
return True
|
||||
|
||||
_LOGGER.debug("%s: No event found!", self.name)
|
||||
return False
|
224
custom_components/ics_calendar/calendardata.py
Normal file
@@ -0,0 +1,224 @@
|
||||
"""Provide CalendarData class."""
|
||||
|
||||
import re
|
||||
from logging import Logger
|
||||
from math import floor
|
||||
|
||||
import httpx
|
||||
import httpx_auth
|
||||
from homeassistant.util.dt import now as hanow
|
||||
|
||||
# from urllib.error import ContentTooShortError, HTTPError, URLError
|
||||
|
||||
|
||||
class DigestWithMultiAuth(httpx.DigestAuth, httpx_auth.SupportMultiAuth):
|
||||
"""Describes a DigestAuth authentication."""
|
||||
|
||||
def __init__(self, username: str, password: str):
|
||||
"""Construct Digest authentication that supports Multi Auth."""
|
||||
httpx.DigestAuth.__init__(self, username, password)
|
||||
|
||||
|
||||
class CalendarData: # pylint: disable=R0902
|
||||
"""CalendarData class.
|
||||
|
||||
The CalendarData class is used to download and cache calendar data from a
|
||||
given URL. Use the get method to retrieve the data after constructing your
|
||||
instance.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
async_client: httpx.AsyncClient,
|
||||
logger: Logger,
|
||||
conf: dict,
|
||||
):
|
||||
"""Construct CalendarData object.
|
||||
|
||||
:param async_client: An httpx.AsyncClient object for requests
|
||||
:type httpx.AsyncClient
|
||||
:param logger: The logger for reporting problems
|
||||
:type logger: Logger
|
||||
:param conf: Configuration options
|
||||
:type conf: dict
|
||||
"""
|
||||
self._auth = None
|
||||
self._calendar_data = None
|
||||
self._headers = []
|
||||
self._last_download = None
|
||||
self._min_update_time = conf["min_update_time"]
|
||||
self.logger = logger
|
||||
self.name = conf["name"]
|
||||
self.url = conf["url"]
|
||||
self.connection_timeout = None
|
||||
self._httpx = async_client
|
||||
|
||||
async def download_calendar(self) -> bool:
|
||||
"""Download the calendar data.
|
||||
|
||||
This only downloads data if self.min_update_time has passed since the
|
||||
last download.
|
||||
|
||||
returns: True if data was downloaded, otherwise False.
|
||||
rtype: bool
|
||||
"""
|
||||
self.logger.debug("%s: download_calendar start", self.name)
|
||||
if (
|
||||
self._calendar_data is None
|
||||
or self._last_download is None
|
||||
or (hanow() - self._last_download) > self._min_update_time
|
||||
):
|
||||
self._calendar_data = None
|
||||
next_url: str = self._make_url()
|
||||
self.logger.debug(
|
||||
"%s: Downloading calendar data from: %s",
|
||||
self.name,
|
||||
next_url,
|
||||
)
|
||||
await self._download_data(next_url)
|
||||
self._last_download = hanow()
|
||||
self.logger.debug("%s: download_calendar done", self.name)
|
||||
return self._calendar_data is not None
|
||||
|
||||
self.logger.debug("%s: download_calendar skipped download", self.name)
|
||||
return False
|
||||
|
||||
def get(self) -> str:
|
||||
"""Get the calendar data that was downloaded.
|
||||
|
||||
:return: The downloaded calendar data.
|
||||
:rtype: str
|
||||
"""
|
||||
return self._calendar_data
|
||||
|
||||
def set_headers(
|
||||
self,
|
||||
user_name: str,
|
||||
password: str,
|
||||
user_agent: str,
|
||||
accept_header: str,
|
||||
):
|
||||
"""Set a user agent, accept header, and/or user name and password.
|
||||
|
||||
The user name and password will be set into an auth object that
|
||||
supports both Basic Auth and Digest Auth for httpx.
|
||||
|
||||
If the user_agent parameter is not "", a User-agent header will be
|
||||
added to the urlopener.
|
||||
|
||||
:param user_name: The user name
|
||||
:type user_name: str
|
||||
:param password: The password
|
||||
:type password: str
|
||||
:param user_agent: The User Agent string to use or ""
|
||||
:type user_agent: str
|
||||
:param accept_header: The accept header string to use or ""
|
||||
:type accept_header: str
|
||||
"""
|
||||
if user_name != "" and password != "":
|
||||
self._auth = httpx_auth.Basic(
|
||||
user_name, password
|
||||
) + DigestWithMultiAuth(user_name, password)
|
||||
|
||||
if user_agent != "":
|
||||
self._headers.append(("User-agent", user_agent))
|
||||
if accept_header != "":
|
||||
self._headers.append(("Accept", accept_header))
|
||||
|
||||
def set_timeout(self, connection_timeout: float):
|
||||
"""Set the connection timeout.
|
||||
|
||||
:param connection_timeout: The timeout value in seconds.
|
||||
:type connection_timeout: float
|
||||
"""
|
||||
self.connection_timeout = connection_timeout
|
||||
|
||||
def _decode_data(self, data):
|
||||
return data.replace("\0", "")
|
||||
|
||||
async def _download_data(self, url): # noqa: C901
|
||||
"""Download the calendar data."""
|
||||
self.logger.debug("%s: _download_data start", self.name)
|
||||
try:
|
||||
response = await self._httpx.get(
|
||||
url,
|
||||
auth=self._auth,
|
||||
headers=self._headers,
|
||||
follow_redirects=True,
|
||||
timeout=self.connection_timeout,
|
||||
)
|
||||
if response.status_code >= 400:
|
||||
raise httpx.HTTPStatusError(
|
||||
"status error", request=None, response=response
|
||||
)
|
||||
self._calendar_data = self._decode_data(response.text)
|
||||
self.logger.debug("%s: _download_data done", self.name)
|
||||
except httpx.HTTPStatusError as http_status_error:
|
||||
self.logger.error(
|
||||
"%s: Failed to open url(%s): %s",
|
||||
self.name,
|
||||
self.url,
|
||||
http_status_error.response.status_code,
|
||||
)
|
||||
except httpx.TimeoutException:
|
||||
self.logger.error(
|
||||
"%s: Timeout opening url: %s", self.name, self.url
|
||||
)
|
||||
except httpx.DecodingError:
|
||||
self.logger.error(
|
||||
"%s: Error decoding data from url: %s", self.name, self.url
|
||||
)
|
||||
except httpx.InvalidURL:
|
||||
self.logger.error("%s: Invalid URL: %s", self.name, self.url)
|
||||
except httpx.HTTPError:
|
||||
self.logger.error(
|
||||
"%s: Error decoding data from url: %s", self.name, self.url
|
||||
)
|
||||
except: # pylint: disable=W0702
|
||||
self.logger.error(
|
||||
"%s: Failed to open url!", self.name, exc_info=True
|
||||
)
|
||||
|
||||
def _make_url(self):
|
||||
"""Replace templates in url and encode."""
|
||||
now = hanow()
|
||||
year: int = now.year
|
||||
month: int = now.month
|
||||
url = self.url
|
||||
(month, year, url) = self._get_month_year(url, month, year)
|
||||
return url.replace("{year}", f"{year:04}").replace(
|
||||
"{month}", f"{month:02}"
|
||||
)
|
||||
|
||||
def _get_year_as_months(self, url: str, month: int) -> int:
|
||||
year_match = re.search("\\{year([-+])([0-9]+)\\}", url)
|
||||
if year_match:
|
||||
if year_match.group(1) == "-":
|
||||
month = month - (int(year_match.group(2)) * 12)
|
||||
else:
|
||||
month = month + (int(year_match.group(2)) * 12)
|
||||
url = url.replace(year_match.group(0), "{year}")
|
||||
return (month, url)
|
||||
|
||||
def _get_month_year(self, url: str, month: int, year: int) -> int:
|
||||
(month, url) = self._get_year_as_months(url, month)
|
||||
print(f"month: {month}\n")
|
||||
month_match = re.search("\\{month([-+])([0-9]+)\\}", url)
|
||||
if month_match:
|
||||
if month_match.group(1) == "-":
|
||||
month = month - int(month_match.group(2))
|
||||
else:
|
||||
month = month + int(month_match.group(2))
|
||||
if month < 1:
|
||||
year -= floor(abs(month) / 12) + 1
|
||||
month = month % 12
|
||||
if month == 0:
|
||||
month = 12
|
||||
elif month > 12:
|
||||
year += abs(floor(month / 12))
|
||||
month = month % 12
|
||||
if month == 0:
|
||||
month = 12
|
||||
year -= 1
|
||||
url = url.replace(month_match.group(0), "{month}")
|
||||
return (month, year, url)
|
330
custom_components/ics_calendar/config_flow.py
Normal file
@@ -0,0 +1,330 @@
|
||||
"""Config Flow for ICS Calendar."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any, Dict, Optional, Self
|
||||
from urllib.parse import quote
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import (
|
||||
CONF_EXCLUDE,
|
||||
CONF_INCLUDE,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PREFIX,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.helpers.selector import selector
|
||||
|
||||
from . import (
|
||||
CONF_ACCEPT_HEADER,
|
||||
CONF_ADV_CONNECT_OPTS,
|
||||
CONF_CONNECTION_TIMEOUT,
|
||||
CONF_DAYS,
|
||||
CONF_DOWNLOAD_INTERVAL,
|
||||
CONF_INCLUDE_ALL_DAY,
|
||||
CONF_OFFSET_HOURS,
|
||||
CONF_PARSER,
|
||||
CONF_REQUIRES_AUTH,
|
||||
CONF_SET_TIMEOUT,
|
||||
CONF_SUMMARY_DEFAULT,
|
||||
CONF_USER_AGENT,
|
||||
)
|
||||
from .const import CONF_SUMMARY_DEFAULT_DEFAULT, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CALENDAR_NAME_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_DAYS, default=1): cv.positive_int,
|
||||
vol.Optional(CONF_INCLUDE_ALL_DAY, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
CALENDAR_OPTS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_EXCLUDE, default=""): cv.string,
|
||||
vol.Optional(CONF_INCLUDE, default=""): cv.string,
|
||||
vol.Optional(CONF_PREFIX, default=""): cv.string,
|
||||
vol.Optional(CONF_DOWNLOAD_INTERVAL, default=15): cv.positive_int,
|
||||
vol.Optional(CONF_OFFSET_HOURS, default=0): int,
|
||||
vol.Optional(CONF_PARSER, default="rie"): selector(
|
||||
{"select": {"options": ["rie", "ics"], "mode": "dropdown"}}
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_SUMMARY_DEFAULT, default=CONF_SUMMARY_DEFAULT_DEFAULT
|
||||
): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
CONNECT_OPTS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): cv.string,
|
||||
vol.Optional(CONF_REQUIRES_AUTH, default=False): cv.boolean,
|
||||
vol.Optional(CONF_ADV_CONNECT_OPTS, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
AUTH_OPTS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_USERNAME, default=""): cv.string,
|
||||
vol.Optional(CONF_PASSWORD, default=""): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
ADVANCED_CONNECT_OPTS_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ACCEPT_HEADER, default=""): cv.string,
|
||||
vol.Optional(CONF_USER_AGENT, default=""): cv.string,
|
||||
vol.Optional(CONF_SET_TIMEOUT, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
TIMEOUT_OPTS_SCHEMA = vol.Schema(
|
||||
{vol.Optional(CONF_CONNECTION_TIMEOUT, default=None): cv.positive_float}
|
||||
)
|
||||
|
||||
|
||||
def is_array_string(arr_str: str) -> bool:
|
||||
"""Return true if arr_str starts with [ and ends with ]."""
|
||||
return arr_str.startswith("[") and arr_str.endswith("]")
|
||||
|
||||
|
||||
def format_url(url: str) -> str:
|
||||
"""Format a URL using quote() and ensure any templates are not quoted."""
|
||||
is_quoted = bool(re.search("%[0-9A-Fa-f][0-9A-Fa-f]", url))
|
||||
if not is_quoted:
|
||||
year_match = re.search("\\{(year([-+][0-9]+)?)\\}", url)
|
||||
month_match = re.search("\\{(month([-+][0-9]+)?)\\}", url)
|
||||
has_template: bool = year_match or month_match
|
||||
url = quote(url, safe=":/?&=")
|
||||
if has_template:
|
||||
year_template = year_match.group(1)
|
||||
month_template = month_match.group(1)
|
||||
year_template1 = year_template.replace("+", "%2[Bb]")
|
||||
month_template1 = month_template.replace("+", "%2[Bb]")
|
||||
url = re.sub(
|
||||
f"%7[Bb]{year_template1}%7[Dd]",
|
||||
f"{{{year_template}}}",
|
||||
url,
|
||||
)
|
||||
url = re.sub(
|
||||
f"%7[Bb]{month_template1}%7[Dd]",
|
||||
f"{{{month_template}}}",
|
||||
url,
|
||||
)
|
||||
if url.startswith("webcal://"):
|
||||
url = re.sub("^webcal://", "https://", url)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
class ICSCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Config Flow for ICS Calendar."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 0
|
||||
|
||||
data: Optional[Dict[str, Any]]
|
||||
|
||||
def __init__(self):
|
||||
"""Construct ICSCalendarConfigFlow."""
|
||||
self.data = {}
|
||||
|
||||
def is_matching(self, _other_flow: Self) -> bool:
|
||||
"""Match discovery method.
|
||||
|
||||
This method doesn't do anything, because this integration has no
|
||||
discoverable components.
|
||||
"""
|
||||
return False
|
||||
|
||||
async def async_step_reauth(self, user_input=None):
|
||||
"""Re-authenticateon auth error."""
|
||||
# self.reauth_entry = self.hass.config_entries.async_get_entry(
|
||||
# self.context["entry_id"]
|
||||
# )
|
||||
return await self.async_step_reauth_confirm(user_input)
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input=None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog to inform user that reauthentication is required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm", data_schema=vol.Schema({})
|
||||
)
|
||||
return await self.async_step_user()
|
||||
|
||||
# Don't allow reconfigure for now!
|
||||
# async def async_step_reconfigure(
|
||||
# self, user_input: dict[str, Any] | None = None
|
||||
# ) -> ConfigFlowResult:
|
||||
# """Reconfigure entry."""
|
||||
# return await self.async_step_user(user_input)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Start of Config Flow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
user_input[CONF_NAME] = user_input[CONF_NAME].strip()
|
||||
if not user_input[CONF_NAME]:
|
||||
errors[CONF_NAME] = "empty_name"
|
||||
else:
|
||||
self._async_abort_entries_match(
|
||||
{CONF_NAME: user_input[CONF_NAME]}
|
||||
)
|
||||
|
||||
if not errors:
|
||||
self.data = user_input
|
||||
return await self.async_step_calendar_opts()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=CALENDAR_NAME_SCHEMA,
|
||||
errors=errors,
|
||||
last_step=False,
|
||||
)
|
||||
|
||||
async def async_step_calendar_opts( # noqa: R701,C901
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Calendar Options step for ConfigFlow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
user_input[CONF_EXCLUDE] = user_input[CONF_EXCLUDE].strip()
|
||||
user_input[CONF_INCLUDE] = user_input[CONF_INCLUDE].strip()
|
||||
if (
|
||||
user_input[CONF_EXCLUDE]
|
||||
and user_input[CONF_EXCLUDE] == user_input[CONF_INCLUDE]
|
||||
):
|
||||
errors[CONF_EXCLUDE] = "exclude_include_cannot_be_the_same"
|
||||
else:
|
||||
if user_input[CONF_EXCLUDE] and not is_array_string(
|
||||
user_input[CONF_EXCLUDE]
|
||||
):
|
||||
errors[CONF_EXCLUDE] = "exclude_must_be_array"
|
||||
if user_input[CONF_INCLUDE] and not is_array_string(
|
||||
user_input[CONF_INCLUDE]
|
||||
):
|
||||
errors[CONF_INCLUDE] = "include_must_be_array"
|
||||
|
||||
if user_input[CONF_DOWNLOAD_INTERVAL] < 15:
|
||||
_LOGGER.error("download_interval_too_small error")
|
||||
errors[CONF_DOWNLOAD_INTERVAL] = "download_interval_too_small"
|
||||
|
||||
if not user_input[CONF_SUMMARY_DEFAULT]:
|
||||
user_input[CONF_SUMMARY_DEFAULT] = CONF_SUMMARY_DEFAULT_DEFAULT
|
||||
|
||||
if not errors:
|
||||
self.data.update(user_input)
|
||||
return await self.async_step_connect_opts()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="calendar_opts",
|
||||
data_schema=CALENDAR_OPTS_SCHEMA,
|
||||
errors=errors,
|
||||
last_step=False,
|
||||
)
|
||||
|
||||
async def async_step_connect_opts(
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Connect Options step for ConfigFlow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
user_input[CONF_URL] = user_input[CONF_URL].strip()
|
||||
if not user_input[CONF_URL]:
|
||||
errors[CONF_URL] = "empty_url"
|
||||
|
||||
if not errors:
|
||||
user_input[CONF_URL] = format_url(user_input[CONF_URL])
|
||||
|
||||
self.data.update(user_input)
|
||||
if user_input.get(CONF_REQUIRES_AUTH, False):
|
||||
return await self.async_step_auth_opts()
|
||||
if user_input.get(CONF_ADV_CONNECT_OPTS, False):
|
||||
return await self.async_step_adv_connect_opts()
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_NAME],
|
||||
data=self.data,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="connect_opts",
|
||||
data_schema=CONNECT_OPTS_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_auth_opts(
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Auth Options step for ConfigFlow."""
|
||||
if user_input is not None:
|
||||
self.data.update(user_input)
|
||||
if self.data.get(CONF_ADV_CONNECT_OPTS, False):
|
||||
return await self.async_step_adv_connect_opts()
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_NAME],
|
||||
data=self.data,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="auth_opts", data_schema=AUTH_OPTS_SCHEMA
|
||||
)
|
||||
|
||||
async def async_step_adv_connect_opts(
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Advanced Connection Options step for ConfigFlow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
|
||||
if not errors:
|
||||
self.data.update(user_input)
|
||||
if user_input.get(CONF_SET_TIMEOUT, False):
|
||||
return await self.async_step_timeout_opts()
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_NAME],
|
||||
data=self.data,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="adv_connect_opts",
|
||||
data_schema=ADVANCED_CONNECT_OPTS_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_timeout_opts(
|
||||
self, user_input: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
"""Timeout Options step for ConfigFlow."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
|
||||
if not errors:
|
||||
self.data.update(user_input)
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_NAME],
|
||||
data=self.data,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="timeout_opts",
|
||||
data_schema=TIMEOUT_OPTS_SCHEMA,
|
||||
errors=errors,
|
||||
last_step=True,
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_data):
|
||||
"""Import config from configuration.yaml."""
|
||||
return self.async_create_entry(
|
||||
title=import_data[CONF_NAME],
|
||||
data=import_data,
|
||||
)
|
24
custom_components/ics_calendar/const.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Constants for ics_calendar platform."""
|
||||
|
||||
VERSION = "5.1.3"
|
||||
DOMAIN = "ics_calendar"
|
||||
|
||||
CONF_DEVICE_ID = "device_id"
|
||||
CONF_CALENDARS = "calendars"
|
||||
CONF_DAYS = "days"
|
||||
CONF_INCLUDE_ALL_DAY = "include_all_day"
|
||||
CONF_PARSER = "parser"
|
||||
CONF_DOWNLOAD_INTERVAL = "download_interval"
|
||||
CONF_USER_AGENT = "user_agent"
|
||||
CONF_OFFSET_HOURS = "offset_hours"
|
||||
CONF_ACCEPT_HEADER = "accept_header"
|
||||
CONF_CONNECTION_TIMEOUT = "connection_timeout"
|
||||
CONF_SET_TIMEOUT = "set_connection_timeout"
|
||||
CONF_REQUIRES_AUTH = "requires_auth"
|
||||
CONF_ADV_CONNECT_OPTS = "advanced_connection_options"
|
||||
CONF_SUMMARY_DEFAULT = "summary_default"
|
||||
# It'd be really nifty if this could be a translatable string, but it seems
|
||||
# that's not supported, unless I want to roll my own interpretation of the
|
||||
# translate/*.json files. :(
|
||||
# See also https://github.com/home-assistant/core/issues/125075
|
||||
CONF_SUMMARY_DEFAULT_DEFAULT = "No title"
|
125
custom_components/ics_calendar/filter.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Provide Filter class."""
|
||||
|
||||
import re
|
||||
from ast import literal_eval
|
||||
from typing import List, Optional, Pattern
|
||||
|
||||
from .parserevent import ParserEvent
|
||||
|
||||
|
||||
class Filter:
|
||||
"""Filter class.
|
||||
|
||||
The Filter class is used to filter events according to the exclude and
|
||||
include rules.
|
||||
"""
|
||||
|
||||
def __init__(self, exclude: str, include: str):
|
||||
"""Construct Filter class.
|
||||
|
||||
:param exclude: The exclude rules
|
||||
:type exclude: str
|
||||
:param include: The include rules
|
||||
:type include: str
|
||||
"""
|
||||
self._exclude = Filter.set_rules(exclude)
|
||||
self._include = Filter.set_rules(include)
|
||||
|
||||
@staticmethod
|
||||
def set_rules(rules: str) -> List[Pattern]:
|
||||
"""Set the given rules into an array which is returned.
|
||||
|
||||
:param rules: The rules to set
|
||||
:type rules: str
|
||||
:return: An array of regular expressions
|
||||
:rtype: List[Pattern]
|
||||
"""
|
||||
arr = []
|
||||
if rules != "":
|
||||
for rule in literal_eval(rules):
|
||||
if rule.startswith("/"):
|
||||
re_flags = re.NOFLAG
|
||||
[expr, flags] = rule[1:].split("/")
|
||||
for flag in flags:
|
||||
match flag:
|
||||
case "i":
|
||||
re_flags |= re.IGNORECASE
|
||||
case "m":
|
||||
re_flags |= re.MULTILINE
|
||||
case "s":
|
||||
re_flags |= re.DOTALL
|
||||
arr.append(re.compile(expr, re_flags))
|
||||
else:
|
||||
arr.append(re.compile(rule, re.IGNORECASE))
|
||||
return arr
|
||||
|
||||
def _is_match(
|
||||
self, summary: str, description: Optional[str], regexes: List[Pattern]
|
||||
) -> bool:
|
||||
"""Indicate if the event matches the given list of regular expressions.
|
||||
|
||||
:param summary: The event summary to examine
|
||||
:type summary: str
|
||||
:param description: The event description summary to examine
|
||||
:type description: Optional[str]
|
||||
:param regexes: The regular expressions to match against
|
||||
:type regexes: List[]
|
||||
:return: True if the event matches the exclude filter
|
||||
:rtype: bool
|
||||
"""
|
||||
for regex in regexes:
|
||||
if regex.search(summary) or (
|
||||
description and regex.search(description)
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _is_excluded(self, summary: str, description: Optional[str]) -> bool:
|
||||
"""Indicate if the event should be excluded.
|
||||
|
||||
:param summary: The event summary to examine
|
||||
:type summary: str
|
||||
:param description: The event description summary to examine
|
||||
:type description: Optional[str]
|
||||
:return: True if the event matches the exclude filter
|
||||
:rtype: bool
|
||||
"""
|
||||
return self._is_match(summary, description, self._exclude)
|
||||
|
||||
def _is_included(self, summary: str, description: Optional[str]) -> bool:
|
||||
"""Indicate if the event should be included.
|
||||
|
||||
:param summary: The event summary to examine
|
||||
:type summary: str
|
||||
:param description: The event description summary to examine
|
||||
:type description: Optional[str]
|
||||
:return: True if the event matches the include filter
|
||||
:rtype: bool
|
||||
"""
|
||||
return self._is_match(summary, description, self._include)
|
||||
|
||||
def filter(self, summary: str, description: Optional[str]) -> bool:
|
||||
"""Check if the event should be included or not.
|
||||
|
||||
:param summary: The event summary to examine
|
||||
:type summary: str
|
||||
:param description: The event description summary to examine
|
||||
:type description: Optional[str]
|
||||
:return: true if the event should be included, otherwise false
|
||||
:rtype: bool
|
||||
"""
|
||||
add_event = not self._is_excluded(summary, description)
|
||||
if not add_event:
|
||||
add_event = self._is_included(summary, description)
|
||||
return add_event
|
||||
|
||||
def filter_event(self, event: ParserEvent) -> bool:
|
||||
"""Check if the event should be included or not.
|
||||
|
||||
:param event: The event to examine
|
||||
:type event: ParserEvent
|
||||
:return: true if the event should be included, otherwise false
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.filter(event.summary, event.description)
|
27
custom_components/ics_calendar/getparser.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Provide GetParser class."""
|
||||
|
||||
from .icalendarparser import ICalendarParser
|
||||
from .parsers.parser_ics import ParserICS
|
||||
from .parsers.parser_rie import ParserRIE
|
||||
|
||||
|
||||
class GetParser: # pylint: disable=R0903
|
||||
"""Provide get_parser to return an instance of ICalendarParser.
|
||||
|
||||
The class provides a static method , get_instace, to get a parser instance.
|
||||
The non static methods allow this class to act as an "interface" for the
|
||||
parser classes.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_parser(parser: str, *args) -> ICalendarParser | None:
|
||||
"""Get an instance of the requested parser."""
|
||||
# parser_cls = ICalendarParser.get_class(parser)
|
||||
# if parser_cls is not None:
|
||||
# return parser_cls(*args)
|
||||
if parser == "rie":
|
||||
return ParserRIE(*args)
|
||||
if parser == "ics":
|
||||
return ParserICS(*args)
|
||||
|
||||
return None
|
72
custom_components/ics_calendar/icalendarparser.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Provide ICalendarParser class."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .filter import Filter
|
||||
from .parserevent import ParserEvent
|
||||
|
||||
|
||||
class ICalendarParser:
|
||||
"""Provide interface for various parser classes."""
|
||||
|
||||
def set_content(self, content: str):
|
||||
"""Parse content into a calendar object.
|
||||
|
||||
This must be called at least once before get_event_list or
|
||||
get_current_event.
|
||||
:param content is the calendar data
|
||||
:type content str
|
||||
"""
|
||||
|
||||
def set_filter(self, filt: Filter):
|
||||
"""Set a Filter object to filter events.
|
||||
|
||||
:param filt: The Filter object
|
||||
:type exclude: Filter
|
||||
"""
|
||||
|
||||
def get_event_list(
|
||||
self,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
include_all_day: bool,
|
||||
offset_hours: int = 0,
|
||||
) -> list[ParserEvent]:
|
||||
"""Get a list of events.
|
||||
|
||||
Gets the events from start to end, including or excluding all day
|
||||
events.
|
||||
:param start the earliest start time of events to return
|
||||
:type start datetime
|
||||
:param end the latest start time of events to return
|
||||
:type end datetime
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type include_all_day boolean
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
:returns a list of events, or an empty list
|
||||
:rtype list[ParserEvent]
|
||||
"""
|
||||
|
||||
def get_current_event(
|
||||
self,
|
||||
include_all_day: bool,
|
||||
now: datetime,
|
||||
days: int,
|
||||
offset_hours: int = 0,
|
||||
) -> Optional[ParserEvent]:
|
||||
"""Get the current or next event.
|
||||
|
||||
Gets the current event, or the next upcoming event with in the
|
||||
specified number of days, if there is no current event.
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type include_all_day boolean
|
||||
:param now the current date and time
|
||||
:type now datetime
|
||||
:param days the number of days to check for an upcoming event
|
||||
:type days int
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
:returns a ParserEvent or None
|
||||
"""
|
13
custom_components/ics_calendar/manifest.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"domain": "ics_calendar",
|
||||
"name": "ics Calendar",
|
||||
"codeowners": ["@franc6"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://github.com/franc6/ics_calendar",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"issue_tracker": "https://github.com/franc6/ics_calendar/issues",
|
||||
"requirements": ["icalendar~=6.1","python-dateutil>=2.9.0.post0","pytz>=2024.1","recurring_ical_events~=3.5,>=3.5.2","ics==0.7.2","arrow","httpx_auth>=0.22.0,<=0.23.1"],
|
||||
"version": "5.1.3"
|
||||
}
|
20
custom_components/ics_calendar/parserevent.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Provide ParserEvent class."""
|
||||
|
||||
import dataclasses
|
||||
|
||||
from homeassistant.components.calendar import CalendarEvent
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParserEvent(CalendarEvent):
|
||||
"""Class to represent CalendarEvent without validation."""
|
||||
|
||||
def validate(self) -> None:
|
||||
"""Invoke __post_init__ from CalendarEvent."""
|
||||
return super().__post_init__()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Don't do validation steps for this class."""
|
||||
# This is necessary to prevent problems when creating events that don't
|
||||
# have a summary. We'll add a summary after the event is created, not
|
||||
# before, to reduce code repitition.
|
1
custom_components/ics_calendar/parsers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Provide parsers."""
|
191
custom_components/ics_calendar/parsers/parser_ics.py
Normal file
@@ -0,0 +1,191 @@
|
||||
"""Support for ics parser."""
|
||||
|
||||
import re
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Optional, Union
|
||||
|
||||
from arrow import Arrow, get as arrowget
|
||||
from ics import Calendar
|
||||
|
||||
from ..filter import Filter
|
||||
from ..icalendarparser import ICalendarParser
|
||||
from ..parserevent import ParserEvent
|
||||
from ..utility import compare_event_dates
|
||||
|
||||
|
||||
class ParserICS(ICalendarParser):
|
||||
"""Class to provide parser using ics module."""
|
||||
|
||||
def __init__(self):
|
||||
"""Construct ParserICS."""
|
||||
self._re_method = re.compile("^METHOD:.*$", flags=re.MULTILINE)
|
||||
self._calendar = None
|
||||
self._filter = Filter("", "")
|
||||
|
||||
def set_content(self, content: str):
|
||||
"""Parse content into a calendar object.
|
||||
|
||||
This must be called at least once before get_event_list or
|
||||
get_current_event.
|
||||
:param content is the calendar data
|
||||
:type content str
|
||||
"""
|
||||
self._calendar = Calendar(re.sub(self._re_method, "", content))
|
||||
|
||||
def set_filter(self, filt: Filter):
|
||||
"""Set a Filter object to filter events.
|
||||
|
||||
:param filt: The Filter object
|
||||
:type exclude: Filter
|
||||
"""
|
||||
self._filter = filt
|
||||
|
||||
def get_event_list(
|
||||
self, start, end, include_all_day: bool, offset_hours: int = 0
|
||||
) -> list[ParserEvent]:
|
||||
"""Get a list of events.
|
||||
|
||||
Gets the events from start to end, including or excluding all day
|
||||
events.
|
||||
:param start the earliest start time of events to return
|
||||
:type datetime
|
||||
:param end the latest start time of events to return
|
||||
:type datetime
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type boolean
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
:returns a list of events, or an empty list
|
||||
:rtype list[ParserEvent]
|
||||
"""
|
||||
event_list: list[ParserEvent] = []
|
||||
|
||||
if self._calendar is not None:
|
||||
# ics 0.8 takes datetime not Arrow objects
|
||||
# ar_start = start
|
||||
# ar_end = end
|
||||
ar_start = arrowget(start - timedelta(hours=offset_hours))
|
||||
ar_end = arrowget(end - timedelta(hours=offset_hours))
|
||||
|
||||
for event in self._calendar.timeline.included(ar_start, ar_end):
|
||||
if event.all_day and not include_all_day:
|
||||
continue
|
||||
summary: str = ""
|
||||
# ics 0.8 uses 'summary' reliably, older versions use 'name'
|
||||
# if hasattr(event, "summary"):
|
||||
# summary = event.summary
|
||||
# elif hasattr(event, "name"):
|
||||
summary = event.name
|
||||
calendar_event: ParserEvent = ParserEvent(
|
||||
summary=summary,
|
||||
start=ParserICS.get_date(
|
||||
event.begin, event.all_day, offset_hours
|
||||
),
|
||||
end=ParserICS.get_date(
|
||||
event.end, event.all_day, offset_hours
|
||||
),
|
||||
location=event.location,
|
||||
description=event.description,
|
||||
)
|
||||
if self._filter.filter_event(calendar_event):
|
||||
event_list.append(calendar_event)
|
||||
|
||||
return event_list
|
||||
|
||||
def get_current_event( # noqa: $701
|
||||
self,
|
||||
include_all_day: bool,
|
||||
now: datetime,
|
||||
days: int,
|
||||
offset_hours: int = 0,
|
||||
) -> Optional[ParserEvent]:
|
||||
"""Get the current or next event.
|
||||
|
||||
Gets the current event, or the next upcoming event with in the
|
||||
specified number of days, if there is no current event.
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type boolean
|
||||
:param now the current date and time
|
||||
:type datetime
|
||||
:param days the number of days to check for an upcoming event
|
||||
:type int
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type int
|
||||
:returns a ParserEvent or None
|
||||
"""
|
||||
if self._calendar is None:
|
||||
return None
|
||||
|
||||
temp_event = None
|
||||
now = now - timedelta(offset_hours)
|
||||
end = now + timedelta(days=days)
|
||||
for event in self._calendar.timeline.included(
|
||||
arrowget(now), arrowget(end)
|
||||
):
|
||||
if event.all_day and not include_all_day:
|
||||
continue
|
||||
|
||||
if not self._filter.filter(event.name, event.description):
|
||||
continue
|
||||
|
||||
if temp_event is None or compare_event_dates(
|
||||
now,
|
||||
temp_event.end,
|
||||
temp_event.begin,
|
||||
temp_event.all_day,
|
||||
event.end,
|
||||
event.begin,
|
||||
event.all_day,
|
||||
):
|
||||
temp_event = event
|
||||
|
||||
if temp_event is None:
|
||||
return None
|
||||
# if hasattr(event, "summary"):
|
||||
# summary = temp_event.summary
|
||||
# elif hasattr(event, "name"):
|
||||
summary = temp_event.name
|
||||
return ParserEvent(
|
||||
summary=summary,
|
||||
start=ParserICS.get_date(
|
||||
temp_event.begin, temp_event.all_day, offset_hours
|
||||
),
|
||||
end=ParserICS.get_date(
|
||||
temp_event.end, temp_event.all_day, offset_hours
|
||||
),
|
||||
location=temp_event.location,
|
||||
description=temp_event.description,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_date(
|
||||
arw: Arrow, is_all_day: bool, offset_hours: int
|
||||
) -> Union[datetime, date]:
|
||||
"""Get datetime.
|
||||
|
||||
:param arw The arrow object representing the date.
|
||||
:type Arrow
|
||||
:param is_all_day If true, the returned datetime will have the time
|
||||
component set to 0.
|
||||
:type: bool
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type int
|
||||
:returns The datetime.
|
||||
:rtype datetime
|
||||
"""
|
||||
# if isinstance(arw, Arrow):
|
||||
if is_all_day:
|
||||
return arw.date()
|
||||
# else:
|
||||
# if arw.tzinfo is None or arw.tzinfo.utcoffset(arw) is None
|
||||
# or is_all_day:
|
||||
# arw = arw.astimezone()
|
||||
# if is_all_day:
|
||||
# return arw.date()
|
||||
#
|
||||
arw = arw.shift(hours=offset_hours)
|
||||
|
||||
return_value = arw.datetime
|
||||
if return_value.tzinfo is None:
|
||||
return_value = return_value.astimezone()
|
||||
return return_value
|
199
custom_components/ics_calendar/parsers/parser_rie.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""Support for recurring_ical_events parser."""
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Optional, Union
|
||||
|
||||
import recurring_ical_events as rie
|
||||
from icalendar import Calendar
|
||||
|
||||
from ..filter import Filter
|
||||
from ..icalendarparser import ICalendarParser
|
||||
from ..parserevent import ParserEvent
|
||||
from ..utility import compare_event_dates
|
||||
|
||||
|
||||
class ParserRIE(ICalendarParser):
|
||||
"""Provide parser using recurring_ical_events."""
|
||||
|
||||
def __init__(self):
|
||||
"""Construct ParserRIE."""
|
||||
self._calendar = None
|
||||
self.oneday = timedelta(days=1)
|
||||
self.oneday2 = timedelta(hours=23, minutes=59, seconds=59)
|
||||
self._filter = Filter("", "")
|
||||
|
||||
def set_content(self, content: str):
|
||||
"""Parse content into a calendar object.
|
||||
|
||||
This must be called at least once before get_event_list or
|
||||
get_current_event.
|
||||
:param content is the calendar data
|
||||
:type content str
|
||||
"""
|
||||
self._calendar = Calendar.from_ical(content)
|
||||
|
||||
def set_filter(self, filt: Filter):
|
||||
"""Set a Filter object to filter events.
|
||||
|
||||
:param filt: The Filter object
|
||||
:type exclude: Filter
|
||||
"""
|
||||
self._filter = filt
|
||||
|
||||
def get_event_list(
|
||||
self,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
include_all_day: bool,
|
||||
offset_hours: int = 0,
|
||||
) -> list[ParserEvent]:
|
||||
"""Get a list of events.
|
||||
|
||||
Gets the events from start to end, including or excluding all day
|
||||
events.
|
||||
:param start the earliest start time of events to return
|
||||
:type datetime
|
||||
:param end the latest start time of events to return
|
||||
:type datetime
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type boolean
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
:returns a list of events, or an empty list
|
||||
:rtype list[ParserEvent]
|
||||
"""
|
||||
event_list: list[ParserEvent] = []
|
||||
|
||||
if self._calendar is not None:
|
||||
for event in rie.of(self._calendar, skip_bad_series=True).between(
|
||||
start - timedelta(hours=offset_hours),
|
||||
end - timedelta(hours=offset_hours),
|
||||
):
|
||||
start, end, all_day = self.is_all_day(event, offset_hours)
|
||||
|
||||
if all_day and not include_all_day:
|
||||
continue
|
||||
|
||||
calendar_event: ParserEvent = ParserEvent(
|
||||
summary=event.get("SUMMARY"),
|
||||
start=start,
|
||||
end=end,
|
||||
location=event.get("LOCATION"),
|
||||
description=event.get("DESCRIPTION"),
|
||||
)
|
||||
if self._filter.filter_event(calendar_event):
|
||||
event_list.append(calendar_event)
|
||||
|
||||
return event_list
|
||||
|
||||
def get_current_event( # noqa: R701
|
||||
self,
|
||||
include_all_day: bool,
|
||||
now: datetime,
|
||||
days: int,
|
||||
offset_hours: int = 0,
|
||||
) -> Optional[ParserEvent]:
|
||||
"""Get the current or next event.
|
||||
|
||||
Gets the current event, or the next upcoming event with in the
|
||||
specified number of days, if there is no current event.
|
||||
:param include_all_day if true, all day events will be included.
|
||||
:type boolean
|
||||
:param now the current date and time
|
||||
:type datetime
|
||||
:param days the number of days to check for an upcoming event
|
||||
:type int
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
:returns a ParserEvent or None
|
||||
"""
|
||||
if self._calendar is None:
|
||||
return None
|
||||
|
||||
temp_event = None
|
||||
temp_start: date | datetime = None
|
||||
temp_end: date | datetime = None
|
||||
temp_all_day: bool = None
|
||||
end: datetime = now + timedelta(days=days)
|
||||
for event in rie.of(self._calendar, skip_bad_series=True).between(
|
||||
now - timedelta(hours=offset_hours),
|
||||
end - timedelta(hours=offset_hours),
|
||||
):
|
||||
start, end, all_day = self.is_all_day(event, offset_hours)
|
||||
|
||||
if all_day and not include_all_day:
|
||||
continue
|
||||
|
||||
if not self._filter.filter(
|
||||
event.get("SUMMARY"), event.get("DESCRIPTION")
|
||||
):
|
||||
continue
|
||||
|
||||
if temp_start is None or compare_event_dates(
|
||||
now, temp_end, temp_start, temp_all_day, end, start, all_day
|
||||
):
|
||||
temp_event = event
|
||||
temp_start = start
|
||||
temp_end = end
|
||||
temp_all_day = all_day
|
||||
|
||||
if temp_event is None:
|
||||
return None
|
||||
|
||||
return ParserEvent(
|
||||
summary=temp_event.get("SUMMARY"),
|
||||
start=temp_start,
|
||||
end=temp_end,
|
||||
location=temp_event.get("LOCATION"),
|
||||
description=temp_event.get("DESCRIPTION"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_date(date_time) -> Union[datetime, date]:
|
||||
"""Get datetime with timezone information.
|
||||
|
||||
If a date object is passed, it will first have a time component added,
|
||||
set to 0.
|
||||
:param date_time The date or datetime object
|
||||
:type date_time datetime or date
|
||||
:type: bool
|
||||
:returns The datetime.
|
||||
:rtype datetime
|
||||
"""
|
||||
# Must use type here, since a datetime is also a date!
|
||||
if isinstance(date_time, date) and not isinstance(date_time, datetime):
|
||||
date_time = datetime.combine(date_time, datetime.min.time())
|
||||
return date_time.astimezone()
|
||||
|
||||
def is_all_day(self, event, offset_hours: int):
|
||||
"""Determine if the event is an all day event.
|
||||
|
||||
Return all day status and start and end times for the event.
|
||||
:param event The event to examine
|
||||
:param offset_hours the number of hours to offset the event
|
||||
:type offset_hours int
|
||||
"""
|
||||
start: datetime | date = ParserRIE.get_date(event.get("DTSTART").dt)
|
||||
end: datetime | date = ParserRIE.get_date(event.get("DTEND").dt)
|
||||
all_day = False
|
||||
diff = event.get("DURATION")
|
||||
if diff is not None:
|
||||
diff = diff.dt
|
||||
else:
|
||||
diff = end - start
|
||||
if (start == end or diff in {self.oneday, self.oneday2}) and all(
|
||||
x == 0 for x in [start.hour, start.minute, start.second]
|
||||
):
|
||||
# if all_day, start and end must be date, not datetime!
|
||||
start = start.date()
|
||||
end = end.date()
|
||||
all_day = True
|
||||
else:
|
||||
start = start + timedelta(hours=offset_hours)
|
||||
end = end + timedelta(hours=offset_hours)
|
||||
if start.tzinfo is None:
|
||||
start = start.astimezone()
|
||||
if end.tzinfo is None:
|
||||
end = end.astimezone()
|
||||
|
||||
return start, end, all_day
|
77
custom_components/ics_calendar/strings.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "YAML configuration is deprecated for ICS Calendar",
|
||||
"description": "YAML configuration of ics_calendar is deprecated and will be removed in ics_calendar v5.0.0. Your configuration items have been imported. Please remove them from your configuration.yaml file."
|
||||
}
|
||||
},
|
||||
"title": "ICS Calendar",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"days": "Days",
|
||||
"include_all_day": "Include all day events?"
|
||||
},
|
||||
"title": "Add Calendar"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "Exclude filter",
|
||||
"include": "Include filter",
|
||||
"prefix": "String to prefix all event summaries",
|
||||
"download_interval": "Download interval (minutes)",
|
||||
"offset_hours": "Number of hours to offset event times",
|
||||
"parser": "Parser (rie or ics)",
|
||||
"summary_default": "Summary if event doesn't have one"
|
||||
},
|
||||
"title": "Calendar Options"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL of ICS file",
|
||||
"requires_auth": "Requires authentication?",
|
||||
"advanced_connection_options": "Set advanced connection options?"
|
||||
},
|
||||
"title": "Connection Options"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Please note this component supports only HTTP Basic Auth and HTTP Digest Auth. More advanced authentication, like OAuth is not supported at this time.",
|
||||
"title": "Authentication"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Custom Accept header for broken servers",
|
||||
"user_agent": "Custom User-agent header",
|
||||
"set_connection_timeout": "Change connection timeout?"
|
||||
},
|
||||
"title": "Advanced Connection Options"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Connection timeout in seconds"
|
||||
},
|
||||
"title": "Connection Timeout Options"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "Authorization failed for calendar. Please re-configured the calendar URL and/or authentication settings.",
|
||||
"title": "Authorization Failure for ICS Calendar"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "The calendar name must not be empty.",
|
||||
"empty_url": "The url must not be empty.",
|
||||
"download_interval_too_small": "The download interval must be at least 15.",
|
||||
"exclude_include_cannot_be_the_same": "The exclude and include strings must not be the same",
|
||||
"exclude_must_be_array": "The exclude option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information.",
|
||||
"include_must_be_array": "The include option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
76
custom_components/ics_calendar/translations/de.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "YAML-Konfiguration für ICS-Kalender ist veraltet",
|
||||
"description": "Die YAML-Konfiguration von ics_calendar ist veraltet und wird in ics_calendar v5.0.0 entfernt. Deine Konfigurationselemente wurden importiert. Bitte entferne sie aus deiner configuration.yaml-Datei."
|
||||
}
|
||||
},
|
||||
"title": "ICS-Kalender",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"days": "Tage",
|
||||
"include_all_day": "Ganztägige Ereignisse einbeziehen?"
|
||||
},
|
||||
"title": "Kalender hinzufügen"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "auszuschließende Ereignisse",
|
||||
"include": "einzuschließende Ereignisse",
|
||||
"prefix": "String, um allen Zusammenfassungen ein Präfix hinzuzufügen",
|
||||
"download_interval": "Download-Intervall (Minuten)",
|
||||
"offset_hours": "Anzahl der Stunden, um Ereigniszeiten zu versetzen",
|
||||
"parser": "Parser (rie oder ics)"
|
||||
},
|
||||
"title": "Kalender-Optionen"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL der ICS-Datei",
|
||||
"requires_auth": "Erfordert Authentifizierung?",
|
||||
"advanced_connection_options": "Erweiterte Verbindungsoptionen festlegen?"
|
||||
},
|
||||
"title": "Verbindungsoptionen"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Benutzername",
|
||||
"password": "Passwort"
|
||||
},
|
||||
"description": "Bitte beachte, dass nur HTTP Basic Auth und HTTP Digest Auth unterstützt wird. Authentifizierungsmethoden wie OAuth werden derzeit nicht unterstützt.",
|
||||
"title": "Authentifizierung"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Eigener Accept-Header für fehlerhafte Server",
|
||||
"user_agent": "Eigener User-Agent-Header",
|
||||
"set_connection_timeout": "Verbindungstimeout ändern?"
|
||||
},
|
||||
"title": "Erweiterte Verbindungsoptionen"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Verbindungstimeout in Sekunden"
|
||||
},
|
||||
"title": "Verbindungstimeout-Optionen"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "Die Autorisierung für den Kalender ist fehlgeschlagen. Bitte konfiguriere die Kalender-URL und/oder die Authentifizierungseinstellungen neu.",
|
||||
"title": "Autorisierungsfehler für ICS-Kalender"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "Der Kalendername darf nicht leer sein.",
|
||||
"empty_url": "Die URL darf nicht leer sein.",
|
||||
"download_interval_too_small": "Das Download-Intervall muss mindestens 15 betragen.",
|
||||
"exclude_include_cannot_be_the_same": "Die Ausschluss- und Einschluss-Strings dürfen nicht identisch sein.",
|
||||
"exclude_must_be_array": "Die \"auszuschließenden Ereignisse\" müssen ein Array von Zeichenfolgen oder regulären Ausdrücken sein. Weitere Informationen finden Sie unter https://github.com/franc6/ics_calendar/blob/releases/README.md#filters.",
|
||||
"include_must_be_array": "Die \"einzuschließenden Ereignisse\" müssen ein Array von Zeichenfolgen oder regulären Ausdrücken sein. Weitere Informationen finden Sie unter https://github.com/franc6/ics_calendar/blob/releases/README.md#filters."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
77
custom_components/ics_calendar/translations/en.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "YAML configuration is deprecated for ICS Calendar",
|
||||
"description": "YAML configuration of ics_calendar is deprecated and will be removed in ics_calendar v5.0.0. Your configuration items have been imported. Please remove them from your configuration.yaml file."
|
||||
}
|
||||
},
|
||||
"title": "ICS Calendar",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Name",
|
||||
"days": "Days",
|
||||
"include_all_day": "Include all day events?"
|
||||
},
|
||||
"title": "Add Calendar"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "Exclude filter",
|
||||
"include": "Include filter",
|
||||
"prefix": "String to prefix all event summaries",
|
||||
"download_interval": "Download interval (minutes)",
|
||||
"offset_hours": "Number of hours to offset event times",
|
||||
"parser": "Parser (rie or ics)",
|
||||
"summary_default": "Summary if event doesn't have one"
|
||||
},
|
||||
"title": "Calendar Options"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL of ICS file",
|
||||
"requires_auth": "Requires authentication?",
|
||||
"advanced_connection_options": "Set advanced connection options?"
|
||||
},
|
||||
"title": "Connection Options"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Username",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Please note this component supports only HTTP Basic Auth and HTTP Digest Auth. More advanced authentication, like OAuth is not supported at this time.",
|
||||
"title": "Authentication"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Custom Accept header for broken servers",
|
||||
"user_agent": "Custom User-agent header",
|
||||
"set_connection_timeout": "Change connection timeout?"
|
||||
},
|
||||
"title": "Advanced Connection Options"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Connection timeout in seconds"
|
||||
},
|
||||
"title": "Connection Timeout Options"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "Authorization failed for calendar. Please re-configured the calendar URL and/or authentication settings.",
|
||||
"title": "Authorization Failure for ICS Calendar"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "The calendar name must not be empty.",
|
||||
"empty_url": "The url must not be empty.",
|
||||
"download_interval_too_small": "The download interval must be at least 15.",
|
||||
"exclude_include_cannot_be_the_same": "The exclude and include strings must not be the same",
|
||||
"exclude_must_be_array": "The exclude option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information.",
|
||||
"include_must_be_array": "The include option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
77
custom_components/ics_calendar/translations/es.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "La configuración YAML está obsoleta para ICS Calendar",
|
||||
"description": "La configuración YAML de ics_calendar está obsoleta y se eliminará en ics_calendar v5.0.0. Sus elementos de configuración se han importado. Elimínelos de su archivo configuration.yaml."
|
||||
}
|
||||
},
|
||||
"title": "ICS Calendar",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Nombre",
|
||||
"days": "Días",
|
||||
"include_all_day": "¿Incluir eventos de todo el día?"
|
||||
},
|
||||
"title": "Agregar calendario"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "Excluir filtro",
|
||||
"include": "Incluir filtro",
|
||||
"prefix": "Cadena que precederá a todos los resúmenes de eventos",
|
||||
"download_interval": "Intervalo de descarga (minutos)",
|
||||
"offset_hours": "Número de horas para compensar los tiempos del evento",
|
||||
"parser": "Parser (rie or ics)",
|
||||
"summary_default": "Resumen si el evento no tiene uno"
|
||||
},
|
||||
"title": "Opciones de calendario"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL del archivo ICS",
|
||||
"requires_auth": "¿Requiere autentificación?",
|
||||
"advanced_connection_options": "¿Establecer opciones de conexión avanzadas?"
|
||||
},
|
||||
"title": "Opciones de conexión"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Nombre de usuario",
|
||||
"password": "Contraseña"
|
||||
},
|
||||
"description": "Tenga en cuenta que este componente solo admite la autenticación básica HTTP y la autenticación HTTP Digest. Actualmente, no se admiten autenticaciones más avanzadas, como OAuth.",
|
||||
"title": "Autentificación"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Encabezado Accept personalizado para servidores rotos",
|
||||
"user_agent": "Encabezado de agente de usuario personalizado",
|
||||
"set_connection_timeout": "¿Cambiar el tiempo de espera de la conexión?"
|
||||
},
|
||||
"title": "Opciones avanzadas de conexión"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Tiempo de espera de la conexión en segundos"
|
||||
},
|
||||
"title": "Opciones de tiempo de espera de la conexión"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "Error de autorización para el calendario. Vuelva a configurar la URL del calendario y/o los ajustes de autenticación.",
|
||||
"title": "Fallo de autorización para ICS Calendar"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "El nombre del calendario no debe estar vacío.",
|
||||
"empty_url": "La url no debe estar vacía.",
|
||||
"download_interval_too_small": "El intervalo de descarga debe ser de al menos 15.",
|
||||
"exclude_include_cannot_be_the_same": "Las cadenas de exclusión e inclusión no deben ser las mismas",
|
||||
"exclude_must_be_array": "La opción de exclusión debe ser una matriz de cadenas o expresiones regulares. Consulte https://github.com/franc6/ics_calendar/blob/releases/README.md#filters para obtener más información.",
|
||||
"include_must_be_array": "La opción de inclusión debe ser un array de cadenas o expresiones regulares. Consulte https://github.com/franc6/ics_calendar/blob/releases/README.md#filters para obtener más información."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
76
custom_components/ics_calendar/translations/fr.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "La configuration YAML pour ICS Calendar est obsolète",
|
||||
"description": "La configuration YAML d'ICS Calendar est obsolète et sera supprimée dans la version 5.0.0 d'ics_calendar. Les éléments de votre configuration ont été importés. Veuillez les supprimer de votre fichier configuration.yaml."
|
||||
}
|
||||
},
|
||||
"title": "ICS Calendar",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Nom",
|
||||
"days": "Jours",
|
||||
"include_all_day": "Inclure les événements à la journée ?"
|
||||
},
|
||||
"title": "Ajouter un calendrier"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "Exclure les événements contenant",
|
||||
"include": "Inclure les événements contenant",
|
||||
"prefix": "Préfixer tous les résumés d'événements avec",
|
||||
"download_interval": "Intervalle de téléchargement (minutes)",
|
||||
"offset_hours": "Décalage à appliquer aux horaires des événements (heures)",
|
||||
"parser": "Parseur (rie ou ics)"
|
||||
},
|
||||
"title": "Options du calendrier"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL du fichier ICS",
|
||||
"requires_auth": "Authentification requise ?",
|
||||
"advanced_connection_options": "Définir les options avancées de la connexion ?"
|
||||
},
|
||||
"title": "Options de connexion"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Utilisateur",
|
||||
"password": "Mot de passe"
|
||||
},
|
||||
"description": "Veuillez noter que cette intégration ne supporte que les modes d'authentification HTTP Basic et HTTP Digest. Les méthodes d'authentification plus avancées, telles que OAuth, ne sont pas supportées actuellement.",
|
||||
"title": "Authentification"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Entête 'Accept' personnalisée pour les serveurs injoignables",
|
||||
"user_agent": "Entête 'User-agent' personnalisée",
|
||||
"set_connection_timeout": "Modifier le délai maximum autorisé pour la connexion ?"
|
||||
},
|
||||
"title": "Options avancées de connexion"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Délai maximum autorisé pour la connexion (secondes)"
|
||||
},
|
||||
"title": "Options de délai de connexion"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "L'autorisation a échoué pour le calendrier. Veuillez vérifier l'URL du calendrier et/ou les paramètres d'authentification.",
|
||||
"title": "Échec d'autorisation pour ICS Calendar"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "Le nom du calendrier doit être renseigné.",
|
||||
"empty_url": "L'URL du calendrier doit être renseignée.",
|
||||
"download_interval_too_small": "L'intervalle de téléchargement ne peut pas être inférieur à 15 minutes.",
|
||||
"exclude_include_cannot_be_the_same": "Les valeurs d'exclusion et d'inclusion ne peuvent pas être identiques.",
|
||||
"exclude_must_be_array": "The exclude option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information.",
|
||||
"include_must_be_array": "The include option must be an array of strings or regular expressions. See https://github.com/franc6/ics_calendar/blob/releases/README.md#filters for more information."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
77
custom_components/ics_calendar/translations/pt-br.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"issues": {
|
||||
"YAML_Warning": {
|
||||
"title": "A configuração YAML está obsoleta para o ICS Calendar",
|
||||
"description": "A configuração YAML do ics_calendar está obsoleta e será removida na versão 5.0.0 do ics_calendar. Seus itens de configuração foram importados. Por favor, remova-os do seu arquivo configuration.yaml."
|
||||
}
|
||||
},
|
||||
"title": "ICS Calendar",
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"name": "Nome",
|
||||
"days": "Dias",
|
||||
"include_all_day": "Incluir eventos de dia inteiro?"
|
||||
},
|
||||
"title": "Adicionar Calendário"
|
||||
},
|
||||
"calendar_opts": {
|
||||
"data": {
|
||||
"exclude": "Filtro de exclusão",
|
||||
"include": "Filtro de inclusão",
|
||||
"prefix": "Texto para prefixar todos os resumos de eventos",
|
||||
"download_interval": "Intervalo de download (minutos)",
|
||||
"offset_hours": "Número de horas para ajustar os horários dos eventos",
|
||||
"parser": "Parser (rie ou ics)",
|
||||
"summary_default": "Resumo padrão se o evento não tiver um"
|
||||
},
|
||||
"title": "Opções do Calendário"
|
||||
},
|
||||
"connect_opts": {
|
||||
"data": {
|
||||
"url": "URL do arquivo ICS",
|
||||
"requires_auth": "Requer autenticação?",
|
||||
"advanced_connection_options": "Definir opções de conexão avançadas?"
|
||||
},
|
||||
"title": "Opções de Conexão"
|
||||
},
|
||||
"auth_opts": {
|
||||
"data": {
|
||||
"username": "Usuário",
|
||||
"password": "Senha"
|
||||
},
|
||||
"description": "Este componente oferece suporte apenas para HTTP Basic Auth e HTTP Digest Auth. Métodos de autenticação mais avançados, como OAuth, ainda não são suportados.",
|
||||
"title": "Autenticação"
|
||||
},
|
||||
"adv_connect_opts": {
|
||||
"data": {
|
||||
"accept_header": "Cabeçalho Accept personalizado para servidores com problemas",
|
||||
"user_agent": "Cabeçalho User-agent personalizado",
|
||||
"set_connection_timeout": "Alterar tempo limite de conexão?"
|
||||
},
|
||||
"title": "Opções Avançadas de Conexão"
|
||||
},
|
||||
"timeout_opts": {
|
||||
"data": {
|
||||
"connection_timeout": "Tempo limite de conexão em segundos"
|
||||
},
|
||||
"title": "Opções de Tempo Limite de Conexão"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "A autorização falhou para o calendário. Por favor, reconfigure a URL do calendário e/ou as configurações de autenticação.",
|
||||
"title": "Falha de Autorização para o ICS Calendar"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"empty_name": "O nome do calendário não pode estar vazio.",
|
||||
"empty_url": "A URL não pode estar vazia.",
|
||||
"download_interval_too_small": "O intervalo de download deve ser de pelo menos 15.",
|
||||
"exclude_include_cannot_be_the_same": "As strings de exclusão e inclusão não podem ser as mesmas.",
|
||||
"exclude_must_be_array": "A opção de exclusão deve ser um array de strings ou expressões regulares. Veja https://github.com/franc6/ics_calendar/blob/releases/README.md#filters para mais informações.",
|
||||
"include_must_be_array": "A opção de inclusão deve ser um array de strings ou expressões regulares. Veja https://github.com/franc6/ics_calendar/blob/releases/README.md#filters para mais informações."
|
||||
},
|
||||
"abort": {
|
||||
}
|
||||
}
|
||||
}
|
38
custom_components/ics_calendar/utility.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Utility methods."""
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
|
||||
def make_datetime(val):
|
||||
"""Ensure val is a datetime, not a date."""
|
||||
if isinstance(val, date) and not isinstance(val, datetime):
|
||||
return datetime.combine(val, datetime.min.time()).astimezone()
|
||||
return val
|
||||
|
||||
|
||||
def compare_event_dates( # pylint: disable=R0913,R0917
|
||||
now, end2, start2, all_day2, end, start, all_day
|
||||
) -> bool:
|
||||
"""Determine if end2 and start2 are newer than end and start."""
|
||||
# Make sure we only compare datetime values, not dates with datetimes.
|
||||
# Set each date object to a datetime at midnight.
|
||||
end = make_datetime(end)
|
||||
end2 = make_datetime(end2)
|
||||
start = make_datetime(start)
|
||||
start2 = make_datetime(start2)
|
||||
|
||||
if all_day2 == all_day:
|
||||
if end2 == end:
|
||||
return start2 > start
|
||||
return end2 > end and start2 >= start
|
||||
|
||||
if now.tzinfo is None:
|
||||
now = now.astimezone()
|
||||
|
||||
event2_current = start2 <= now <= end2
|
||||
event_current = start <= now <= end
|
||||
|
||||
if event_current and event2_current:
|
||||
return all_day
|
||||
|
||||
return start2 >= start or end2 >= end
|
BIN
custom_components/ics_calendar_old.tar.bz2
Normal file
141
esphome/atorch-ble-proxy.yaml
Normal file
@@ -0,0 +1,141 @@
|
||||
substitutions:
|
||||
name: "dc-load"
|
||||
friendly_name: Atorch programmable DC load
|
||||
external_components_source: github://syssi/esphome-atorch-dl24@main
|
||||
dl24_mac_address: !secret dl24_mac_address
|
||||
project_version: 2.1.0
|
||||
device_description: "Monitor and control a Atorch meter via bluetooth BLE"
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
comment: ${device_description}
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "syssi.esphome-atorch-dl24"
|
||||
version: ${project_version}
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
external_components:
|
||||
- source: ${external_components_source}
|
||||
refresh: 0s
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
# Enable over-the-air updates
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: atorch-dc-load.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "DC-Load Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
#esp32_ble_tracker:
|
||||
# scan_parameters:
|
||||
# active: true
|
||||
# on_ble_advertise:
|
||||
# then:
|
||||
# - lambda: |-
|
||||
# if (x.get_name().rfind("-BLE", 0) == 0) {
|
||||
# ESP_LOGI("ble_adv", "New Atorch device found");
|
||||
# ESP_LOGI("ble_adv", " Name: %s", x.get_name().c_str());
|
||||
# ESP_LOGI("ble_adv", " MAC address: %s", x.address_str().c_str());
|
||||
# ESP_LOGD("ble_adv", " Advertised service UUIDs:");
|
||||
# for (auto uuid : x.get_service_uuids()) {
|
||||
# ESP_LOGD("ble_adv", " - %s", uuid.to_string().c_str());
|
||||
# }
|
||||
# }
|
||||
#
|
||||
#text_sensor:
|
||||
# - platform: ble_scanner
|
||||
# name: "BLE Devices Scanner"
|
||||
|
||||
esp32_ble_tracker:
|
||||
scan_parameters:
|
||||
active: False
|
||||
|
||||
ble_client:
|
||||
- mac_address: ${dl24_mac_address}
|
||||
id: ble_client0
|
||||
|
||||
atorch_dl24:
|
||||
- id: atorch0
|
||||
ble_client_id: ble_client0
|
||||
check_crc: false
|
||||
# The meter publishes a status report per second via BLE notification. If you don't like this update interval
|
||||
# you can use this setting to throttle the sensor updates by skipping some status reports.
|
||||
throttle: 0s
|
||||
|
||||
binary_sensor:
|
||||
- platform: atorch_dl24
|
||||
atorch_dl24_id: atorch0
|
||||
running:
|
||||
name: "${name} running"
|
||||
|
||||
sensor:
|
||||
- platform: atorch_dl24
|
||||
atorch_dl24_id: atorch0
|
||||
voltage:
|
||||
name: "${name} voltage"
|
||||
current:
|
||||
name: "${name} current"
|
||||
power:
|
||||
name: "${name} power"
|
||||
capacity:
|
||||
name: "${name} capacity"
|
||||
energy:
|
||||
name: "${name} energy"
|
||||
temperature:
|
||||
name: "${name} temperature"
|
||||
dim_backlight:
|
||||
name: "${name} dim backlight"
|
||||
runtime:
|
||||
name: "${name} runtime"
|
||||
|
||||
text_sensor:
|
||||
- platform: atorch_dl24
|
||||
atorch_dl24_id: atorch0
|
||||
runtime_formatted:
|
||||
name: "${name} runtime formatted"
|
||||
|
||||
button:
|
||||
- platform: atorch_dl24
|
||||
atorch_dl24_id: atorch0
|
||||
reset_energy:
|
||||
name: "${name} reset energy"
|
||||
reset_capacity:
|
||||
name: "${name} reset capacity"
|
||||
reset_runtime:
|
||||
name: "${name} reset runtime"
|
||||
reset_all:
|
||||
name: "${name} reset all"
|
||||
usb_plus:
|
||||
name: "${name} plus"
|
||||
usb_minus:
|
||||
name: "${name} minus"
|
||||
setup:
|
||||
name: "${name} setup"
|
||||
enter:
|
||||
name: "${name} enter"
|
@@ -1,12 +1,25 @@
|
||||
substitutions:
|
||||
name: "aussensensor"
|
||||
friendly_name: "Außensensor"
|
||||
|
||||
esphome:
|
||||
name: aussensensor
|
||||
platform: ESP32
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.outdoor
|
||||
version: "0.8"
|
||||
min_version: 2022.1.0
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: aussensensor.home
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
@@ -26,6 +39,7 @@ api:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# DHT22 sensor
|
||||
|
76
esphome/badezimmer-luft.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
substitutions:
|
||||
name: "bathroom"
|
||||
friendly_name: "Bad"
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.outdoor
|
||||
version: "0.8"
|
||||
min_version: 2022.1.0
|
||||
|
||||
esp32:
|
||||
board: esp32-c3-devkitm-1
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: bathroom.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Bad Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Initialize I²C
|
||||
i2c:
|
||||
- id: bus_a
|
||||
sda: 6
|
||||
scl: 7
|
||||
scan: true
|
||||
|
||||
# Temp/humidity sensors
|
||||
sensor:
|
||||
- platform: dht
|
||||
model: dht22
|
||||
pin: 4
|
||||
temperature:
|
||||
name: "Badezimmer Temperatur DHT"
|
||||
humidity:
|
||||
name: "Badezimmer Luftfeuchtigkeit DHT"
|
||||
update_interval: 60s
|
||||
- platform: sht3xd
|
||||
temperature:
|
||||
name: "Badezimmer Temperatur"
|
||||
filters:
|
||||
- offset: -4.4
|
||||
humidity:
|
||||
name: "Badezimmer Luftfeuchtigkeit"
|
||||
address: 0x44
|
||||
heater_enabled: True
|
||||
update_interval: 60s
|
||||
|
||||
# WiFi signal strength
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signalstärke"
|
||||
update_interval: 60s
|
@@ -1,7 +1,16 @@
|
||||
esphome:
|
||||
substitutions:
|
||||
name: epaperframe
|
||||
platform: ESP32
|
||||
board: nodemcu-32s
|
||||
friendly_name: "Info-Dashboard"
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: dashboard.epaper
|
||||
version: "1.0"
|
||||
min_version: 2022.4.0
|
||||
|
||||
on_boot:
|
||||
priority: -10
|
||||
then:
|
||||
@@ -9,6 +18,11 @@ esphome:
|
||||
- display.page.show: power # temporary for power page dev
|
||||
- component.update: epaper
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
@@ -32,6 +46,7 @@ api:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Power key of li-ion charger
|
||||
@@ -104,7 +119,7 @@ sensor:
|
||||
update_interval: 60s
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.wohnzimmer_feinstaub_pm_2_5um
|
||||
entity_id: sensor.livingroom_wohnzimmer_feinstaub_pm_2_5_m
|
||||
id: pm25_livingroom
|
||||
|
||||
- platform: homeassistant
|
||||
@@ -120,7 +135,7 @@ sensor:
|
||||
id: co2_livingroom
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.feinstaub_pm_2_5um
|
||||
entity_id: sensor.sleepingroom_feinstaub_pm_2_5_m
|
||||
id: pm25_sleepingroom
|
||||
|
||||
- platform: homeassistant
|
||||
@@ -136,23 +151,23 @@ sensor:
|
||||
id: co2_sleepingroom
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.temperatur_aussen
|
||||
entity_id: sensor.aussentemperatur
|
||||
id: temp_outdoor
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.auriol_ahfl_1_106_h
|
||||
entity_id: sensor.aussenluftfeuchtigkeit
|
||||
id: humid_outdoor
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.luftdruck
|
||||
entity_id: sensor.serverroom_luftdruck
|
||||
id: airpressure
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.serveraum_temperatur
|
||||
entity_id: sensor.serverroom_serveraum_temperatur
|
||||
id: temp_serverroom
|
||||
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.line_power_total
|
||||
entity_id: sensor.netzleistung
|
||||
id: power_total
|
||||
|
||||
- platform: homeassistant
|
||||
@@ -182,12 +197,14 @@ sensor:
|
||||
text_sensor:
|
||||
- platform: homeassistant
|
||||
name: "Sun Rising ESP"
|
||||
#entity_id: sensor.sun_next_rising
|
||||
entity_id: sensor.sun_rising_template
|
||||
id: sun_rising
|
||||
internal: true
|
||||
|
||||
- platform: homeassistant
|
||||
name: "Sun Setting ESP"
|
||||
#entity_id: sensor.sun_next_setting
|
||||
entity_id: sensor.sun_setting_template
|
||||
id: sun_setting
|
||||
internal: true
|
||||
@@ -372,6 +389,7 @@ image:
|
||||
id: c1024_logo
|
||||
type: binary
|
||||
resize: 77x40
|
||||
invert_alpha: True
|
||||
|
||||
spi:
|
||||
clk_pin: 23
|
||||
@@ -423,7 +441,7 @@ display:
|
||||
it.print(375, 130, id(mdi_small), TextAlign::BASELINE_CENTER, ""); // water percent icon
|
||||
it.print(95, 80, id(mdi_small), TextAlign::BASELINE_LEFT, ""); // radioactive icon
|
||||
|
||||
if(outdoor_radiation > 0 && outdoor_radiation < 100) {
|
||||
if(outdoor_radiation >= 0) {
|
||||
it.printf(200, 80, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, "%2.2f", outdoor_radiation);
|
||||
it.print(205, 80, id(sensor_unit), TextAlign::BASELINE_LEFT, "µS/h");
|
||||
}
|
||||
@@ -446,7 +464,7 @@ display:
|
||||
it.print(357, 130, id(sub_sensor_font), TextAlign::BASELINE_RIGHT, " - %");
|
||||
}
|
||||
|
||||
if(air_pressure < 1200 && air_pressure >=800) {
|
||||
if(air_pressure < 1200 && air_pressure >=700) {
|
||||
it.printf(220, 130, id(big_sensor_font), TextAlign::BASELINE_RIGHT, "%4.1f", air_pressure);
|
||||
it.print(225, 130, id(sensor_unit), TextAlign::BASELINE_LEFT, "hPa");
|
||||
}
|
||||
|
@@ -1,12 +1,25 @@
|
||||
substitutions:
|
||||
name: "geigercounter"
|
||||
friendly_name: "Geigerzähler"
|
||||
|
||||
esphome:
|
||||
name: geigercounter
|
||||
platform: ESP32
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.radiation
|
||||
version: "1.0"
|
||||
min_version: 2022.11.0
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: geigercounter.home
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
@@ -26,6 +39,7 @@ api:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
spi:
|
||||
|
31
esphome/home-assistant-voice-09c0e7.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
substitutions:
|
||||
name: home-assistant-voice-09c0e7
|
||||
friendly_name: Home Assistant Voice 09c0e7
|
||||
packages:
|
||||
Nabu Casa.Home Assistant Voice PE: github://esphome/home-assistant-voice-pe/home-assistant-voice.yaml
|
||||
esphome:
|
||||
name: ${name}
|
||||
name_add_mac_suffix: false
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
#use_address: ${name}.home
|
||||
#power_save_mode: high
|
||||
#fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "HA Voice Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
114
esphome/kamera-balkon.yaml
Normal file
@@ -0,0 +1,114 @@
|
||||
substitutions:
|
||||
name: kamera-balkon
|
||||
friendly_name: Kamera Balkon
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.camera
|
||||
version: "1.0"
|
||||
min_version: 2022.1.0
|
||||
|
||||
esp32:
|
||||
board: esp32cam
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
# use_address: cam-balcony.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Cam Balkon Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Initialize I²C
|
||||
i2c:
|
||||
- id: bus_a
|
||||
sda: GPIO13
|
||||
scl: GPIO14
|
||||
scan: true
|
||||
- id: bus_c
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
|
||||
# Camera
|
||||
esp32_camera:
|
||||
name: ${friendly_name}
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_id:
|
||||
bus_c
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: SVGA
|
||||
max_framerate: 24 fps
|
||||
idle_framerate: 0.2 fps
|
||||
jpeg_quality: 30
|
||||
agc_mode: auto
|
||||
agc_gain_ceiling: 4x
|
||||
wb_mode: auto
|
||||
vertical_flip: true
|
||||
horizontal_mirror: true
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 8080
|
||||
mode: stream
|
||||
- port: 8081
|
||||
mode: snapshot
|
||||
|
||||
# Temp/humidity sensors
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signalstärke"
|
||||
update_interval: 60s
|
||||
- platform: sht3xd
|
||||
i2c_id:
|
||||
bus_a
|
||||
temperature:
|
||||
name: "Balkon Temperatur"
|
||||
filters:
|
||||
- offset: -4.4
|
||||
humidity:
|
||||
name: "Balkon Luftfeuchtigkeit"
|
||||
address: 0x44
|
||||
heater_enabled: True
|
||||
update_interval: 60s
|
||||
|
||||
# Flash LED
|
||||
output:
|
||||
- platform: ledc
|
||||
pin: GPIO4
|
||||
id: flash
|
||||
channel: 2
|
||||
|
||||
# Define RGB mode for LED
|
||||
light:
|
||||
- platform: monochromatic
|
||||
id: flashlight
|
||||
name: "Blitzlicht"
|
||||
output: flash
|
92
esphome/kamera-wohnzimmer.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
substitutions:
|
||||
name: kamera-wohnzimmer
|
||||
friendly_name: Kamera Wohnzimmer
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.camera
|
||||
version: "1.0"
|
||||
min_version: 2022.1.0
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: cam-livingroom.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Cam Wohnzimmer Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Camera
|
||||
esp32_camera:
|
||||
name: ${friendly_name}
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_pins:
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: SVGA
|
||||
max_framerate: 24 fps
|
||||
idle_framerate: 0.2 fps
|
||||
jpeg_quality: 30
|
||||
agc_mode: auto
|
||||
agc_gain_ceiling: 4x
|
||||
wb_mode: auto
|
||||
vertical_flip: true
|
||||
horizontal_mirror: true
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 8080
|
||||
mode: stream
|
||||
- port: 8081
|
||||
mode: snapshot
|
||||
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signalstärke"
|
||||
update_interval: 60s
|
||||
|
||||
# Flash LED
|
||||
output:
|
||||
- platform: ledc
|
||||
pin: GPIO4
|
||||
id: flash
|
||||
channel: 2
|
||||
|
||||
# Define RGB mode for LED
|
||||
light:
|
||||
- platform: monochromatic
|
||||
id: flashlight
|
||||
name: "Blitzlicht"
|
||||
output: flash
|
@@ -1,8 +1,16 @@
|
||||
substitutions:
|
||||
name: "livingroom"
|
||||
friendly_name: "Luftqualität Wohnzimmer"
|
||||
|
||||
esphome:
|
||||
name: livingroom
|
||||
platform: ESP32
|
||||
board: nodemcu-32s
|
||||
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.${name}
|
||||
version: "1.1"
|
||||
min_version: 2021.9.0
|
||||
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
@@ -41,10 +49,15 @@ esphome:
|
||||
id: pm25_warn
|
||||
state: "grün"
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: livingroom.home
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
@@ -66,6 +79,7 @@ api:
|
||||
|
||||
# Enable over-the-air updates
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Initialize I²C
|
||||
@@ -186,11 +200,12 @@ sensor:
|
||||
temperature:
|
||||
name: "Wohnzimmer Temperatur"
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- offset: -2.5
|
||||
humidity:
|
||||
name: "Wohnzimmer Luftfeuchtigkeit"
|
||||
accuracy_decimals: 1
|
||||
# SCD30 temp sensor is a bit off
|
||||
temperature_offset: 0 °C
|
||||
ambient_pressure_compensation: 1
|
||||
automatic_self_calibration: True
|
||||
address: 0x61
|
||||
|
360
esphome/m5stack-atom-echo.yaml
Normal file
@@ -0,0 +1,360 @@
|
||||
# Source: https://github.com/esphome/wake-word-voice-assistants/blob/main/m5stack-atom-echo/m5stack-atom-echo.yaml
|
||||
|
||||
substitutions:
|
||||
name: m5stack-atom-echo
|
||||
friendly_name: M5Stack Atom Echo
|
||||
micro_wake_word_model: okay_nabu # alexa, hey_jarvis, hey_mycroft are also supported
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
name_add_mac_suffix: False
|
||||
friendly_name: ${friendly_name}
|
||||
min_version: 2025.2.0
|
||||
|
||||
esp32:
|
||||
board: m5stack-atom
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
id: ota_esphome
|
||||
password: !secret ota
|
||||
|
||||
wifi:
|
||||
on_connect:
|
||||
- delay: 5s # Gives time for improv results to be transmitted
|
||||
ssid: Voltage-legacy
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Raspiaudio Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
logger:
|
||||
|
||||
captive_portal:
|
||||
|
||||
button:
|
||||
- platform: factory_reset
|
||||
id: factory_reset_btn
|
||||
name: Factory reset
|
||||
|
||||
i2s_audio:
|
||||
- id: i2s_audio_bus
|
||||
i2s_lrclk_pin: GPIO33
|
||||
i2s_bclk_pin: GPIO19
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: echo_microphone
|
||||
i2s_din_pin: GPIO23
|
||||
adc_type: external
|
||||
pdm: true
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: echo_speaker
|
||||
i2s_dout_pin: GPIO22
|
||||
dac_type: external
|
||||
bits_per_sample: 32bit
|
||||
channel: right
|
||||
buffer_duration: 60ms
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
id: echo_media_player
|
||||
announcement_pipeline:
|
||||
speaker: echo_speaker
|
||||
format: WAV
|
||||
codec_support_enabled: false
|
||||
buffer_size: 6000
|
||||
volume_min: 0.4
|
||||
files:
|
||||
- id: timer_finished_wave_file
|
||||
file: https://github.com/esphome/wake-word-voice-assistants/raw/main/sounds/timer_finished.wav
|
||||
on_announcement:
|
||||
- if:
|
||||
condition:
|
||||
- microphone.is_capturing:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- micro_wake_word.stop:
|
||||
else:
|
||||
- voice_assistant.stop:
|
||||
- script.execute: reset_led
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_idle:
|
||||
- script.execute: start_wake_word
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
microphone: echo_microphone
|
||||
media_player: echo_media_player
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
volume_multiplier: 2.0
|
||||
on_listening:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Slow Pulse"
|
||||
on_stt_vad_end:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
effect: "Fast Pulse"
|
||||
on_tts_start:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
blue: 100%
|
||||
red: 0%
|
||||
green: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
on_end:
|
||||
- delay: 100ms
|
||||
- script.execute: start_wake_word
|
||||
on_error:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 0%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: none
|
||||
- delay: 2s
|
||||
- script.execute: reset_led
|
||||
on_client_connected:
|
||||
- delay: 2s # Give the api server time to settle
|
||||
- script.execute: start_wake_word
|
||||
on_client_disconnected:
|
||||
- voice_assistant.stop:
|
||||
- micro_wake_word.stop:
|
||||
on_timer_finished:
|
||||
- voice_assistant.stop:
|
||||
- micro_wake_word.stop:
|
||||
- wait_until:
|
||||
not:
|
||||
microphone.is_capturing:
|
||||
- switch.turn_on: timer_ringing
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
effect: "Fast Pulse"
|
||||
- wait_until:
|
||||
- switch.is_off: timer_ringing
|
||||
- light.turn_off: led
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
binary_sensor:
|
||||
# button does the following:
|
||||
# short click - stop a timer
|
||||
# if no timer then restart either microwakeword or voice assistant continuous
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO39
|
||||
inverted: true
|
||||
name: Button
|
||||
disabled_by_default: true
|
||||
entity_category: diagnostic
|
||||
id: echo_button
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at least 50ms
|
||||
- OFF for at least 50ms
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_on: timer_ringing
|
||||
then:
|
||||
- switch.turn_off: timer_ringing
|
||||
else:
|
||||
- script.execute: start_wake_word
|
||||
- timing:
|
||||
- ON for at least 10s
|
||||
then:
|
||||
- button.press: factory_reset_btn
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led
|
||||
name: None
|
||||
disabled_by_default: true
|
||||
entity_category: config
|
||||
pin: GPIO27
|
||||
default_transition_length: 0s
|
||||
chipset: SK6812
|
||||
num_leds: 1
|
||||
rgb_order: grb
|
||||
effects:
|
||||
- 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%
|
||||
|
||||
script:
|
||||
- id: reset_led
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state == "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 100%
|
||||
green: 89%
|
||||
blue: 71%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: return id(wake_word_engine_location).state != "On device";
|
||||
- switch.is_on: use_listen_light
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 100%
|
||||
brightness: 60%
|
||||
effect: none
|
||||
else:
|
||||
- light.turn_off: led
|
||||
- id: start_wake_word
|
||||
then:
|
||||
- wait_until:
|
||||
and:
|
||||
- media_player.is_idle:
|
||||
- speaker.is_stopped:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return id(wake_word_engine_location).state == "On device";
|
||||
then:
|
||||
- voice_assistant.stop
|
||||
- micro_wake_word.stop:
|
||||
- delay: 1s
|
||||
- script.execute: reset_led
|
||||
- script.wait: reset_led
|
||||
- micro_wake_word.start:
|
||||
else:
|
||||
- if:
|
||||
condition: voice_assistant.is_running
|
||||
then:
|
||||
- voice_assistant.stop:
|
||||
- script.execute: reset_led
|
||||
- voice_assistant.start_continuous:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: Use listen light
|
||||
id: use_listen_light
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
entity_category: config
|
||||
on_turn_on:
|
||||
- script.execute: reset_led
|
||||
on_turn_off:
|
||||
- script.execute: reset_led
|
||||
- platform: template
|
||||
id: timer_ringing
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
on_turn_off:
|
||||
# Turn off the repeat mode and disable the pause between playlist items
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_OFF)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 0);
|
||||
# Stop playing the alarm
|
||||
- media_player.stop:
|
||||
announcement: true
|
||||
on_turn_on:
|
||||
# Turn on the repeat mode and pause for 1000 ms between playlist items/repeats
|
||||
- lambda: |-
|
||||
id(echo_media_player)
|
||||
->make_call()
|
||||
.set_command(media_player::MediaPlayerCommand::MEDIA_PLAYER_COMMAND_REPEAT_ONE)
|
||||
.set_announcement(true)
|
||||
.perform();
|
||||
id(echo_media_player)->set_playlist_delay_ms(speaker::AudioPipelineType::ANNOUNCEMENT, 1000);
|
||||
- media_player.speaker.play_on_device_media_file:
|
||||
media_file: timer_finished_wave_file
|
||||
announcement: true
|
||||
- delay: 15min
|
||||
- switch.turn_off: timer_ringing
|
||||
|
||||
select:
|
||||
- platform: template
|
||||
entity_category: config
|
||||
name: Wake word engine location
|
||||
id: wake_word_engine_location
|
||||
optimistic: true
|
||||
restore_value: true
|
||||
options:
|
||||
- In Home Assistant
|
||||
- On device
|
||||
initial_option: On device
|
||||
on_value:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "In Home Assistant";
|
||||
then:
|
||||
- micro_wake_word.stop
|
||||
- delay: 500ms
|
||||
- lambda: id(va).set_use_wake_word(true);
|
||||
- voice_assistant.start_continuous:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return x == "On device";
|
||||
then:
|
||||
- lambda: id(va).set_use_wake_word(false);
|
||||
- voice_assistant.stop
|
||||
- delay: 500ms
|
||||
- micro_wake_word.start
|
||||
|
||||
micro_wake_word:
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
vad:
|
||||
models:
|
||||
- model: ${micro_wake_word_model}
|
@@ -1,21 +1,14 @@
|
||||
# Source: https://github.com/RASPIAUDIO/esphomeLuxe/blob/main/luxe_microWW.yaml
|
||||
|
||||
substitutions:
|
||||
name: "raspiaudio-muse-luxe"
|
||||
packages:
|
||||
raspiaudio.muse-luxe: github://esphome/media-players/raspiaudio-muse-luxe.yaml@main
|
||||
esphome:
|
||||
name: ${name}
|
||||
name_add_mac_suffix: false
|
||||
|
||||
wifi:
|
||||
ssid: Voltage-legacy
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: raspiaudio-muse-luxe.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Raspiaudio Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
friendly_name: "RaspiAudio Muse Luxe"
|
||||
#States
|
||||
P_starting: "0"
|
||||
P_waiting: "1"
|
||||
P_playing: "2"
|
||||
P_listening: "3"
|
||||
P_answering: "4"
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
@@ -23,10 +16,472 @@ api:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
password: !secret ota
|
||||
- platform: esphome
|
||||
id: ota_esphome
|
||||
password: !secret ota
|
||||
|
||||
external_components:
|
||||
- source: github://RASPIAUDIO/esphomeLuxe@main
|
||||
# - source:
|
||||
# type: local
|
||||
# path: components
|
||||
components: [es8388]
|
||||
refresh: 0s
|
||||
|
||||
wifi:
|
||||
ssid: Voltage-legacy
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Raspiaudio Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
min_version: 2025.2.0
|
||||
name_add_mac_suffix: false
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
board_build.arduino.memory_type: qio_opi
|
||||
on_boot:
|
||||
priority: -100.0
|
||||
then:
|
||||
- lambda: id(phase) = 0;
|
||||
- script.execute: update_led
|
||||
|
||||
esp32:
|
||||
board: esp-wrover-kit
|
||||
flash_size: 4MB
|
||||
framework:
|
||||
type: esp-idf
|
||||
version: recommended
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
||||
|
||||
micro_wake_word:
|
||||
id: mww
|
||||
models:
|
||||
- model: https://github.com/kahrendt/microWakeWord/releases/download/okay_nabu_20241226.3/okay_nabu.json
|
||||
# - model: hey_jarvis
|
||||
# - model: hey_mycroft
|
||||
# - model: alexa
|
||||
# vad:
|
||||
microphone: luxe_mic
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
|
||||
captive_portal:
|
||||
|
||||
improv_serial:
|
||||
|
||||
##########
|
||||
# Hardware Configuration
|
||||
es8388:
|
||||
id: my_es8388
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
#######
|
||||
# Buses Configuration
|
||||
i2c:
|
||||
sda: GPIO18
|
||||
scl: GPIO23
|
||||
|
||||
#####################
|
||||
# Internal Components
|
||||
output:
|
||||
- platform: gpio
|
||||
id: dac_mute
|
||||
pin:
|
||||
number: GPIO21
|
||||
inverted: true
|
||||
mode:
|
||||
output: true
|
||||
|
||||
globals:
|
||||
- id: Vol
|
||||
type: float
|
||||
initial_value: '0.6'
|
||||
- id: phase
|
||||
type: int
|
||||
initial_value: '0'
|
||||
- id: mute
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
- id: muteH
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
|
||||
interval:
|
||||
- interval: 0.1sec
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- speaker.is_stopped:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- not:
|
||||
- lambda: 'return(id(muteH));'
|
||||
then:
|
||||
- output.turn_on: dac_mute
|
||||
- lambda: id(muteH) = true;
|
||||
- logger.log: "====> hardware mute"
|
||||
|
||||
- if:
|
||||
condition:
|
||||
- speaker.is_playing:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: 'return(id(muteH));'
|
||||
then:
|
||||
- output.turn_off: dac_mute
|
||||
- lambda: id(muteH) = false;
|
||||
- logger.log: "====> hardware unmute"
|
||||
|
||||
sensor:
|
||||
# WiFi signal strength
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signalstärke"
|
||||
update_interval: 60s
|
||||
- platform: adc
|
||||
pin: GPIO33
|
||||
name: Battery voltage
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
accuracy_decimals: 2
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
update_interval: 15s
|
||||
attenuation: auto
|
||||
filters:
|
||||
- multiply: 2 # https://forum.raspiaudio.com/t/esp-muse-luxe-bluetooth-speaker/294/12
|
||||
- exponential_moving_average:
|
||||
alpha: 0.2
|
||||
send_every: 2
|
||||
- delta: 0.002
|
||||
on_value:
|
||||
then:
|
||||
- sensor.template.publish:
|
||||
id: battery_percent
|
||||
state: !lambda "return x;"
|
||||
|
||||
- platform: template
|
||||
name: Battery
|
||||
id: battery_percent
|
||||
device_class: battery
|
||||
unit_of_measurement: "%"
|
||||
accuracy_decimals: 0
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
update_interval: 15s
|
||||
filters:
|
||||
- calibrate_polynomial:
|
||||
degree: 3
|
||||
datapoints:
|
||||
- 4.58 -> 100.0
|
||||
- 4.5 -> 97.1
|
||||
- 4.47 -> 94.2
|
||||
- 4.44 -> 88.4
|
||||
- 4.42 -> 82.7
|
||||
- 4.41 -> 76.9
|
||||
- 4.41 -> 71.1
|
||||
- 4.37 -> 65.3
|
||||
- 4.35 -> 59.5
|
||||
- 4.31 -> 53.8
|
||||
- 4.28 -> 48.0
|
||||
- 4.26 -> 42.2
|
||||
- 4.23 -> 36.4
|
||||
- 4.21 -> 30.6
|
||||
- 4.19 -> 24.9
|
||||
- 4.16 -> 19.1
|
||||
- 4.1 -> 13.3
|
||||
- 4.07 -> 10.4
|
||||
- 4.03 -> 7.5
|
||||
- 3.97 -> 4.6
|
||||
- 3.82 -> 1.7
|
||||
- 3.27 -> 0.0
|
||||
- lambda: return clamp(x, 0.0f, 100.0f);
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO19
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
name: Volume Up
|
||||
on_click:
|
||||
- lambda: |-
|
||||
id(Vol) += 0.05;
|
||||
if(id(Vol) > 1) id(Vol) = 1;
|
||||
- media_player.volume_set:
|
||||
id: luxe_media_player
|
||||
volume: !lambda return id(Vol);
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO32
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
name: Volume Down
|
||||
on_click:
|
||||
- lambda: |-
|
||||
id(Vol) -= 0.05;
|
||||
if(id(Vol) < 0) id(Vol) = 0;
|
||||
- media_player.volume_set:
|
||||
id: luxe_media_player
|
||||
volume: !lambda return id(Vol);
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO12
|
||||
inverted: true
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
name: Mute
|
||||
on_click:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: 'return(id(mute));'
|
||||
then:
|
||||
- script.execute: mute_off
|
||||
- lambda: id(mute) = false;
|
||||
else:
|
||||
- script.execute: mute_on
|
||||
- lambda: id(mute) = true;
|
||||
on_double_click:
|
||||
- if:
|
||||
condition:
|
||||
- lambda: 'return(id(phase) == 2);'
|
||||
then:
|
||||
- media_player.stop:
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
name: None
|
||||
id: top_led
|
||||
pin: GPIO22
|
||||
chipset: WS2812
|
||||
num_leds: 1
|
||||
rgb_order: grb
|
||||
# rmt_channel: 0
|
||||
default_transition_length: 0s
|
||||
gamma_correct: 2.8
|
||||
effects:
|
||||
- pulse:
|
||||
name: pulse
|
||||
transition_length: 250ms
|
||||
update_interval: 250ms
|
||||
- pulse:
|
||||
name: slow_pulse
|
||||
transition_length: 1s
|
||||
update_interval: 2s
|
||||
|
||||
i2s_audio:
|
||||
i2s_lrclk_pin: GPIO25
|
||||
i2s_bclk_pin: GPIO5
|
||||
i2s_mclk_pin: GPIO0
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: luxe_mic
|
||||
sample_rate: 16000
|
||||
i2s_din_pin: GPIO35
|
||||
bits_per_sample: 16bit
|
||||
channel: stereo
|
||||
adc_type: external
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: luxe_speaker
|
||||
i2s_dout_pin: GPIO26
|
||||
dac_type: external
|
||||
sample_rate: 48000
|
||||
bits_per_sample: 16bit
|
||||
channel: stereo
|
||||
buffer_duration: 100ms
|
||||
|
||||
media_player:
|
||||
- platform: speaker
|
||||
name: None
|
||||
id: luxe_media_player
|
||||
# volume_min: 0.5
|
||||
# volume_max: 0.8
|
||||
announcement_pipeline:
|
||||
speaker: luxe_speaker
|
||||
format: FLAC
|
||||
sample_rate: 48000
|
||||
num_channels: 2
|
||||
files:
|
||||
- id: little_sound
|
||||
file: https://github.com/esphome/home-assistant-voice-pe/raw/dev/sounds/timer_finished.flac
|
||||
on_announcement:
|
||||
- micro_wake_word.stop:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return(id(phase) != 2);'
|
||||
then:
|
||||
- lambda: |-
|
||||
if(id(phase) == 1) id(phase) = 2;
|
||||
- script.execute: mute_off
|
||||
- script.execute: update_led
|
||||
|
||||
on_idle:
|
||||
- wait_until:
|
||||
and:
|
||||
- not:
|
||||
media_player.is_announcing:
|
||||
- not:
|
||||
voice_assistant.is_running:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return((id(phase) == 4) || (id(phase) == 2));'
|
||||
then:
|
||||
- lambda: |-
|
||||
id(phase) = 1;
|
||||
- micro_wake_word.start:
|
||||
- script.execute: update_led
|
||||
|
||||
voice_assistant:
|
||||
id: va
|
||||
microphone: luxe_mic
|
||||
media_player: luxe_media_player
|
||||
use_wake_word: false
|
||||
noise_suppression_level: 2
|
||||
auto_gain: 31dBFS
|
||||
volume_multiplier: 2.0
|
||||
|
||||
on_listening:
|
||||
- logger.log: "listening 3 => phase"
|
||||
- micro_wake_word.stop:
|
||||
- lambda: |-
|
||||
id(phase) = 3;
|
||||
- script.execute: update_led
|
||||
|
||||
on_stt_end:
|
||||
- media_player.play_media: !lambda return x;
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
blue: 60%
|
||||
red: 20%
|
||||
green: 20%
|
||||
effect: pulse
|
||||
|
||||
|
||||
on_tts_start:
|
||||
- logger.log: "answering 4 => phase"
|
||||
- lambda: |-
|
||||
id(phase) = 4;
|
||||
- script.execute: update_led
|
||||
|
||||
on_error:
|
||||
- logger.log: "ERROR!!!!!!!!!!!!!!!!"
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
blue: 0%
|
||||
red: 100%
|
||||
green: 0%
|
||||
effect: pulse
|
||||
- delay: 3s
|
||||
- lambda: id(phase) = 1;
|
||||
- script.execute: update_led
|
||||
|
||||
|
||||
#########
|
||||
# Scripts
|
||||
|
||||
script:
|
||||
- id: update_led
|
||||
then:
|
||||
- logger.log: "==>>>update_led"
|
||||
- lambda: |-
|
||||
if(id(phase) == 0)id(start).execute();
|
||||
if(id(phase) == 1)id(waiting).execute();
|
||||
if(id(phase) == 2)id(external_player).execute();
|
||||
if(id(phase) == 3)id(listening).execute();
|
||||
if(id(phase) == 4)id(answering).execute();
|
||||
|
||||
- id: start
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
effect: slow_pulse
|
||||
red: 80%
|
||||
green: 0%
|
||||
blue: 80%
|
||||
- delay: 5sec
|
||||
- lambda: id(my_es8388).setup();
|
||||
- output.turn_off: dac_mute
|
||||
- lambda: id(phase) = 1;
|
||||
- media_player.speaker.play_on_device_media_file:
|
||||
media_file: little_sound
|
||||
announcement: true
|
||||
- script.execute: update_led
|
||||
|
||||
- id: waiting
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
effect: pulse
|
||||
red: 0%
|
||||
green: 0%
|
||||
blue: 100%
|
||||
brightness: 100%
|
||||
- voice_assistant.stop:
|
||||
- micro_wake_word.start:
|
||||
|
||||
|
||||
- id: listening
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
effect: pulse
|
||||
red: 0%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
|
||||
- id: answering
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
effect: none
|
||||
red: 100%
|
||||
green: 100%
|
||||
blue: 0%
|
||||
brightness: 100%
|
||||
|
||||
- id: external_player
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: top_led
|
||||
effect: none
|
||||
red: 80%
|
||||
green: 40%
|
||||
blue: 0%
|
||||
|
||||
- id: mute_on
|
||||
then:
|
||||
- media_player.volume_set:
|
||||
volume: '0'
|
||||
- lambda: id(mute) = true;
|
||||
|
||||
- id: mute_off
|
||||
then:
|
||||
- media_player.volume_set:
|
||||
volume: !lambda return(id(Vol));
|
||||
- lambda: id(mute) = false;
|
||||
|
506
esphome/riden-labornetzteil-18a.yaml
Normal file
@@ -0,0 +1,506 @@
|
||||
substitutions:
|
||||
name: "riden-labornetzteil-18a"
|
||||
friendly_name: "Riden Labornetzteil 18A"
|
||||
# Change this model to fit your particular one.
|
||||
# You can find it in Home Assistant as the device diagnostic "Model Name".
|
||||
model: "RD6018"
|
||||
device_name: "rd6018-controller"
|
||||
device_friendly_name: "Riden RD6018"
|
||||
device_description: "Monitor and control a RD6018 PSU via WiFi"
|
||||
time_timezone: "Europe/Berlin"
|
||||
|
||||
# Model specific settings (Don't change these!)
|
||||
RD6006_voltage_maximum: "60"
|
||||
RD6006_voltage_accuracy: "2"
|
||||
RD6006_voltage_multiplier: "0.01"
|
||||
RD6006_current_maximum: "6"
|
||||
RD6006_current_accuracy: "3"
|
||||
RD6006_current_multiplier: "0.001"
|
||||
|
||||
RD6006P_voltage_maximum: "60"
|
||||
RD6006P_voltage_accuracy: "3"
|
||||
RD6006P_voltage_multiplier: "0.001"
|
||||
RD6006P_current_maximum: "6"
|
||||
RD6006P_current_accuracy: "4"
|
||||
RD6006P_current_multiplier: "0.0001"
|
||||
|
||||
RD6012_voltage_maximum: "60"
|
||||
RD6012_voltage_accuracy: "2"
|
||||
RD6012_voltage_multiplier: "0.01"
|
||||
RD6012_current_maximum: "12"
|
||||
RD6012_current_accuracy: "2"
|
||||
RD6012_current_multiplier: "0.01"
|
||||
|
||||
RD6018_voltage_maximum: "60"
|
||||
RD6018_voltage_accuracy: "2"
|
||||
RD6018_voltage_multiplier: "0.01"
|
||||
RD6018_current_maximum: "18"
|
||||
RD6018_current_accuracy: "2"
|
||||
RD6018_current_multiplier: "0.01"
|
||||
|
||||
esphome:
|
||||
name: $device_name
|
||||
friendly_name: $device_friendly_name
|
||||
comment: $device_description
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: "wildekek.rd6006-controller"
|
||||
version: "1.4.1"
|
||||
|
||||
esp8266:
|
||||
board: esp12e
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: riden-labornetzteil-18a.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "RD6018 Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: INFO
|
||||
# Disable logging via UART, since we're using this for modbus communication
|
||||
baud_rate: 0
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Enable status LED
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO2
|
||||
inverted: true
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: GPIO1
|
||||
rx_pin: GPIO3
|
||||
baud_rate: 115200
|
||||
data_bits: 8
|
||||
stop_bits: 1
|
||||
parity: none
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
|
||||
modbus_controller:
|
||||
- id: powersupply
|
||||
## This address should be set to the "Address" value in the config menu
|
||||
address: 0x01
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
update_interval: 5s
|
||||
|
||||
time:
|
||||
# Get the time from HA, so we can use it for uptime
|
||||
- platform: homeassistant
|
||||
id: time_homeassistant
|
||||
timezone: "${time_timezone}"
|
||||
on_time_sync:
|
||||
- logger.log: Time has been set and is valid!
|
||||
- component.update: sensor_uptime_timestamp
|
||||
- number.set:
|
||||
id: date_year
|
||||
value: !lambda return id(time_homeassistant).now().year;
|
||||
- number.set:
|
||||
id: date_month
|
||||
value: !lambda return id(time_homeassistant).now().month;
|
||||
- number.set:
|
||||
id: date_day
|
||||
value: !lambda return id(time_homeassistant).now().day_of_month;
|
||||
- number.set:
|
||||
id: date_hour
|
||||
value: !lambda return id(time_homeassistant).now().hour;
|
||||
- number.set:
|
||||
id: date_minute
|
||||
value: !lambda return id(time_homeassistant).now().minute;
|
||||
- number.set:
|
||||
id: date_second
|
||||
value: !lambda return id(time_homeassistant).now().second;
|
||||
|
||||
sensor:
|
||||
- platform: modbus_controller
|
||||
id: model_number
|
||||
name: "Model Number"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
modbus_controller_id: powersupply
|
||||
address: 0
|
||||
skip_updates: 10
|
||||
unit_of_measurement: ""
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 0
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(model_name).publish_state(value_accuracy_to_string(x, 0));
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Serial Number"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
modbus_controller_id: powersupply
|
||||
address: 1
|
||||
skip_updates: 10
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 0
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 3
|
||||
name: "Firmware version"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
unit_of_measurement: ""
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 10
|
||||
name: "Output voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 11
|
||||
name: "Output current"
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
unit_of_measurement: "A"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_current_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_current_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 12
|
||||
name: "Output Power"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
unit_of_measurement: "W"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 33
|
||||
name: "Battery voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 38
|
||||
name: "Battery charge"
|
||||
device_class: "energy_storage"
|
||||
state_class: measurement
|
||||
unit_of_measurement: "Ah"
|
||||
icon: "mdi:battery-60"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 40
|
||||
name: "Battery energy"
|
||||
device_class: "energy_storage"
|
||||
state_class: measurement
|
||||
unit_of_measurement: "Wh"
|
||||
icon: "mdi:battery-60"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 14
|
||||
name: "Input voltage"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Temperature"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
modbus_controller_id: powersupply
|
||||
register_type: holding
|
||||
address: 4
|
||||
value_type: S_DWORD
|
||||
unit_of_measurement: "°C"
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Temperature external"
|
||||
state_class: measurement
|
||||
modbus_controller_id: powersupply
|
||||
register_type: holding
|
||||
address: 34
|
||||
value_type: S_DWORD
|
||||
device_class: temperature
|
||||
unit_of_measurement: "°C"
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "Wi-Fi Signal"
|
||||
disabled_by_default: True
|
||||
update_interval: 60s
|
||||
|
||||
# Uptime is used internally only
|
||||
- platform: uptime
|
||||
id: sensor_uptime
|
||||
# This sensor is an alternative for the uptime sensor, which only sends the
|
||||
# startup timestamp of the device to home assistant once
|
||||
- platform: template
|
||||
id: sensor_uptime_timestamp
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
device_class: "timestamp"
|
||||
accuracy_decimals: 0
|
||||
update_interval: never
|
||||
lambda: |-
|
||||
static float timestamp = (
|
||||
id(time_homeassistant).utcnow().timestamp - id(sensor_uptime).state
|
||||
);
|
||||
return timestamp;
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
disabled_by_default: True
|
||||
ssid:
|
||||
name: "Wi-Fi SSID"
|
||||
disabled_by_default: True
|
||||
bssid:
|
||||
name: "Wi-Fi BSSID"
|
||||
disabled_by_default: True
|
||||
- platform: template
|
||||
id: model_name
|
||||
name: "Model Name"
|
||||
entity_category: diagnostic
|
||||
# Updated by model number
|
||||
update_interval: never
|
||||
filters:
|
||||
- map:
|
||||
- 60062 -> RD6006
|
||||
- 60065 -> RD6006P
|
||||
- 60121 -> RD6012
|
||||
- 60181 -> RD6018
|
||||
|
||||
binary_sensor:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Keypad lock"
|
||||
entity_category: diagnostic
|
||||
device_class: lock
|
||||
address: 15
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
filters:
|
||||
- invert:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 32
|
||||
name: "Battery mode"
|
||||
device_class: connectivity
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Voltage Protection"
|
||||
device_class: problem
|
||||
address: 16
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Current Protection"
|
||||
device_class: problem
|
||||
address: 16
|
||||
register_type: holding
|
||||
bitmask: 0x2
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Constant Voltage"
|
||||
address: 17
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
filters:
|
||||
- invert:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Constant Current"
|
||||
address: 17
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
|
||||
number:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Backlight"
|
||||
icon: "mdi:lightbulb"
|
||||
entity_category: config
|
||||
address: 72
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: 5
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output voltage"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
entity_category: config
|
||||
address: 8
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_voltage_maximum}
|
||||
step: ${${model}_voltage_multiplier}
|
||||
lambda: !lambda return x * ${${model}_voltage_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_voltage_multiplier});
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output current"
|
||||
device_class: current
|
||||
unit_of_measurement: "A"
|
||||
entity_category: config
|
||||
address: 9
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_current_maximum}
|
||||
step: ${${model}_current_multiplier}
|
||||
lambda: !lambda return x * ${${model}_current_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_current_multiplier});
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Voltage Protection"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
address: 82
|
||||
entity_category: config
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_voltage_maximum}
|
||||
step: ${${model}_voltage_multiplier}
|
||||
lambda: !lambda return x * ${${model}_voltage_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_voltage_multiplier});
|
||||
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Current Protection"
|
||||
device_class: current
|
||||
unit_of_measurement: "A"
|
||||
address: 83
|
||||
entity_category: config
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_current_maximum}
|
||||
step: ${${model}_current_multiplier}
|
||||
lambda: !lambda return x * ${${model}_current_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_current_multiplier});
|
||||
|
||||
# Date components are kept internal
|
||||
- platform: modbus_controller
|
||||
id: date_year
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 48
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_month
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 49
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_day
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 50
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_hour
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 51
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_minute
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 52
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_second
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 53
|
||||
value_type: U_WORD
|
||||
|
||||
switch:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output"
|
||||
address: 18
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
entity_category: config
|
||||
|
||||
button:
|
||||
- platform: safe_mode
|
||||
name: "Safe mode"
|
506
esphome/riden-labornetzteil-6a.yaml
Normal file
@@ -0,0 +1,506 @@
|
||||
substitutions:
|
||||
name: "riden-labornetzteil-6a"
|
||||
friendly_name: "Riden Labornetzteil 6A"
|
||||
# Change this model to fit your particular one.
|
||||
# You can find it in Home Assistant as the device diagnostic "Model Name".
|
||||
model: "RD6006"
|
||||
device_name: "rd6006-controller"
|
||||
device_friendly_name: "Riden RD6006"
|
||||
device_description: "Monitor and control a RD6006 PSU via WiFi"
|
||||
time_timezone: "Europe/Berlin"
|
||||
|
||||
# Model specific settings (Don't change these!)
|
||||
RD6006_voltage_maximum: "60"
|
||||
RD6006_voltage_accuracy: "2"
|
||||
RD6006_voltage_multiplier: "0.01"
|
||||
RD6006_current_maximum: "6"
|
||||
RD6006_current_accuracy: "3"
|
||||
RD6006_current_multiplier: "0.001"
|
||||
|
||||
RD6006P_voltage_maximum: "60"
|
||||
RD6006P_voltage_accuracy: "3"
|
||||
RD6006P_voltage_multiplier: "0.001"
|
||||
RD6006P_current_maximum: "6"
|
||||
RD6006P_current_accuracy: "4"
|
||||
RD6006P_current_multiplier: "0.0001"
|
||||
|
||||
RD6012_voltage_maximum: "60"
|
||||
RD6012_voltage_accuracy: "2"
|
||||
RD6012_voltage_multiplier: "0.01"
|
||||
RD6012_current_maximum: "12"
|
||||
RD6012_current_accuracy: "2"
|
||||
RD6012_current_multiplier: "0.01"
|
||||
|
||||
RD6018_voltage_maximum: "60"
|
||||
RD6018_voltage_accuracy: "2"
|
||||
RD6018_voltage_multiplier: "0.01"
|
||||
RD6018_current_maximum: "18"
|
||||
RD6018_current_accuracy: "2"
|
||||
RD6018_current_multiplier: "0.01"
|
||||
|
||||
esphome:
|
||||
name: $device_name
|
||||
friendly_name: $device_friendly_name
|
||||
comment: $device_description
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: "wildekek.rd6006-controller"
|
||||
version: "1.4.1"
|
||||
|
||||
esp8266:
|
||||
board: esp12e
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: riden-labornetzteil-6a.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "RD6006 Fallback Hotspot"
|
||||
password: !secret fallback_psk
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: INFO
|
||||
# Disable logging via UART, since we're using this for modbus communication
|
||||
baud_rate: 0
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Enable status LED
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO2
|
||||
inverted: true
|
||||
|
||||
uart:
|
||||
id: mod_bus
|
||||
tx_pin: GPIO1
|
||||
rx_pin: GPIO3
|
||||
baud_rate: 115200
|
||||
data_bits: 8
|
||||
stop_bits: 1
|
||||
parity: none
|
||||
|
||||
modbus:
|
||||
id: modbus1
|
||||
|
||||
modbus_controller:
|
||||
- id: powersupply
|
||||
## This address should be set to the "Address" value in the config menu
|
||||
address: 0x01
|
||||
modbus_id: modbus1
|
||||
setup_priority: -10
|
||||
update_interval: 5s
|
||||
|
||||
time:
|
||||
# Get the time from HA, so we can use it for uptime
|
||||
- platform: homeassistant
|
||||
id: time_homeassistant
|
||||
timezone: "${time_timezone}"
|
||||
on_time_sync:
|
||||
- logger.log: Time has been set and is valid!
|
||||
- component.update: sensor_uptime_timestamp
|
||||
- number.set:
|
||||
id: date_year
|
||||
value: !lambda return id(time_homeassistant).now().year;
|
||||
- number.set:
|
||||
id: date_month
|
||||
value: !lambda return id(time_homeassistant).now().month;
|
||||
- number.set:
|
||||
id: date_day
|
||||
value: !lambda return id(time_homeassistant).now().day_of_month;
|
||||
- number.set:
|
||||
id: date_hour
|
||||
value: !lambda return id(time_homeassistant).now().hour;
|
||||
- number.set:
|
||||
id: date_minute
|
||||
value: !lambda return id(time_homeassistant).now().minute;
|
||||
- number.set:
|
||||
id: date_second
|
||||
value: !lambda return id(time_homeassistant).now().second;
|
||||
|
||||
sensor:
|
||||
- platform: modbus_controller
|
||||
id: model_number
|
||||
name: "Model Number"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
modbus_controller_id: powersupply
|
||||
address: 0
|
||||
skip_updates: 10
|
||||
unit_of_measurement: ""
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 0
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(model_name).publish_state(value_accuracy_to_string(x, 0));
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Serial Number"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
modbus_controller_id: powersupply
|
||||
address: 1
|
||||
skip_updates: 10
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 0
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 3
|
||||
name: "Firmware version"
|
||||
entity_category: diagnostic
|
||||
disabled_by_default: True
|
||||
unit_of_measurement: ""
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 10
|
||||
name: "Output voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 11
|
||||
name: "Output current"
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
unit_of_measurement: "A"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_current_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_current_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 12
|
||||
name: "Output Power"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
unit_of_measurement: "W"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- multiply: 0.01
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 33
|
||||
name: "Battery voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 38
|
||||
name: "Battery charge"
|
||||
device_class: "energy_storage"
|
||||
state_class: measurement
|
||||
unit_of_measurement: "Ah"
|
||||
icon: "mdi:battery-60"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 40
|
||||
name: "Battery energy"
|
||||
device_class: "energy_storage"
|
||||
state_class: measurement
|
||||
unit_of_measurement: "Wh"
|
||||
icon: "mdi:battery-60"
|
||||
register_type: holding
|
||||
value_type: U_DWORD
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 14
|
||||
name: "Input voltage"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
register_type: holding
|
||||
value_type: U_WORD
|
||||
accuracy_decimals: ${${model}_voltage_accuracy}
|
||||
filters:
|
||||
- multiply: ${${model}_voltage_multiplier}
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Temperature"
|
||||
device_class: temperature
|
||||
state_class: measurement
|
||||
modbus_controller_id: powersupply
|
||||
register_type: holding
|
||||
address: 4
|
||||
value_type: S_DWORD
|
||||
unit_of_measurement: "°C"
|
||||
|
||||
- platform: modbus_controller
|
||||
name: "Temperature external"
|
||||
state_class: measurement
|
||||
modbus_controller_id: powersupply
|
||||
register_type: holding
|
||||
address: 34
|
||||
value_type: S_DWORD
|
||||
device_class: temperature
|
||||
unit_of_measurement: "°C"
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "Wi-Fi Signal"
|
||||
disabled_by_default: True
|
||||
update_interval: 60s
|
||||
|
||||
# Uptime is used internally only
|
||||
- platform: uptime
|
||||
id: sensor_uptime
|
||||
# This sensor is an alternative for the uptime sensor, which only sends the
|
||||
# startup timestamp of the device to home assistant once
|
||||
- platform: template
|
||||
id: sensor_uptime_timestamp
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
device_class: "timestamp"
|
||||
accuracy_decimals: 0
|
||||
update_interval: never
|
||||
lambda: |-
|
||||
static float timestamp = (
|
||||
id(time_homeassistant).utcnow().timestamp - id(sensor_uptime).state
|
||||
);
|
||||
return timestamp;
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
disabled_by_default: True
|
||||
ssid:
|
||||
name: "Wi-Fi SSID"
|
||||
disabled_by_default: True
|
||||
bssid:
|
||||
name: "Wi-Fi BSSID"
|
||||
disabled_by_default: True
|
||||
- platform: template
|
||||
id: model_name
|
||||
name: "Model Name"
|
||||
entity_category: diagnostic
|
||||
# Updated by model number
|
||||
update_interval: never
|
||||
filters:
|
||||
- map:
|
||||
- 60062 -> RD6006
|
||||
- 60065 -> RD6006P
|
||||
- 60121 -> RD6012
|
||||
- 60181 -> RD6018
|
||||
|
||||
binary_sensor:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Keypad lock"
|
||||
entity_category: diagnostic
|
||||
device_class: lock
|
||||
address: 15
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
filters:
|
||||
- invert:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
address: 32
|
||||
name: "Battery mode"
|
||||
device_class: connectivity
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Voltage Protection"
|
||||
device_class: problem
|
||||
address: 16
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Current Protection"
|
||||
device_class: problem
|
||||
address: 16
|
||||
register_type: holding
|
||||
bitmask: 0x2
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Constant Voltage"
|
||||
address: 17
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
filters:
|
||||
- invert:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Constant Current"
|
||||
address: 17
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
|
||||
number:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Backlight"
|
||||
icon: "mdi:lightbulb"
|
||||
entity_category: config
|
||||
address: 72
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: 5
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output voltage"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
entity_category: config
|
||||
address: 8
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_voltage_maximum}
|
||||
step: ${${model}_voltage_multiplier}
|
||||
lambda: !lambda return x * ${${model}_voltage_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_voltage_multiplier});
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output current"
|
||||
device_class: current
|
||||
unit_of_measurement: "A"
|
||||
entity_category: config
|
||||
address: 9
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_current_maximum}
|
||||
step: ${${model}_current_multiplier}
|
||||
lambda: !lambda return x * ${${model}_current_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_current_multiplier});
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Voltage Protection"
|
||||
device_class: voltage
|
||||
unit_of_measurement: "V"
|
||||
address: 82
|
||||
entity_category: config
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_voltage_maximum}
|
||||
step: ${${model}_voltage_multiplier}
|
||||
lambda: !lambda return x * ${${model}_voltage_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_voltage_multiplier});
|
||||
|
||||
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Over Current Protection"
|
||||
device_class: current
|
||||
unit_of_measurement: "A"
|
||||
address: 83
|
||||
entity_category: config
|
||||
value_type: U_WORD
|
||||
min_value: 0
|
||||
max_value: ${${model}_current_maximum}
|
||||
step: ${${model}_current_multiplier}
|
||||
lambda: !lambda return x * ${${model}_current_multiplier};
|
||||
write_lambda: !lambda return x * (1/${${model}_current_multiplier});
|
||||
|
||||
# Date components are kept internal
|
||||
- platform: modbus_controller
|
||||
id: date_year
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 48
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_month
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 49
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_day
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 50
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_hour
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 51
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_minute
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 52
|
||||
value_type: U_WORD
|
||||
- platform: modbus_controller
|
||||
id: date_second
|
||||
modbus_controller_id: powersupply
|
||||
entity_category: diagnostic
|
||||
register_type: holding
|
||||
address: 53
|
||||
value_type: U_WORD
|
||||
|
||||
switch:
|
||||
- platform: modbus_controller
|
||||
modbus_controller_id: powersupply
|
||||
name: "Output"
|
||||
address: 18
|
||||
register_type: holding
|
||||
bitmask: 0x1
|
||||
entity_category: config
|
||||
|
||||
button:
|
||||
- platform: safe_mode
|
||||
name: "Safe mode"
|
@@ -1,7 +1,20 @@
|
||||
substitutions:
|
||||
name: "serverroom"
|
||||
friendly_name: "Serverraum Luft"
|
||||
|
||||
esphome:
|
||||
name: serverroom
|
||||
platform: ESP32
|
||||
board: nodemcu-32s
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.outdoor
|
||||
version: "0.8"
|
||||
min_version: 2022.1.0
|
||||
|
||||
esp32:
|
||||
board: esp32-c3-devkitm-1
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
@@ -22,23 +35,23 @@ logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
password: !secret api
|
||||
encryption:
|
||||
key: !secret apikey
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Initialize I²C
|
||||
i2c:
|
||||
- id: bus_a
|
||||
sda: 32
|
||||
scl: 25
|
||||
sda: 4
|
||||
scl: 3
|
||||
scan: true
|
||||
|
||||
# DHT22 sensor
|
||||
# BMP280 sensor
|
||||
sensor:
|
||||
- platform: bmp280
|
||||
- platform: bmp280_i2c
|
||||
i2c_id: bus_a
|
||||
temperature:
|
||||
name: "Serveraum Temperatur"
|
||||
|
@@ -1,8 +1,15 @@
|
||||
substitutions:
|
||||
name: "sleepingroom"
|
||||
friendly_name: "Luftqualität Schlafzimmer"
|
||||
|
||||
esphome:
|
||||
name: sleepingroom
|
||||
platform: ESP32
|
||||
board: nodemcu-32s
|
||||
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
project:
|
||||
name: sensor.${name}
|
||||
version: "1.0"
|
||||
min_version: 2021.9.0
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
@@ -41,10 +48,15 @@ esphome:
|
||||
id: pm25_warn
|
||||
state: "grün"
|
||||
|
||||
esp32:
|
||||
board: nodemcu-32s
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
wifi:
|
||||
ssid: "Voltage-legacy"
|
||||
password: !secret voltage_legacy_psk
|
||||
use_address: sleepingroom.home
|
||||
use_address: ${name}.home
|
||||
power_save_mode: high
|
||||
fast_connect: on
|
||||
|
||||
@@ -66,6 +78,7 @@ api:
|
||||
|
||||
# Enable over-the-air updates
|
||||
ota:
|
||||
platform: esphome
|
||||
password: !secret ota
|
||||
|
||||
# Initialize I²C
|
||||
@@ -186,11 +199,12 @@ sensor:
|
||||
temperature:
|
||||
name: "Schlafzimmer Temperatur"
|
||||
accuracy_decimals: 2
|
||||
filters:
|
||||
- offset: -2.5
|
||||
humidity:
|
||||
name: "Schlafzimmer Luftfeuchtigkeit"
|
||||
accuracy_decimals: 1
|
||||
# SCD30 temp sensor is a bit off
|
||||
temperature_offset: 0 °C
|
||||
ambient_pressure_compensation: 1
|
||||
automatic_self_calibration: True
|
||||
address: 0x61
|
||||
|
BIN
image/24c27e221bdb3c4e659741e304d0ee9d/256x256
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
image/24c27e221bdb3c4e659741e304d0ee9d/512x512
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
image/24c27e221bdb3c4e659741e304d0ee9d/original
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
image/2519dc3f137090683d62ff576d91a0c9/256x256
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
image/2519dc3f137090683d62ff576d91a0c9/512x512
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
image/2519dc3f137090683d62ff576d91a0c9/original
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
image/2d5009a2c9dab46f57b9ddaf569b947e/256x256
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
image/3ad2f364a964574f42a8f3206d104fdb/256x256
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
image/3ad2f364a964574f42a8f3206d104fdb/512x512
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
image/3ad2f364a964574f42a8f3206d104fdb/original
Normal file
After Width: | Height: | Size: 623 KiB |
BIN
image/5fedb57f4aec74efb42840acd1a05380/256x256
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
image/681fb9bf3fd83a14a1ec95b48169723c/256x256
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
image/681fb9bf3fd83a14a1ec95b48169723c/512x512
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
image/681fb9bf3fd83a14a1ec95b48169723c/original
Normal file
After Width: | Height: | Size: 685 KiB |
BIN
image/68915e3f6106faf4706cb9531f406398/256x256
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
image/a117f2afda53dcd987db921889f23bb8/256x256
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
image/a117f2afda53dcd987db921889f23bb8/512x512
Normal file
After Width: | Height: | Size: 373 KiB |
BIN
image/a117f2afda53dcd987db921889f23bb8/original
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
image/cd75aa150fe6b7181701c1246d6dfc71/256x256
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
image/cd75aa150fe6b7181701c1246d6dfc71/512x512
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
image/cd75aa150fe6b7181701c1246d6dfc71/original
Normal file
After Width: | Height: | Size: 1.2 MiB |
@@ -1,6 +0,0 @@
|
||||
bdm:
|
||||
name: BDM
|
||||
mac: BLE_34:14:B5:A0:99:BD
|
||||
icon:
|
||||
picture:
|
||||
track: true
|
268
mqtt.yaml
@@ -1,44 +1,13 @@
|
||||
sensor:
|
||||
# MQTT bathroom sensors
|
||||
- name: "Badezimmer Temperatur"
|
||||
unique_id: bathroom.temperature
|
||||
state_topic: "home/bathroom/climate/sensor/temperature"
|
||||
device_class: 'temperature'
|
||||
state_class: 'measurement'
|
||||
unit_of_measurement: '°C'
|
||||
last_reset_topic: "home/bathroom/climate/sensor/temperature"
|
||||
last_reset_value_template: '1970-01-01T00:00:00+00:00'
|
||||
device:
|
||||
identifiers: MagicMirror-DHT22-T
|
||||
name: MagicMirror-DHT22-T
|
||||
model: DHT22
|
||||
manufacturer: mqtt-io
|
||||
force_update: true
|
||||
|
||||
- name: "Badezimmer Luftfeuchtigkeit"
|
||||
unique_id: bathroom.humidity
|
||||
state_topic: "home/bathroom/climate/sensor/humidity"
|
||||
device_class: 'humidity'
|
||||
state_class: 'measurement'
|
||||
unit_of_measurement: '%'
|
||||
last_reset_topic: "home/bathroom/climate/sensor/humidity"
|
||||
last_reset_value_template: '1970-01-01T00:00:00+00:00'
|
||||
device:
|
||||
identifiers: MagicMirror-DHT22-H
|
||||
name: MagicMirror-DHT22-H
|
||||
model: DHT22
|
||||
manufacturer: mqtt-io
|
||||
force_update: true
|
||||
|
||||
# MQTT/RTL_433 (outdoor) sensors
|
||||
# Reliable sensor with good placement (good values) and good reception
|
||||
- device_class: battery
|
||||
name: Auriol-AHFL-1-106-B
|
||||
name: Batterie
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ float(value) * 99 + 1 }}'
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/106/battery_ok
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/216/battery_ok
|
||||
unique_id: Auriol-AHFL-1-106-B
|
||||
device:
|
||||
identifiers: Auriol-AHFL-1-106
|
||||
@@ -47,11 +16,11 @@
|
||||
manufacturer: rtl_433
|
||||
|
||||
- device_class: temperature
|
||||
name: Auriol-AHFL-1-106-T
|
||||
name: Temperatur
|
||||
unit_of_measurement: °C
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/106/temperature_C
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/216/temperature_C
|
||||
unique_id: Auriol-AHFL-1-106-T
|
||||
device:
|
||||
identifiers: Auriol-AHFL-1-106
|
||||
@@ -60,11 +29,11 @@
|
||||
manufacturer: rtl_433
|
||||
|
||||
- device_class: humidity
|
||||
name: Auriol-AHFL-1-106-H
|
||||
name: Luftfeuchtigkeit
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/106/humidity
|
||||
state_topic: rtl_433/sdr/devices/Auriol-AHFL/1/216/humidity
|
||||
unique_id: Auriol-AHFL-1-106-H
|
||||
device:
|
||||
identifiers: Auriol-AHFL-1-106
|
||||
@@ -72,98 +41,197 @@
|
||||
model: Auriol-AHFL
|
||||
manufacturer: rtl_433
|
||||
|
||||
# Only temperature
|
||||
- device_class: temperature
|
||||
name: AmbientWeather-TX8300-1-29-T
|
||||
unit_of_measurement: °C
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/AmbientWeather-TX8300/1/29/temperature_C
|
||||
unique_id: AmbientWeather-TX8300-1-29-T
|
||||
device:
|
||||
identifiers: AmbientWeather-TX8300-1-29
|
||||
name: AmbientWeather-TX8300-1-29
|
||||
model: AmbientWeather-TX8300
|
||||
manufacturer: rtl_433
|
||||
|
||||
# fair T/H/Bat
|
||||
- device_class: battery
|
||||
name: inFactory-TH-1-129-B
|
||||
name: Batterie
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ float(value) * 99 + 1 }}'
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
state_topic: rtl_433/sdr/devices/inFactory-TH/1/129/battery_ok
|
||||
unique_id: inFactory-TH-1-129-B
|
||||
state_topic: rtl_433/sdr/devices/Vauno-EN8822C/1/216/battery_ok
|
||||
unique_id: Vauno-EN8822C-1-244-B
|
||||
device:
|
||||
identifiers: inFactory-TH-1-129
|
||||
name: inFactory-TH-1-129
|
||||
model: inFactory-TH
|
||||
identifiers: Vauno-EN8822C-1-244
|
||||
name: Vauno-EN8822C-1-244
|
||||
model: Vauno-EN8822C
|
||||
manufacturer: rtl_433
|
||||
|
||||
- device_class: temperature
|
||||
name: inFactory-TH-1-129-F
|
||||
name: Temperatur
|
||||
unit_of_measurement: °C
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Vauno-EN8822C/1/244/temperature_C
|
||||
unique_id: Vauno-EN8822C-1-244-T
|
||||
device:
|
||||
identifiers: Vauno-EN8822C-1-244
|
||||
name: Vauno-EN8822C-1-244
|
||||
model: Vauno-EN8822C
|
||||
manufacturer: rtl_433
|
||||
|
||||
- device_class: humidity
|
||||
name: Luftfeuchtigkeit
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Vauno-EN8822C/1/244/humidity
|
||||
unique_id: Vauno-EN8822C-1-244-H
|
||||
device:
|
||||
identifiers: Vauno-EN8822C-1-244
|
||||
name: Vauno-EN8822C-1-244
|
||||
model: Vauno-EN8822C
|
||||
manufacturer: rtl_433
|
||||
|
||||
- device_class: humidity
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Nexus-TH/1/238/humidity
|
||||
unique_id: Nexus-TH-1-238-H
|
||||
device:
|
||||
identifiers:
|
||||
- Nexus-TH-1-238
|
||||
model: Nexus-TH
|
||||
manufacturer: rtl_433
|
||||
name: Nexus-TH-1-238
|
||||
name: Humidity
|
||||
|
||||
- device_class: humidity
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Nexus-TH/1/238/humidity
|
||||
unique_id: Nexus-TH-1-238-H
|
||||
device:
|
||||
identifiers:
|
||||
- Nexus-TH-1-238
|
||||
model: Nexus-TH
|
||||
manufacturer: rtl_433
|
||||
name: Nexus-TH-1-238
|
||||
name: Humidity
|
||||
|
||||
# Cotech-367959
|
||||
|
||||
- device_class: battery
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ ((float(value) * 99)|round(0)) + 1 }}'
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/battery_ok
|
||||
unique_id: Cotech-367959-130-B
|
||||
device:
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Battery
|
||||
|
||||
- device_class: temperature
|
||||
unit_of_measurement: °F
|
||||
value_template: '{{ value|float }}'
|
||||
value_template: '{{ value|float|round(1) }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/inFactory-TH/1/129/temperature_F
|
||||
unique_id: inFactory-TH-1-129-F
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/temperature_F
|
||||
unique_id: Cotech-367959-130-F
|
||||
device:
|
||||
identifiers: inFactory-TH-1-129
|
||||
name: inFactory-TH-1-129
|
||||
model: inFactory-TH
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Temperature
|
||||
|
||||
- device_class: humidity
|
||||
name: inFactory-TH-1-129-H
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/inFactory-TH/1/129/humidity
|
||||
unique_id: inFactory-TH-1-129-H
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/humidity
|
||||
unique_id: Cotech-367959-130-H
|
||||
device:
|
||||
identifiers: inFactory-TH-1-129
|
||||
name: inFactory-TH-1-129
|
||||
model: inFactory-TH
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Humidity
|
||||
|
||||
# weak, good T/H/Bat
|
||||
- device_class: battery
|
||||
name: Nexus-TH-1-224-B
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ float(value) * 99 + 1 }}'
|
||||
state_class: measurement
|
||||
entity_category: diagnostic
|
||||
state_topic: rtl_433/sdr/devices/Nexus-TH/1/224/battery_ok
|
||||
unique_id: Nexus-TH-1-224-B
|
||||
- device_class: precipitation
|
||||
unit_of_measurement: mm
|
||||
value_template: '{{ value|float|round(2) }}'
|
||||
state_class: total_increasing
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/rain_mm
|
||||
unique_id: Cotech-367959-130-RT
|
||||
device:
|
||||
identifiers: Nexus-TH-1-224
|
||||
name: Nexus-TH-1-224
|
||||
model: Nexus-TH
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Rain Total
|
||||
|
||||
- device_class: temperature
|
||||
name: Nexus-TH-1-224-T
|
||||
unit_of_measurement: °C
|
||||
- unit_of_measurement: °
|
||||
value_template: '{{ value|float }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Nexus-TH/1/224/temperature_C
|
||||
unique_id: Nexus-TH-1-224-T
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/wind_dir_deg
|
||||
unique_id: Cotech-367959-130-WD
|
||||
device:
|
||||
identifiers: Nexus-TH-1-224
|
||||
name: Nexus-TH-1-224
|
||||
model: Nexus-TH
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Wind Direction
|
||||
|
||||
- device_class: humidity
|
||||
name: Nexus-TH-1-224-H
|
||||
unit_of_measurement: '%'
|
||||
value_template: '{{ value|float }}'
|
||||
- device_class: wind_speed
|
||||
unit_of_measurement: km/h
|
||||
value_template: '{{ (float(value|float) * 3.6) | round(2) }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Nexus-TH/1/224/humidity
|
||||
unique_id: Nexus-TH-1-224-H
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/wind_avg_m_s
|
||||
unique_id: Cotech-367959-130-WS
|
||||
device:
|
||||
identifiers: Nexus-TH-1-224
|
||||
name: Nexus-TH-1-224
|
||||
model: Nexus-TH
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Wind Average
|
||||
|
||||
- device_class: wind_speed
|
||||
unit_of_measurement: km/h
|
||||
value_template: '{{ (float(value|float) * 3.6) | round(2) }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/wind_max_m_s
|
||||
unique_id: Cotech-367959-130-GS
|
||||
device:
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Wind max
|
||||
|
||||
- device_class: illuminance
|
||||
unit_of_measurement: lx
|
||||
value_template: '{{ value|int }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/light_lux
|
||||
unique_id: Cotech-367959-130-lux
|
||||
device:
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: Outside Luminance
|
||||
|
||||
- unit_of_measurement: UV Index
|
||||
value_template: '{{ value|float|round(1) }}'
|
||||
state_class: measurement
|
||||
state_topic: rtl_433/sdr/devices/Cotech-367959/130/uv
|
||||
unique_id: Cotech-367959-130-uv
|
||||
device:
|
||||
identifiers:
|
||||
- Cotech-367959-130
|
||||
model: Cotech-367959
|
||||
manufacturer: rtl_433
|
||||
name: Cotech-367959-130
|
||||
name: UV Index
|
||||
|
8
mqtt_statestream.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
base_topic: homeassistant
|
||||
publish_attributes: true
|
||||
publish_timestamps: true
|
||||
# include:
|
||||
# entities:
|
||||
# - sensor.Netzleistung
|
||||
|
||||
|
8
notify.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
#All Mobile Phones
|
||||
- platform: group
|
||||
name: "Alle mobilen Geräte"
|
||||
services:
|
||||
- service: mobile_app_le2123
|
||||
- service: mobile_app_apollo
|
||||
- service: mobile_app_xt2125_4
|
||||
|
@@ -1,10 +1,16 @@
|
||||
db_url: !secret ha_recorder_dburl
|
||||
# Commit to db only every X seconds
|
||||
commit_interval: 10
|
||||
# Delete events and states older than 2 weeks
|
||||
commit_interval: 60
|
||||
# Delete events and states older than 1 week
|
||||
auto_purge: true
|
||||
purge_keep_days: 7
|
||||
# include:
|
||||
# entity_globs:
|
||||
# - sensor.rd6018_controller_*
|
||||
# entities:
|
||||
# - sensor.line_power_channel_a_voltage
|
||||
# - sensor.line_power_channel_b_voltage
|
||||
# - sensor.line_power_channel_c_voltage
|
||||
# domains:
|
||||
# - sensor
|
||||
# - switch
|
||||
@@ -16,21 +22,20 @@
|
||||
entity_globs:
|
||||
- weather.zuhause_*
|
||||
- sensor.*_power_factor
|
||||
- sensor.*_current
|
||||
- sensor.*_voltage
|
||||
- sensor.dwd*
|
||||
- sensor.germany_coronavirus*
|
||||
- binary_sensor.*firmware_update
|
||||
- binary_sensor.*_overpowering
|
||||
- binary_sensor.*_overheating
|
||||
- sensor.*_wi_fi_signal
|
||||
- sensor.*_energy_returned
|
||||
- sensor.*_wifi_strenght
|
||||
- sensor.*_uptime
|
||||
- sensor.sun*
|
||||
entities:
|
||||
- sun.sun # Don't record sun data
|
||||
- sensor.fritzbox_device_uptime
|
||||
- sensor.snmp_wan_in
|
||||
- sensor.snmp_wan_out
|
||||
- sensor.time
|
||||
- sensor.awtrix_kitchen_current_app
|
||||
- sensor.awtrix_desk_current_app
|
||||
# event_types:
|
||||
# - call_service # Don't record service calls
|
||||
|
||||
|
2532
scenes.yaml
69
scripts.yaml
@@ -45,3 +45,72 @@ moodlight_neutral:
|
||||
transition: 2
|
||||
mode: single
|
||||
icon: mdi:lightbulb-on
|
||||
moodlight_orange_plasma:
|
||||
alias: Moodlight Orange Plasma
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
data:
|
||||
brightness_pct: 20
|
||||
transition: 2
|
||||
effect: Lake
|
||||
target:
|
||||
device_id:
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- c64e7c3dcda7f1c23e456959f2c60f39
|
||||
- service: select.select_option
|
||||
target:
|
||||
entity_id: select.wohnzimmer_hinten_color_palette, select.wohnzimmer_vorne_color_palette,
|
||||
select.kuche_color_palette
|
||||
data:
|
||||
option: Orangery
|
||||
- condition: state
|
||||
state: 'on'
|
||||
entity_id: media_player.lg_webos_smart_tv
|
||||
- service: light.turn_off
|
||||
target:
|
||||
device_id: 6dcbd87b459412144bddc42af3ae8b83
|
||||
data:
|
||||
transition: 2
|
||||
mode: single
|
||||
icon: mdi:lightbulb-on
|
||||
moodlight_xmas:
|
||||
alias: Moodlight XMas
|
||||
sequence:
|
||||
- data:
|
||||
brightness_pct: 20
|
||||
transition: 2
|
||||
effect: Glitter
|
||||
target:
|
||||
device_id:
|
||||
- 6dcbd87b459412144bddc42af3ae8b83
|
||||
- 4edd9b9df7d1f6f2fe7dcc2e5c0eb968
|
||||
- c64e7c3dcda7f1c23e456959f2c60f39
|
||||
action: light.turn_on
|
||||
- target:
|
||||
entity_id: select.wohnzimmer_hinten_color_palette, select.wohnzimmer_vorne_color_palette,
|
||||
select.kuche_color_palette
|
||||
data:
|
||||
option: Orangery
|
||||
action: select.select_option
|
||||
- condition: state
|
||||
state: 'on'
|
||||
entity_id: media_player.lg_webos_smart_tv
|
||||
- target:
|
||||
device_id: 6dcbd87b459412144bddc42af3ae8b83
|
||||
data:
|
||||
transition: 2
|
||||
action: light.turn_off
|
||||
mode: single
|
||||
icon: mdi:lightbulb-on
|
||||
wled_wohnzimmer_nachster_effekt:
|
||||
alias: 'WLED: Wohnzimmer nächster Effekt'
|
||||
sequence:
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: light.wohnzimmer_hinten, light.wohnzimmer_vorne
|
||||
data:
|
||||
effect: '{{ state_attr(''light.wohnzimmer_hinten'', ''effect_list'') | random
|
||||
}}'
|
||||
mode: single
|
||||
icon: mdi:firework
|
||||
|
61
sensors.yaml
@@ -3,16 +3,6 @@
|
||||
display_options:
|
||||
- 'time'
|
||||
- 'date'
|
||||
# Raspberry Pi CPU temp
|
||||
- platform: command_line
|
||||
name: "CPU Temp"
|
||||
command: "cat /sys/class/thermal/thermal_zone0/temp"
|
||||
unit_of_measurement: "°C"
|
||||
value_template: "{{ value | multiply(0.001) | round(1) }}"
|
||||
# DWD weather warnings
|
||||
- platform: dwd_weather_warnings
|
||||
name: "DWD Unwetterwarnungen"
|
||||
region_name: "Münster-Süd"
|
||||
# Database size sensor
|
||||
- platform: sql
|
||||
db_url: !secret ha_recorder_dburl
|
||||
@@ -25,14 +15,20 @@
|
||||
# SNMP (Juniper) router traffic sensor
|
||||
- platform: snmp
|
||||
name: snmp_wan_in
|
||||
unique_id: '3303381540758'
|
||||
host: !secret router_ip
|
||||
community: !secret router_community
|
||||
baseoid: .1.3.6.1.2.1.2.2.1.10.511
|
||||
baseoid: .1.3.6.1.2.1.31.1.1.1.6.511
|
||||
version: 2c
|
||||
unit_of_measurement: "Octets"
|
||||
- platform: snmp
|
||||
name: snmp_wan_out
|
||||
unique_id: '1573258703922'
|
||||
host: !secret router_ip
|
||||
community: !secret router_community
|
||||
baseoid: .1.3.6.1.2.1.2.2.1.16.511
|
||||
baseoid: .1.3.6.1.2.1.31.1.1.1.10.511
|
||||
version: 2c
|
||||
unit_of_measurement: "Octets"
|
||||
|
||||
- platform: derivative
|
||||
source: sensor.snmp_wan_in
|
||||
@@ -45,24 +41,15 @@
|
||||
unit: B
|
||||
name: wan_out_derivative
|
||||
|
||||
- platform: template
|
||||
sensors:
|
||||
internet_speed_in:
|
||||
friendly_name: 'Internet Speed IN'
|
||||
value_template: "{{ (( states('sensor.wan_in_derivative') | float * 8 / 1000000 ) | round(2)) }}"
|
||||
unit_of_measurement: 'Mbps'
|
||||
internet_speed_out:
|
||||
friendly_name: 'Internet Speed OUT'
|
||||
value_template: "{{ (( states('sensor.wan_out_derivative') | float * 8 / 1000000 ) | round(2)) }}"
|
||||
unit_of_measurement: 'Mbps'
|
||||
|
||||
- platform: statistics
|
||||
name: 'WAN Traffic In'
|
||||
unique_id: '9081721471264'
|
||||
state_characteristic: mean
|
||||
entity_id: sensor.internet_speed_in
|
||||
sampling_size: 10
|
||||
- platform: statistics
|
||||
name: 'WAN Traffic Out'
|
||||
unique_id: '8688955223027'
|
||||
state_characteristic: mean
|
||||
entity_id: sensor.internet_speed_out
|
||||
sampling_size: 10
|
||||
@@ -71,11 +58,39 @@
|
||||
sensors:
|
||||
sun_rising_template:
|
||||
friendly_name: "Sun Rising Template"
|
||||
unique_id: '0680294616247'
|
||||
value_template: "{{ as_timestamp(states.sun.sun.attributes.next_rising) | timestamp_custom ('%H:%M') }}"
|
||||
|
||||
- platform: template
|
||||
sensors:
|
||||
sun_setting_template:
|
||||
friendly_name: "Sun Setting Template"
|
||||
unique_id: '8298170865533'
|
||||
value_template: "{{ as_timestamp(states.sun.sun.attributes.next_setting) | timestamp_custom ('%H:%M') }}"
|
||||
|
||||
# Sensor for Riemann sum of energy import (W -> Wh)
|
||||
- platform: integration
|
||||
source: sensor.power_import
|
||||
name: energy_import_sum
|
||||
unique_id: '6355740355352'
|
||||
unit_prefix: k
|
||||
round: 2
|
||||
method: left
|
||||
|
||||
# Sensor for Riemann sum of energy export (W -> Wh)
|
||||
- platform: integration
|
||||
source: sensor.power_export
|
||||
name: energy_export_sum
|
||||
unique_id: '6978829126367'
|
||||
unit_prefix: k
|
||||
round: 2
|
||||
method: left
|
||||
|
||||
# Sensor for Riemann sum of energy consumption (W -> Wh)
|
||||
- platform: integration
|
||||
source: sensor.power_consumption
|
||||
name: energy_consumption_sum
|
||||
unique_id: '8749045190416'
|
||||
unit_prefix: k
|
||||
round: 2
|
||||
method: left
|
||||
|
135
template.yaml
Normal file
@@ -0,0 +1,135 @@
|
||||
# - select:
|
||||
# - name: "Wohnzimmer Effekt"
|
||||
# unique_id: '6641823075755'
|
||||
# state: "{{ state_attr('light.wohnzimmer_hinten', 'effect') }}"
|
||||
# icon: mdi:firework
|
||||
# options: >
|
||||
# {{ state_attr('light.wohnzimmer_hinten', 'effect_list') }}
|
||||
# select_option:
|
||||
# - service: light.turn_on
|
||||
# target:
|
||||
# entity_id: light.wohnzimmer_hinten, light.wohnzimmer_vorne
|
||||
# data:
|
||||
# effect: "{{ option }}"
|
||||
- select:
|
||||
- name: "Wohnzimmer Palette"
|
||||
unique_id: '3107042775387'
|
||||
state: "{{ states('select.wohnzimmer_hinten_color_palette') }}"
|
||||
icon: mdi:palette
|
||||
options: >
|
||||
{{ state_attr('select.wohnzimmer_hinten_color_palette', 'options') }}
|
||||
select_option:
|
||||
- service: select.select_option
|
||||
target:
|
||||
entity_id: select.wohnzimmer_hinten_color_palette, select.wohnzimmer_vorne_color_palette
|
||||
data:
|
||||
option: '{{ option }}'
|
||||
- select:
|
||||
- name: "Available Media Players"
|
||||
unique_id: '6284128947660'
|
||||
state: "{{ states('input_text.selected_media_player') }}"
|
||||
options: >
|
||||
{{ states.media_player
|
||||
| rejectattr('state', 'in', ['off', 'idle', 'unavailable', 'unknown'])
|
||||
| map(attribute ='entity_id') | list }}
|
||||
select_option:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: input_text.selected_media_player
|
||||
data:
|
||||
value: "{{ option }}"
|
||||
- sensor:
|
||||
- name: "power_other"
|
||||
unique_id: '5579422933393'
|
||||
unit_of_measurement: "W"
|
||||
icon: mdi:flash
|
||||
state: >
|
||||
{% set total = states('sensor.netzleistung') | float %}
|
||||
{% set solar = states('sensor.balkonkraftwerk_power') | float %}
|
||||
{% set raumduft = states('sensor.flur_raumduft_power') | float %}
|
||||
{% set keller = states('sensor.keller_power') | float %}
|
||||
{% set anrichte = states('sensor.kuche_anrichte_power') | float %}
|
||||
{% set musik = states('sensor.kuche_musik_power') | float %}
|
||||
{% set bett = states('sensor.schlafzimmer_bett_power') | float %}
|
||||
{% set heimkino_sz = states('sensor.schlafzimmer_heimkino_power') | float %}
|
||||
{% set deko = states('sensor.schreibtisch_deko_power') | float %}
|
||||
{% set schreibtisch = states('sensor.schreibtisch_power') | float %}
|
||||
{% set serverraum = states('sensor.serverraum_power') | float %}
|
||||
{% set heimkino_wz = states('sensor.wohnzimmer_heimkino_power') | float %}
|
||||
{% set spieleschrank = states('sensor.wohnzimmer_spieleschrank_power') | float %}
|
||||
{% set kuehlschrank = states('sensor.tz3000_ww6drja5_ts011f_leistung') | float %}
|
||||
{% set kaffeemaschine = states('sensor.kaffeemaschine_leistung_2') | float %}
|
||||
{% set waeschetrockner = states('sensor.waschetrockner_leistung') | float %}
|
||||
{% set waschmaschine = states('sensor.waschmaschine_leistung') | float %}
|
||||
{% set arcade = states('sensor.arcade_automat_leistung') | float %}
|
||||
{{ (total + solar - raumduft - keller - musik - bett - heimkino_sz - deko - schreibtisch - serverraum - heimkino_wz - spieleschrank - kuehlschrank - kaffeemaschine - waeschetrockner - waschmaschine - arcade) | round(1) }}
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
||||
|
||||
# Shelly 3EM cumulative sensors (incl. PV)
|
||||
- sensor:
|
||||
# Template sensor for values of power import (active_power > 0)
|
||||
- name: power_import
|
||||
unique_id: '2385816278013'
|
||||
unit_of_measurement: 'W'
|
||||
state: >
|
||||
{% if (states('sensor.line_power_channel_a_power')|float + states('sensor.line_power_channel_b_power')|float + states('sensor.line_power_channel_c_power')|float) > 0 %}
|
||||
{{ (states('sensor.line_power_channel_a_power')|float + states('sensor.line_power_channel_b_power')|float + states('sensor.line_power_channel_c_power')|float)|round(1) }}
|
||||
{% else %}
|
||||
{{ 0 }}
|
||||
{% endif %}
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-export
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
||||
|
||||
# Template sensor for values of power export (active_power < 0)
|
||||
- name: power_export
|
||||
unique_id: '9143524256421'
|
||||
unit_of_measurement: 'W'
|
||||
state: >
|
||||
{% if (states('sensor.line_power_channel_a_power')|float + states('sensor.line_power_channel_b_power')|float + states('sensor.line_power_channel_c_power')|float) < 0 %}
|
||||
{{ ((states('sensor.line_power_channel_a_power')|float + states('sensor.line_power_channel_b_power')|float + states('sensor.line_power_channel_c_power')|float) * -1 ) | round(1) }}
|
||||
{% else %}
|
||||
{{ 0 }}
|
||||
{% endif %}
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:transmission-tower-import
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
||||
|
||||
# Template sensor for values of power consumption
|
||||
- name: power_consumption
|
||||
unique_id: '3502047649408'
|
||||
unit_of_measurement: 'W'
|
||||
state: >
|
||||
{% if (states('sensor.power_export')|float(0)) > 0 and (states('sensor.balkonkraftwerk_power')|float(0) - states('sensor.power_export')|float(0)) < 0 %}
|
||||
{% elif (states('sensor.power_export')|float(0)) > 0 and (states('sensor.balkonkraftwerk_power')|float(0) - states('sensor.power_export')|float(0)) > 0 %}
|
||||
{{ ((states('sensor.balkonkraftwerk_power')|float(0)) - states('sensor.power_export')|float(0)) | round(1) }}
|
||||
{% else %}
|
||||
{{ (states('sensor.power_import')|float(0) + states('sensor.balkonkraftwerk_power')|float(0)) | round(1) }}
|
||||
{% endif %}
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
icon: mdi:home-lightning-bolt
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
||||
|
||||
# Internet Speed template sensor
|
||||
- name: internet_speed_in
|
||||
unique_id: '9519483670666'
|
||||
state: >
|
||||
{{ (( states('sensor.wan_in_derivative') | float * 8 / 1000000 ) | round(2)) }}
|
||||
unit_of_measurement: 'Mbps'
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
||||
- name: internet_speed_out
|
||||
state: >
|
||||
{{ (( states('sensor.wan_out_derivative') | float * 8 / 1000000 ) | round(2)) }}
|
||||
unit_of_measurement: 'Mbps'
|
||||
attributes:
|
||||
last_reset: '1970-01-01T00:00:00+00:00'
|
@@ -1,24 +1,75 @@
|
||||
# Internet traffic
|
||||
internet_usage_in_monthly:
|
||||
source: sensor.snmp_wan_in
|
||||
name: Monthly internet traffic in
|
||||
unique_id: monthly_internet_traffic_in
|
||||
cycle: monthly
|
||||
internet_usage_out_monthly:
|
||||
source: sensor.snmp_wan_out
|
||||
name: Monthly internet traffic out
|
||||
unique_id: monthly_internet_traffic_out
|
||||
cycle: monthly
|
||||
internet_usage_in_daily:
|
||||
source: sensor.snmp_wan_in
|
||||
name: Daily internet traffic in
|
||||
unique_id: daily_internet_traffic_in
|
||||
cycle: daily
|
||||
internet_usage_out_daily:
|
||||
source: sensor.snmp_wan_out
|
||||
name: Daily internet traffic out
|
||||
unique_id: daily_internet_traffic_out
|
||||
cycle: daily
|
||||
internet_usage_in_hourly:
|
||||
source: sensor.snmp_wan_in
|
||||
name: Hourly internet traffic in
|
||||
unique_id: hourly_internet_traffic_in
|
||||
cycle: hourly
|
||||
internet_usage_out_hourly:
|
||||
source: sensor.snmp_wan_out
|
||||
name: Hourly internet traffic out
|
||||
unique_id: hourly_internet_traffic_out
|
||||
cycle: hourly
|
||||
|
||||
# Energy
|
||||
energy_import_daily:
|
||||
source: sensor.energy_import_sum
|
||||
name: Energy Import Daily
|
||||
unique_id: energy_import_daily
|
||||
cycle: daily
|
||||
energy_import_monthly:
|
||||
source: sensor.energy_import_sum
|
||||
name: Energy Import Monthly
|
||||
unique_id: energy_import_monthly
|
||||
cycle: monthly
|
||||
energy_export_daily:
|
||||
source: sensor.energy_export_sum
|
||||
name: Energy Export Daily
|
||||
unique_id: energy_export_daily
|
||||
cycle: daily
|
||||
energy_export_monthly:
|
||||
source: sensor.energy_export_sum
|
||||
name: Energy Export Monthly
|
||||
unique_id: energy_export_monthly
|
||||
cycle: monthly
|
||||
energy_consumption_daily:
|
||||
source: sensor.energy_consumption_sum
|
||||
name: Energy Consumption Daily
|
||||
unique_id: energy_consumption_daily
|
||||
cycle: daily
|
||||
energy_consumption_monthly:
|
||||
source: sensor.energy_consumption_sum
|
||||
name: Energy Consumption Monthly
|
||||
unique_id: energy_consumption_monthly
|
||||
cycle: monthly
|
||||
|
||||
# Energy (Solar)
|
||||
energy_solar_daily:
|
||||
source: sensor.balkonkraftwerk_energy
|
||||
name: Energy Solar Daily
|
||||
unique_id: energy_solar_daily
|
||||
cycle: daily
|
||||
energy_solar_monthly:
|
||||
source: sensor.balkonkraftwerk_energy
|
||||
name: Energy Solar Monthly
|
||||
unique_id: energy_solar_monthly
|
||||
cycle: monthly
|
||||
|
18
www/DWD.svg
Normal file
After Width: | Height: | Size: 11 KiB |
13
www/Nina_app.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="220">
|
||||
<path fill="#E72" d="m149,58a52,52 0 1,0 2,0z"/>
|
||||
<path fill="#237" d="m112,135h76l-38-66z"/>
|
||||
<path fill="none" stroke="#EEE" stroke-width="12"
|
||||
d="m63,8a72,117 0 0,0 0,204m174,0a72,117 0 0,0 0-204M82
|
||||
30a70,99 0 0,0 0,160m136,0a72,101 0 0,0 0-160M101
|
||||
54a68,78 0 0,0 0,112m98,0a68,78 0 0,0 0-112"/>
|
||||
<path fill="none" stroke="#E72" stroke-width="12"
|
||||
d="m63,22a96,112 0 0,0 0,176m174,0a96,112 0 0,0 0-176M80
|
||||
40a80,93 0 0,0 0,140m140,0a80,93 0 0,0 0-140M100,60a62
|
||||
66 0 0,0 0,100m100,0a62,66 0 0,0 0-100"/>
|
||||
</svg>
|
After Width: | Height: | Size: 594 B |