diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68b508b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.yaml +output/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f9310eb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +06-Feb-23 | Andy Symons | First posted on the community forum + +08-Feb-23 | Andy Symons | Improved robustness when sensors become unavailable. Posted on GitHub. + +12-Feb-23 | Andy Symons | Improved logic when first starting with idle timers + +15-Feb-23 | Andy Symons | Mode changed to queued so all triggers used but without conficting states. + +19-Feb-23 | Andy Symons | Continual refresh; robust through restart, all day events, and overlappng events. + +20-Feb-23 | Andy Symons | More verbose 'reason' text with event details where applicable (needs 255 character helper). + +25-Feb-23 | Andy Symons | Robust against absent or invalid tempterature specification in an event. Reports errors in the reason text. diff --git a/Heating X code generator - use on a local drive.zip b/Heating X code generator - use on a local drive.zip deleted file mode 100644 index 5b75828..0000000 Binary files a/Heating X code generator - use on a local drive.zip and /dev/null differ diff --git a/README.md b/README.md index 6db32b4..46198e4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ -# Heating-X-code-generator +# HeatingX -Use Microsoft Mail Merge to create YAML files for all the template sensors and helpers required for a complete home installation of Heating X zones. -Microsoft Office (WORD and EXCEL) is a prerequisite (sorry I could not find a free package to do this) +Use Jinja to create YAML files for all the template sensors and helpers required for a complete home installation of HeatingX zones. -To get started: -1. Download the ZIP file to a temporary location on a local disk drive. Problems can occur if it is used on a drive that is synchronised to a cloud service, such as iCloud or OneDrive (but you can keep a copy of the ZIP file if you wish). -2. Unzip it. -3. In the resulting folder there are detailed instructions in $README on how to use the Code Generator to generate and install all the YAML code you need for even the most conplex installation. - -Basically you enter the details of your installation (zones and thermostats) in the EXCEL file, then use each of the five WORD files to generate the contents of a YAML file. They each generate a whole file except the dashboard card generator, which has to be installed one card at a time. +To get started: +1. Make sure you have Python 3 installed. +2. Create a virtual environment with `python3 -m venv .venv`. +3. Activate your virtual environment with `source .venv/bin/activate`. +4. Install the required dependencies with `python3 -m pip install requirements.txt`. +5. Make your own `config.yaml` with `cp config.yaml.sample config.yaml`. +6. Edit the contents of the resulting `config.yaml` to match your own installation (zones and thermostats). +7. Create the required files with `python generate.py` which will output the files into +the `output/` directory. diff --git a/blueprints/heating_x.yaml b/blueprints/heating_x.yaml new file mode 100644 index 0000000..2785d24 --- /dev/null +++ b/blueprints/heating_x.yaml @@ -0,0 +1,771 @@ +blueprint: + name: "Heating X" + description: >- + Controls one or more thermostats from a calendar, + allows temporary manual override, and optionally turns off thermostat + if a door or window is opened, or if the zone is unoccupied for a while. + domain: automation + + ### ---------------------------------------------------------------------------- + ### INPUTS + ### ---------------------------------------------------------------------------- + + input: + thermostat_controls: + name: DEVICE ENTITY - Thermostat control + description: One or more thermostat entities that are to be controlled by this automation + selector: + entity: + domain: climate + multiple: true + + door_or_window_open_sensors: + name: DEVICE ENTITY - Door or window open sensors + description: Zero or more sensors that detect whether a door or window is open + selector: + entity: + domain: binary_sensor + device_class: opening + multiple: true + default: [] + + zone_occupancy_sensors: + name: DEVICE ENTITY - Zone occupancy sensors + description: Zero or more sensors that detect whether there is anyone in the zone + selector: + entity: + domain: binary_sensor + device_class: occupancy + multiple: true + default: [] + + zone_calendar: + name: CALENDAR ENTITY - Zone calendar + description: The calendar dedicated to scheduling events for this zone + selector: + entity: + domain: calendar + + event_name: + name: HELPER - Event name + description: The global variable (helper) to hold the name (aka Summary) of the current event (if any) + selector: + entity: + domain: input_text + + event_start: + name: HELPER - Event start + description: The global variable (helper) to hold the start date and time of the current event (if any) + selector: + entity: + domain: input_datetime + + event_end: + name: HELPER - Event end + description: The global variable (helper) to hold the end date and time of the current event (if any) + selector: + entity: + domain: input_datetime + + event_temperature: + name: HELPER - Event temperature + description: The global variable (helper) to hold the temperature specified in the current event (if any) + selector: + entity: + domain: input_number + + manual_temperature: + name: HELPER - Manual temperature + description: The global variable (helper) to hold the temperature specified by manual control of the device or the dashboard + selector: + entity: + domain: input_number + + setting_reason: + name: HELPER - Setting reason + description: The global variable (helper) into which the automation writes the reason for the current setting (for use on a dashboard) + selector: + entity: + domain: input_text + + door_or_window_open_timer: + name: HELPER - Door or window open timer + description: The global variable (helper) to hold the timer for the period since a door or window was opened + selector: + entity: + domain: timer + + unoccupancy_timer: + name: HELPER - Unoccupancy timer + description: The global variable (helper) to hold the timer for the period since the zone was last unoccupied + selector: + entity: + domain: timer + + warmup_timer: + name: HELPER - Warmup timer + description: The global variable (helper) to hold the timer for the event warmup period + selector: + entity: + domain: timer + + manual_override_timer: + name: HELPER - Manual override timer + description: The global variable (helper) to hold the timer for a manual intervention + selector: + entity: + domain: timer + + echoblock_timer: + name: HELPER - Echoblock timer + description: The timer for use inside the automation to disinguish genuine manual changes of the set temperature from those set by the automation + selector: + entity: + domain: timer + + frost_setting: + name: PARAMETER - Frost setting + description: The temperature to be used when the heating is turned off + selector: + number: + min: 0 + max: 100 + default: 5 + + warmup_period: + name: PARAMETER - Warmup period + description: The period of time from the start of a new event for which zone unoccupancy will be ignored + selector: + time: + default: "02:00:00" + + manual_override_period: + name: PARAMETER - Manual override period + description: The time period for which a manual intervention will override the schedule + selector: + time: + default: "02:00:00" + + door_or_window_open_period: + name: PARAMETER - Door or window open period + description: The time period for which a door or window may be open before the heating is turned off + selector: + time: + default: "0:03:00" + + unoccupancy_period: + name: PARAMETER - Unoccupancy period + description: The time period for which the zone may be unoccupied before the heating is turned off + selector: + time: + default: "01:00:00" + +mode: queued # Use all triggers but avoid conflicting states + +## ---------------------------------------------------------------------------- +## LOCAL VARIABLES +## ---------------------------------------------------------------------------- + +variables: + # Input variables needed to capture global variable values for use in templates. + thermostat_controls: !input thermostat_controls + door_or_window_open_sensors: !input door_or_window_open_sensors + event_name: !input event_name + event_temperature: !input event_temperature + frost_setting: !input frost_setting + manual_temperature: !input manual_temperature + manual_override_timer: !input manual_override_timer + zone_calendar: !input zone_calendar + zone_occupancy_sensors: !input zone_occupancy_sensors + # Reusable variables. + echoblock_duration: 5 + maximum_thermostat_temperature: "{{ thermostat_controls | map('state_attr', 'max_temp') | min }}" + minimum_thermostat_temperature: "{{ thermostat_controls | map('state_attr', 'min_temp') | max }}" + off_temperature: "{{ [frost_setting, minimum_thermostat_temperature] | max }}" + +## ---------------------------------------------------------------------------- +## TRIGGERS +## ---------------------------------------------------------------------------- + +trigger: + # Calendar event starts + - platform: calendar + event: start + entity_id: !input zone_calendar + id: calendar_event_start + + # Calendar event ends + - platform: calendar + event: end + entity_id: !input zone_calendar + id: calendar_event_end + + # Calendar becomes unknown + - platform: state + entity_id: !input zone_calendar + to: "unknown" + id: calendar_unknown + + # Calendar becomes unavailable + - platform: state + entity_id: !input zone_calendar + to: "unavailable" + id: calendar_unavailable + + # Change in any one of the thermostat set temperatures + - platform: state + attribute: temperature + entity_id: !input thermostat_controls + id: set_temperature_change + variables: + set_temperature: "{{ state_attr(trigger.entity_id, 'temperature') }}" + + # Manual override starts + - platform: event + event_type: + - timer.started + - timer.restarted + event_data: + entity_id: !input manual_override_timer + id: manual_override_start + + # Manual override ends + - platform: event + event_type: + - timer.finished + - timer.cancelled + event_data: + entity_id: !input manual_override_timer + id: manual_override_end + + # Zone becomes unoccupied: sensor available and off (clear) + - platform: state + entity_id: !input zone_occupancy_sensors + to: "off" + for: + seconds: 10 + id: zone_unoccupied + + # Zone becomes occupied: sensor 'detected' (on), 'unknown', or 'unavailable' + - platform: state + entity_id: !input zone_occupancy_sensors + from: "off" + for: + seconds: 10 + id: zone_occupied + + # A door or window is opened + - platform: state + entity_id: !input door_or_window_open_sensors + to: "on" + for: + seconds: 10 + id: door_or_window_opened + + # A doors or windows is closed, or becomes 'unknown' or 'unavailable' + - platform: state + entity_id: !input door_or_window_open_sensors + from: "on" + for: + seconds: 10 + id: doors_and_windows_closed + + # Door or window open timer finished (time to turn off the heating) + - platform: event + event_type: timer.finished + event_data: + entity_id: !input door_or_window_open_timer + id: door_or_window_open_timer_end + + # Unoccupancy timer finished (time to turn off the heating) + - platform: event + event_type: timer.finished + event_data: + entity_id: !input unoccupancy_timer + id: unoccupancy_timer_end + +## ---------------------------------------------------------------------------- +## ACTIONS STEP 1 -- SET THE STATE VARİABLES +## ---------------------------------------------------------------------------- + +action: + - choose: + # + # Manual override start + # + - conditions: + - condition: trigger + id: set_temperature_change + # Ignore if it is just an echo from a setting from this automation + - condition: state + entity_id: !input echoblock_timer + state: idle + sequence: + - service: input_number.set_value + data: + value: "{{ set_temperature }}" + target: + entity_id: !input manual_temperature + - service: timer.start + data: + duration: !input manual_override_period + target: + entity_id: !input manual_override_timer + # + # Door or window opened - start timer + # + - conditions: + - condition: trigger + id: door_or_window_opened + # Do not restart timer if already running + - condition: not + conditions: + - condition: state + entity_id: !input door_or_window_open_timer + state: active + sequence: + - service: timer.start + data: + duration: !input door_or_window_open_period + target: + entity_id: !input door_or_window_open_timer + # + # Doors and windows closed - restart and pause timer + # + - conditions: + - condition: trigger + id: doors_and_windows_closed + sequence: + - service: timer.cancel + target: + entity_id: !input door_or_window_open_timer + # + # Unoccupancy - start timer + # + - conditions: + - condition: trigger + id: zone_unoccupied + # Do not restart timer if already running + - condition: not + conditions: + - condition: state + entity_id: !input unoccupancy_timer + state: active + sequence: + - service: timer.start + data: + duration: !input unoccupancy_period + target: + entity_id: !input unoccupancy_timer + # + # Zone occupied - restart and pause timer + # + - conditions: + - condition: trigger + id: zone_occupied + sequence: + - service: timer.cancel + target: + entity_id: !input unoccupancy_timer + + ## ---------------------------------------------------------------------------- + ## ACTIONS STEP 2 -- REFRESH THE CALENDAR EVENT HELPERS ON CALENDAR EVENT + ## ---------------------------------------------------------------------------- + + - choose: + - conditions: + - condition: trigger + id: calendar_event_start + sequence: + - service: input_text.set_value + data: + value: "{{ trigger.calendar_event.summary }}" + target: + entity_id: !input event_name + - service: input_datetime.set_datetime + data: + datetime: "{{ trigger.calendar_event.start }}" + target: + entity_id: !input event_start + - service: input_datetime.set_datetime + data: + datetime: "{{ trigger.calendar_event.end }}" + target: + entity_id: !input event_end + - service: input_number.set_value + data: + value: >- + {# copy description field #} + {% set description = trigger.calendar_event.description %} + + {% if description.split('#') | count < 3 %} + {% set temperature_error_code = -4 %} {# no number field #} + {% elif not description.split('#')[1] | is_number %} + {% set temperature_error_code = -3 %} {# not a number #} + {% elif (description.split('#')[1] | float(0)) < minimum_thermostat_temperature %} + {% set temperature_error_code = -2 %} {# number too small #} + {% elif (description.split('#')[1] | float(0)) > maximum_thermostat_temperature %} + {% set temperature_error_code = -1 %} {# number too big #} + {% else %} + {% set temperature_error_code = 0 %} + {% endif %} + + {% if temperature_error_code == 0 %} + {{ '%0.1f' | format(description.split('#')[1] | float(0)) }} + {% else %} + {{ temperature_error_code }} + {% endif %} + target: + entity_id: !input event_temperature + - conditions: + - condition: trigger + id: calendar_event_end + sequence: + - service: input_text.set_value + data: + value: (none) + target: + entity_id: !input event_name + - service: input_datetime.set_datetime + data: + datetime: "3000-01-01T00:00:00" + target: + entity_id: !input event_start + - service: input_datetime.set_datetime + data: + datetime: "3000-01-01T00:00:00" + target: + entity_id: !input event_end + - service: input_number.set_value + data: + value: -5 # unset temperature + target: + entity_id: !input event_temperature + + ## ---------------------------------------------------------------------------- + ## ACTIONS STEP 3 -- DETERMINE THE REQUIRED TEMPERATURE AND THE REASON + ## ---------------------------------------------------------------------------- + - choose: + # If a door or window has been open for the set period, turn the heating off + - conditions: + - condition: trigger + id: door_or_window_open_timer_end + - condition: template + value_template: "{{ door_or_window_open_sensors | select ('is_state', 'on') | list | count > 0 }}" + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: "Turned off because a door or window is open" + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If the zone has been unoccupied for the set period, turn the heating off + - conditions: + - condition: trigger + id: unoccupancy_timer_end + - condition: template + value_template: "{{ zone_occupancy_sensors | reject ('is_state', [ 'unknown', 'unavailable' ] ) | select ('is_state', 'on') | list | count > 0 }}" + sequence: + - service: climate.set_temperature + data: + temperature: "{{ [frost_setting, minimum_thermostat_temperature] | max }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: "Turned off because the zone is unoccupied" + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is a manual override in operation + - conditions: + - condition: or + conditions: + - condition: trigger + id: manual_override_start + - condition: and + conditions: + - condition: trigger + id: + - doors_and_windows_closed + - zone_occupied + - condition: state + entity_id: !input manual_override_timer + state: active + sequence: + - service: climate.set_temperature + data: + temperature: "{{ states(manual_temperature) }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: "Set manually to {{ states(manual_temperature) }}. Finishes at {{ as_timestamp(state_attr(manual_override_timer,'finishes_at')) | timestamp_custom('%H:%M') }}." + target: + entity_id: !input setting_reason + + # If the calendar state becomes unknown + - conditions: + - condition: trigger + id: calendar_state_unknown + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: Turned off because the calendar state is unknown + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If the calendar becomes unavailable + - conditions: + - condition: trigger + id: calendar_unavailable + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: Turned off because the calendar is unavailable + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is a calendar but no active calendar event (unset temperature) + - conditions: + - condition: trigger + id: + - calendar_event_end + - manual_override_end + - doors_and_windows_closed + - zone_occupied + - condition: template + value_template: >- + {{ states(event_temperature) | float == -5 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Turned off because nothing is scheduled."}} + {% if state_attr(zone_calendar, 'message') %} + {{ "The next event is '" + state_attr(zone_calendar, 'message') + "' " + ( as_timestamp(state_attr(zone_calendar, 'start_time')) ) | timestamp_custom('on %a %d %b %Y at %H:%M')}} + {% else %} + {{ "There are no future events." }} + {% endif %} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is an active calendar event with no temperature field + - conditions: + - condition: trigger + id: + - calendar_event_start + - manual_override_end + - doors_and_windows_closed + - zone_occupied + # The calendar event has no temperature field. + - condition: template + value_template: >- + {{ states(event_temperature) | float == -4 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Turned off because the calendar event '" + states(event_name) + "' does not specify a temperature." }} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is an active calendar event with a temperature field that is not a number + - conditions: + - condition: trigger + id: + - calendar_event_start + - manual_override_end + - doors_and_windows_closed + - zone_occupied + - condition: template + value_template: >- + {{ states(event_temperature) | float == -3 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Turned off because the calendar event '" + states(event_name) + "' does not specify a valid number for the temperature." }} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is an active calendar event with a temperature that is too low + - conditions: + - condition: or + conditions: + - condition: trigger + id: + - calendar_event_start + - condition: and + conditions: + - condition: trigger + id: + - manual_override_end + - doors_and_windows_closed + - zone_occupied + - condition: state + entity_id: !input zone_calendar + state: "on" + - condition: template + value_template: >- + {{ states(event_temperature) | float == -2 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Turned off because the calendar event '" + states(event_name) + "' specifies a temperature below the minimum." }} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is an active calendar event with a temperature that is too high + - conditions: + - condition: trigger + id: + - calendar_event_start + - manual_override_end + - doors_and_windows_closed + - zone_occupied + - condition: template + value_template: >- + {{ states(event_temperature) | float == -1 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ off_temperature }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Turned off because the calendar event '" + states(event_name) + "' specifies a temperature above the maximum." }} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer + + # If there is an active calendar event with a valid temperature + - conditions: + - condition: trigger + id: + - calendar_event_start + - manual_override_end + - doors_and_windows_closed + - zone_occupied + - condition: template + value_template: >- + {{ states(event_temperature) | float >= 0 }} + sequence: + - service: climate.set_temperature + data: + temperature: "{{ states(event_temperature) }}" + target: + entity_id: !input thermostat_controls + - service: input_text.set_value + data: + value: >- + {{ "Set to " + states(event_temperature) + " by calendar event '" + states(event_name) + "' until " + ( as_timestamp(state_attr(zone_calendar, 'end_time')) ) | timestamp_custom('%a %d %b %Y at %H:%M') + "." }} + target: + entity_id: !input setting_reason + # Echoblock to avoid setting the temperature to trigger a manual override + - service: timer.start + data: + duration: + seconds: "{{ echoblock_duration }}" + target: + entity_id: !input echoblock_timer diff --git a/config.yaml.sample b/config.yaml.sample new file mode 100644 index 0000000..1dbb82b --- /dev/null +++ b/config.yaml.sample @@ -0,0 +1,6 @@ +dashboard: +temperature: +zones: + - heating_zone_friendly_name: Bathroom + heating_zone_entity_name: bathroom + thermostat_entity_name: climate.bathroom diff --git a/generate.py b/generate.py new file mode 100644 index 0000000..b42a62e --- /dev/null +++ b/generate.py @@ -0,0 +1,46 @@ +import pathlib +import uuid +from typing import Any + +import jinja2 +import yaml + +TEMPLATE_DIR = pathlib.Path('templates') +OUTPUT_DIR = pathlib.Path('output') + + +def read_config() -> dict[str, Any]: + """Read the configuration file.""" + with open('config.yaml') as f: + return yaml.safe_load(f) + + +def read_templates() -> jinja2.Environment: + """Read the templates.""" + return jinja2.Environment( + loader=jinja2.FileSystemLoader('templates'), + trim_blocks=True, + lstrip_blocks=True, + ) + + +def generate_uuid() -> uuid.UUID: + """Generate a UUID.""" + return uuid.uuid4() + + +def generate() -> None: + """Generate the files.""" + config = read_config() + templates = read_templates() + templates.globals.update(generate_uuid=generate_uuid) + + for template in templates.list_templates(): + template = templates.get_template(template) + output = (OUTPUT_DIR / template.name).with_suffix('') + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(template.render(config=config)) + + +if __name__ == '__main__': + generate() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4e859bb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +jinja2 +pyyaml diff --git a/templates/dashboard_cards.yaml.jinja b/templates/dashboard_cards.yaml.jinja new file mode 100644 index 0000000..d01812e --- /dev/null +++ b/templates/dashboard_cards.yaml.jinja @@ -0,0 +1,29 @@ +{% for zone in config.zones %} +### {{ zone.heating_zone_friendly_name }} +type: vertical-stack +cards: + - type: custom:mushroom-title-card + title: {{ zone.heating_zone_entity_name }} + - square: false + columns: 2 + type: grid + cards: + - type: custom:better-thermostat-ui-card + entity: climate.{{ zone.heating_zone_entity_name }} + eco_temperature: {{ config.dashboard.eco_temperature or 5}} + disable_window: {{ (config.dashboard.disable_window or True) | lower }} + disable_summer: {{ (config.dashboard.disable_summer or True) | lower }} + disable_eco: {{ (config.dashboard.disable_eco or True) | lower }} + disable_heat: {{ (config.dashboard.disable_heat or True) | lower }} + disable_off: {{ (config.dashboard.disable_off or True) | lower }} + set_current_as_main: {{ (config.dashboard.set_current_as_main or False) | lower }} + - type: markdown + content: |- + **Schedule**: {% raw %}{{{% endraw %} states('input_text.{{ zone.heating_zone_entity_name }}_event_name') {% raw %}}}{% endraw %} + + **Status**: *{% raw %}{{{% endraw %} states('input_text.{{ zone.heating_zone_entity_name }}_setting_reason') {% raw %}}}{% endraw %}* +{% if not loop.last %} + +--- +{% endif %} +{% endfor %} diff --git a/templates/input_datetimes.yaml.jinja b/templates/input_datetimes.yaml.jinja new file mode 100644 index 0000000..7594f1f --- /dev/null +++ b/templates/input_datetimes.yaml.jinja @@ -0,0 +1,17 @@ +{% for zone in config.zones %} +### {{ zone.heating_zone_friendly_name }} +{{ zone.heating_zone_entity_name }}_event_start: + name: {{ zone.heating_zone_friendly_name }} event start + has_date: true + has_time: true + icon: mdi:clock-outline + +{{ zone.heating_zone_entity_name }}_event_end: + name: {{ zone.heating_zone_friendly_name }} event end + has_date: true + has_time: true + icon: mdi:clock-outline +{% if not loop.last %} + +{% endif %} +{% endfor -%} diff --git a/templates/input_numbers.yaml.jinja b/templates/input_numbers.yaml.jinja new file mode 100644 index 0000000..dad3a1e --- /dev/null +++ b/templates/input_numbers.yaml.jinja @@ -0,0 +1,19 @@ +{% for zone in config.zones %} +### {{ zone.heating_zone_friendly_name }} +{{ zone.heating_zone_entity_name }}_manual_temperature: + name: {{ zone.heating_zone_friendly_name }} manual temperature + min: {{ config.temperature.min or 0}} + max: {{ config.temperature.max or 40 }} + step: {{ config.temperature.step or 1 }} + icon: mdi:temperature-celsius + +{{ zone.heating_zone_entity_name }}_event_temperature: + name: {{ zone.heating_zone_friendly_name }} event temperature + min: {{ config.temperature.min or 0}} + max: {{ config.temperature.max or 40 }} + step: {{ config.temperature.step or 1 }} + icon: mdi:temperature-celsius +{% if not loop.last %} + +{% endif %} +{% endfor -%} diff --git a/templates/input_texts.yaml.jinja b/templates/input_texts.yaml.jinja new file mode 100644 index 0000000..0cfc4cc --- /dev/null +++ b/templates/input_texts.yaml.jinja @@ -0,0 +1,20 @@ +{% for zone in config.zones %} +### {{ zone.heating_zone_friendly_name }} +{{ zone.heating_zone_entity_name }}_event_name: + name: {{ zone.heating_zone_friendly_name }} event name + icon: mdi:card-text-outline + max: 255 + +{{ zone.heating_zone_entity_name }}_event_description: + name: {{ zone.heating_zone_friendly_name }} event description + icon: mdi:card-text-outline + max: 255 + +{{ zone.heating_zone_entity_name }}_setting_reason: + name: {{ zone.heating_zone_friendly_name }} setting reason + icon: mdi:card-text-outline + max: 255 +{% if not loop.last %} + +{% endif %} +{% endfor -%} diff --git a/templates/timers.yaml.jinja b/templates/timers.yaml.jinja new file mode 100644 index 0000000..d3da1d2 --- /dev/null +++ b/templates/timers.yaml.jinja @@ -0,0 +1,35 @@ +{% for zone in config.zones %} +### {{ zone.heating_zone_friendly_name }} +{{ zone.heating_zone_entity_name }}_manual_override_timer: + name: {{ zone.heating_zone_friendly_name }} manual override timer + duration: "00:00:00" + restore: true + icon: mdi:av-timer + +{{ zone.heating_zone_entity_name }}_warmup_timer: + name: {{ zone.heating_zone_friendly_name }} warmup timer + duration: "00:00:00" + restore: true + icon: mdi:av-timer + +{{ zone.heating_zone_entity_name }}_door_or_window_open_timer: + name: {{ zone.heating_zone_friendly_name }} door or window open timer + duration: "00:00:00" + restore: true + icon: mdi:av-timer + +{{ zone.heating_zone_entity_name }}_room_unoccupancy_timer: + name: {{ zone.heating_zone_friendly_name }} room unoccupancy timer + duration: "00:00:00" + restore: true + icon: mdi:av-timer + +{{ zone.heating_zone_entity_name }}_echoblock_timer: + name: {{ zone.heating_zone_friendly_name }} echoblock timer + duration: "00:00:00" + restore: true + icon: mdi:av-timer +{% if not loop.last %} + +{% endif %} +{% endfor -%}