├── .github └── workflows │ └── static.yml ├── README.md ├── blueprints └── automation │ ├── auto-off-timer.yaml │ ├── awtrix_aqi.yaml │ ├── awtrix_battery_monitor.yaml │ ├── awtrix_calendar_countdown.yaml │ ├── awtrix_door_status.yaml │ ├── awtrix_hvac.yaml │ ├── awtrix_indicator.yaml │ ├── awtrix_pollen.yaml │ ├── awtrix_pollen_template.yaml │ ├── awtrix_uv_hum.yaml │ ├── awtrix_weatherflow.yaml │ ├── climate_alert.yaml │ ├── fireplace_sound.yml │ ├── hue-dimmer.yaml │ ├── inovelli_door_cover_notification.yaml │ └── state-monitor.yaml ├── docs ├── sunset.gif └── weather.gif ├── icons ├── dev │ ├── README.md │ ├── ansiPreview.py │ ├── awtrixEmbed.html │ ├── awtrixPreview.html │ ├── gifConverter.py │ ├── gifMaker.py │ └── makeRGB.py ├── door_monitor_blinds │ ├── blinds_close.gif │ ├── blinds_error.gif │ ├── blinds_is_open.gif │ └── blinds_open.gif ├── door_monitor_front_door │ ├── front_door_close.gif │ ├── front_door_error.gif │ ├── front_door_is_open.gif │ └── front_door_open.gif ├── door_monitor_garage │ ├── garage_close.gif │ ├── garage_error.gif │ ├── garage_is_open.gif │ └── garage_open.gif ├── door_monitor_house_shed │ ├── house_shed_close.gif │ ├── house_shed_error.gif │ ├── house_shed_is_open.gif │ └── house_shed_open.gif ├── door_monitor_internal_door │ ├── internal_door_close.gif │ ├── internal_door_error.gif │ ├── internal_door_is_open.gif │ └── internal_door_open.gif ├── door_monitor_sliding_door │ ├── sliding_door_close.gif │ ├── sliding_door_error.gif │ ├── sliding_door_is_open.gif │ └── sliding_door_open.gif ├── door_monitor_window │ ├── window_close.gif │ ├── window_error.gif │ ├── window_is_open.gif │ └── window_open.gif ├── hvac │ ├── cool.gif │ └── heat.gif ├── phone │ ├── README.md │ ├── battery-100.gif │ ├── battery-20.gif │ ├── battery-40.gif │ ├── battery-60.gif │ ├── battery-80.gif │ ├── battery-charging-100.gif │ ├── battery-charging-20.gif │ ├── battery-charging-40.gif │ ├── battery-charging-60.gif │ └── battery-charging-80.gif ├── upload_icon.sh ├── weather │ ├── w-clear-night.gif │ ├── w-cloudy.gif │ ├── w-exceptional.gif │ ├── w-fog.gif │ ├── w-hail.gif │ ├── w-lightning-rainy.gif │ ├── w-lightning.gif │ ├── w-partlycloudy.gif │ ├── w-pouring.gif │ ├── w-rainy.gif │ ├── w-snowy-rainy.gif │ ├── w-snowy.gif │ ├── w-sunny.gif │ ├── w-sunrise.gif │ ├── w-sunset.gif │ ├── w-windy-variant.gif │ └── w-windy.gif └── wind_direction │ ├── wind_e.gif │ ├── wind_n.gif │ ├── wind_ne.gif │ ├── wind_nw.gif │ ├── wind_s.gif │ ├── wind_se.gif │ ├── wind_sw.gif │ └── wind_w.gif ├── resources ├── WeatherPreview1.gif ├── annotated_calendar.png ├── aqi_preview.gif ├── calendar_preview.gif ├── pollen │ ├── icon_flower_frame_0.svg │ ├── icon_flower_frame_1.svg │ ├── icon_flower_frame_2.svg │ ├── icon_flower_frame_3.svg │ ├── icon_flower_frame_4.svg │ ├── icon_grass_frame_0.svg │ ├── icon_grass_frame_1.svg │ ├── icon_grass_frame_2.svg │ ├── icon_grass_frame_3.svg │ ├── icon_grass_frame_4.svg │ ├── icon_tree_frame_0.svg │ ├── icon_tree_frame_1.svg │ ├── icon_tree_frame_2.svg │ ├── icon_tree_frame_3.svg │ ├── icon_tree_frame_4.svg │ ├── icon_weed_frame_0.svg │ ├── icon_weed_frame_1.svg │ ├── icon_weed_frame_2.svg │ ├── icon_weed_frame_3.svg │ ├── icon_weed_frame_4.svg │ └── pollenPreview.gif └── uv_preview.gif └── scripts ├── inovelli_led.yaml ├── inovelli_led_off.yaml ├── inovelli_led_set_defaults.yaml ├── inovelli_led_status_restore.yaml └── inovelli_led_status_start.yaml /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v3 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v3 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v1 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v2 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HomeAssistant 2 | 3 | This archive contains various blueprints for Home Assistant 4 | 5 | |Bluperint|Description|Import|Preview| 6 | |-----------|-----------|-------|----| 7 | | Awtrix 🔋️ Battery Monitor 🪫️|Monitors the battery status of a mobile device|[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_battery_monitor.yaml)| 8 | |Awtrix 🚪️ Door Status Monitor 🔍️|Icon based monitor for Binary/Sensor (open/close) status monitoring. Generally used for doors and windows|[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_door_status.yaml) | 9 | |Awtrix HVAC 🥵 🌡️ 🥶| See the current heating/cooling mode |[![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_hvac.yaml)| 10 | | Awtrix Weather ⛈️ + Forecast + 🌕️ | Super weather blueprint - Conditions + Forecast + Sunrise/Set + MoonPhase| [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_weatherflow.yaml) | ![](./resources/WeatherPreview1.gif) | 11 | | Awtrix UV ☀️ Humidity💧️ | See Humidity & Current UV Index | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_uv_hum.yaml)|![](./resources/uv_preview.gif)| 12 | | Awtrix AQI IQAir/AirNow.gov 🌬️ | Give current AQI + Forecast | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_aqi.yaml)|![](./resources/aqi_preview.gif)| 13 | | Awtrix Pollen 🥀️| Parses IQAir's pollen data into a nice picture. (Only works if IQAir supports pollen forecasts in your region) | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_pollen.yaml)|![](./resources/pollen/pollenPreview.gif)| 14 | | AWTRIX Pollen (Template 🇪🇺️) 🥀️| Allows you to define a custom Template Sensor for Pollen Data | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_pollen_template.yaml)|![](./resources/pollen/pollenPreview.gif)| 15 | | Fireplace 🔥️ Sounds 🎶️ | Play a fireplace sound when fireplace is on| [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Ffireplace_sound.yml)| 16 | | Hue Remote | Simulate the state change feature of a hue remote in software | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fhue-dimmer.yaml) | 17 | | Climate Alert | Get an actionable notification if somebody sets the heat too high | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fclimate_alert.yaml) | 18 | | Calendar 📅️ Indicator 🚥️ | Tie indicator lights to calendar events | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fraw.githubusercontent.com%2Fjeeftor%2FHomeAssistant%2Fmaster%2Fblueprints%2Fautomation%2Fawtrix_indicator.yaml) | ![](resources/calendar_preview.gif) | 19 | 20 | # Getting the ICONS 21 | 22 | ```bash 23 | # If you runt his script it will help upload icons to your Awtrix device 24 | bash -c "$(curl -fsSL https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/icons/upload_icon.sh)" 25 | 26 | # Or you can run 27 | bash -c "$(curl -fsSL https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/icons/upload_icon.sh)" -- IP_ADDRESS_OF_CLOCK 28 | ``` 29 | 30 | ### Innoveli 31 | 32 | It was built out with the following devices: 33 | 34 | * Inovelli Red Dimmer 35 | * August SmartLock Pro (zwave) 36 | * MyQ Garage Door opener 37 | * ZwaveJS 38 | 39 | In order to set the LEDs i forked scripts from @brianhanifin's [Home-Assistant-Config](https://github.com/brianhanifin/Home-Assistant-Config) repo 40 | 41 | 42 | -------------------------------------------------------------------------------- /blueprints/automation/auto-off-timer.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: Switch auto-off timer 3 | description: When a switch is turned on - auto turn off after a specified duration 4 | domain: automation 5 | input: 6 | switch: 7 | name: Switch 8 | description: The switch/plug you want to have turn off after a set time 9 | selector: 10 | entity: 11 | domain: switch 12 | on_duration: 13 | name: When turned on, how long before auot-turned off 14 | selector: 15 | number: 16 | min: 1 17 | max: 600 18 | 19 | 20 | trigger: 21 | - platform: state 22 | entity: !input switch 23 | to: 'on' 24 | for: 25 | minutes: !input on_duration 26 | 27 | action: 28 | service: switch.turn_off 29 | entity_id: !input switch 30 | -------------------------------------------------------------------------------- /blueprints/automation/awtrix_aqi.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: AWTRIX AQI IQAir/AirNow.gov 🌬️ 4 | description: > 5 | Blueprint to show Air Quality Forecast + Air Quality from AirNow.gov + IQAir 6 | 7 | In my limited research IQAir offers a scrapable AQI forecast, but primarily focused on 2.5PPM air quality. AirNow.gov has a worse update rate but it offers an Ozone forecast - so it may be useful to have both sensors up 8 | 9 | In order to use this blueprint you'll have to install a HACS component (see below) to scrape the IQAir webpage 10 | 11 | 12 | ### Requirements 13 | 14 | - [HACS Multiscraper](https://github.com/danieldotnl/ha-multiscrape) 15 | 16 | - [AirNow - or equivalent](https://www.home-assistant.io/integrations/airnow/) 17 | 18 | - IQAir Sensor: (example for chicago below) 19 | 20 | 21 | multiscrape: 22 | - name: IQAir Scraper 23 | resource: https://www.iqair.com/usa/illinois/chicago 24 | scan_interval: 600 25 | log_response: true 26 | sensor: 27 | - unique_id: iq_air_aqi 28 | name: IQAir MultiScrape 29 | icon: >- 30 | {%- if value | int < 50 %} 31 | mdi:gauge-empty 32 | {%- elif value | int < 100 %} 33 | mdi:gauge-low 34 | {%- elif value | int < 200 %} 35 | mdi:gauge 36 | {%- else %} 37 | mdi:gauge-full 38 | {%-endif%} 39 | select_list: ".pollutant-level-wrapper b" 40 | value_template: "{{value.split(',')[4] | int }}" 41 | select: "forecast" 42 | attributes: 43 | - name: Main Pollutant 44 | select: ".aqi-overview-detail__main-pollution-table td:last-child" 45 | - name: Raw Data 46 | select_list: ".pollutant-level-wrapper b" 47 | - name: History 48 | select_list: ".pollutant-level-wrapper b" 49 | value_template: "{{value.split(',')[0:4] | reverse | map('int') | list }}" 50 | - name: Forecast 51 | select_list: ".pollutant-level-wrapper b" 52 | value_template: "{{value.split(',')[5:] | map('int') | list}}" 53 | 54 | domain: automation 55 | input: 56 | awtrix: 57 | name: AWTRIX Device 58 | description: Select the Awtrix light 59 | selector: 60 | device: 61 | integration: mqtt 62 | manufacturer: Blueforcer 63 | model: AWTRIX 3 64 | multiple: true 65 | airnow_sensor: 66 | name: Airnow.Gov AQI 67 | description: IQAir and Airnow have different readings so we use both 68 | selector: 69 | entity: 70 | filter: 71 | domain: 72 | - sensor 73 | iqair_sensor: 74 | name: IQAir Custom multi-scrape sensor 75 | description: >- 76 | You need to configure a custom sensor with the HACS Multiscrape plugin: 77 | 78 | Replace `<>` with correct webpage you want to scrape 79 | 80 | 81 | - name: IQAir Scraper 82 | resource: <> 83 | scan_interval: 600 84 | log_response: true 85 | sensor: 86 | - unique_id: iq_air_aqi 87 | name: IQAir Now 88 | icon: >- 89 | {%- if value | int < 50 %} 90 | mdi:gauge-empty 91 | {%- elif value | int < 100 %} 92 | mdi:gauge-low 93 | {%- elif value | int < 200 %} 94 | mdi:gauge 95 | {%- else %} 96 | mdi:gauge-full 97 | {%-endif%} 98 | select_list: ".pollutant-level-wrapper b" 99 | value_template: "{{value.split(',')[4] | int }}" 100 | select: "forecast" 101 | attributes: 102 | - name: Main Pollutant 103 | select: ".aqi-overview-detail__main-pollution-table td:last-child" 104 | - name: Raw Data 105 | select_list: ".pollutant-level-wrapper b" 106 | - name: History 107 | select_list: ".pollutant-level-wrapper b" 108 | value_template: "{{value.split(',')[0:4] | reverse | map('int') | list }}" 109 | - name: Forecast 110 | select_list: ".pollutant-level-wrapper b" 111 | value_template: "{{value.split(',')[5:] | map('int') | list}}" 112 | selector: 113 | entity: 114 | domain: 115 | - sensor 116 | 117 | app_name: 118 | name: Awtrix Applicaiton name 119 | description: This is the app name listed in the MQTT topic - it should be unique 120 | selector: 121 | text: 122 | default: iq_air 123 | 124 | mode: restart 125 | variables: 126 | device_ids: !input awtrix 127 | app_topic: !input app_name 128 | aqi_icon: >- 129 | {"db": [0, 0, 8, 8, [5029628, 5029628, 0, 0, 0, 0, 0, 0, 5029628, 0, 5029628, 0, 16777215, 0, 0, 0, 5029628, 5029628, 5029628, 16777215, 0, 16777215, 0, 2425087, 5029628, 0, 5029628, 16777215, 0, 16777215, 0, 2425087, 5029628, 0, 5029628, 16777215, 0, 16777215, 0, 2425087, 0, 0, 0, 0, 16777215, 16777215, 16777215, 2425087, 0, 0, 0, 0, 0, 0, 0, 2425087, 327172, 16580100, 16557572, 16515588, 10224284, 7602692, 0, 0]]} 130 | 131 | # Generate history stuff 132 | sensor_prefix: "iqair" 133 | iqair_sensor: !input iqair_sensor 134 | airnow_sensor: !input airnow_sensor 135 | 136 | iq_air_aqi: "{{ states('sensor.iq_air_aqi',-1) | int | round(0)}}" 137 | iq_air_forecast_1: "{{(state_attr(iqair_sensor, 'forecast') | from_json)[0] | int}}" 138 | iq_air_forecast_2: "{{(state_attr(iqair_sensor, 'forecast') | from_json)[1] | int}}" 139 | iq_air_forecast_3: "{{(state_attr(iqair_sensor, 'forecast') | from_json)[2] | int}}" 140 | iq_air_forecast_4: "{{(state_attr(iqair_sensor, 'forecast') | from_json)[3] | int}}" 141 | iq_air_forecast_5: "{{(state_attr(iqair_sensor, 'forecast') | from_json)[4] | int}}" 142 | airnow_aqi: "{{ states(airnow_sensor) | round(0) }}" 143 | 144 | forecast: >- 145 | {%- macro aqi_line(x, value) %} 146 | {"dl": 147 | {%- if value >= 301 -%} 148 | [{{x}},7,{{x+1}},7,"#750000"] 149 | {%- elif value >= 201 -%} 150 | [{{x}},7,{{x+1}},7,"#9a009a"] 151 | {%- elif value >= 151 -%} 152 | [{{x}},7,{{x+1}},7,"#ff0000"] 153 | {%- elif value >= 101 -%} 154 | [{{x}},7,{{x+1}},7,"#ffa500"] 155 | {%- elif value >= 51 -%} 156 | [{{x}},7,{{x+1}},7,"#FFFF00"] 157 | {%- else -%} 158 | [{{x}},7,{{x+1}},7,"#00FF00"] 159 | {%- endif -%} 160 | } 161 | {%- endmacro %} 162 | 163 | {{aqi_line(10,iq_air_aqi)}}, 164 | {{aqi_line(13, iq_air_forecast_1) }}, 165 | {{aqi_line(16, iq_air_forecast_2) }}, 166 | {{aqi_line(19, iq_air_forecast_3) }}, 167 | {{aqi_line(22, iq_air_forecast_4) }}, 168 | {{aqi_line(25, iq_air_forecast_5) }} 169 | 170 | payload: >- 171 | {%- macro get_index_color(value) %} 172 | {%- if value >= 301 %} 173 | {{- "#750000" -}} 174 | {%- elif value >= 201 %} 175 | {{- "#9a009a" -}} 176 | {%- elif value >= 151 %} 177 | {{- "#ff0000" -}} 178 | {%- elif value >= 101 %} 179 | {{- "#ffa500" -}} 180 | {%- elif value >= 51 %} 181 | {{- "#FFFF00" -}} 182 | {%- else %} 183 | {{- "#00FF00" -}} 184 | {%- endif %} 185 | {%- endmacro %} 186 | 187 | {%- set iqair = iq_air_aqi %} 188 | {%- set airnow = airnow_aqi %} 189 | 190 | { 191 | "lifetime": 600, 192 | "lifetimeMode":1, 193 | "draw": [ 194 | {{aqi_icon}}, 195 | {%- if iqair < 10 %} 196 | {"dt": [12,1,"{{iqair}}","{{get_index_color(iqair)}}"]}, 197 | {%- elif iqair < 100 -%} 198 | {"dt": [10,1,"{{iqair}}","{{get_index_color(iqair)}}"]}, 199 | {%- else %} 200 | {"dt": [9,1,"{{iqair}}","{{get_index_color(iqair)}}"]}, 201 | {%- endif %} 202 | 203 | {%- if airnow < 10 %} 204 | {"dt": [21,1,"{{airnow}}","{{get_index_color(airnow)}}"]}, 205 | {%- elif airnow < 100 -%} 206 | {"dt": [21,1,"{{airnow}}","{{get_index_color(airnow)}}"]}, 207 | {%- else %} 208 | {"dt": [21,1,"{{airnow}}","{{get_index_color(airnow)}}"]}, 209 | {%- endif %} 210 | 211 | {{forecast}} 212 | ]} 213 | message_topics: >- 214 | {%- macro get_device_topic(device_id) %} 215 | {{ states((device_entities(device_id) | select('search','device_topic') | list)[0]) }} 216 | {%- endmacro %} 217 | 218 | {%- set ns = namespace(devices=[]) %} 219 | {%- for device_id in device_ids %} 220 | {%- set device=get_device_topic(device_id)|replace(' ','') %} 221 | {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ 'jeef_' ~ app_topic] %} 222 | {%- endfor %} 223 | {{ ns.devices | reject('match','unavailable') | list}} 224 | trigger: 225 | - platform: time_pattern 226 | seconds: /5 227 | condition: [] 228 | action: 229 | - repeat: 230 | for_each: "{{ message_topics }}" 231 | sequence: 232 | - service: mqtt.publish 233 | data: 234 | qos: 0 235 | retain: false 236 | topic: "{{ repeat.item }}" 237 | payload: > 238 | {{payload}} 239 | -------------------------------------------------------------------------------- /blueprints/automation/awtrix_battery_monitor.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: AWTRIX 📱️ Mobile App - Device 🔋️ Battery Monitor 🪫️ 4 | description: > 5 | This blueprint will print out the battery status of a device available to home assistant. 6 | It uses a custom icon set you need to install. 7 | 8 | You can find all the icons here: https://github.com/jeeftor/HomeAssistant/tree/master/icons/phone 9 | 10 | ### On Battery Icons 11 | 12 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53204_icon_thumb.png?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53205_icon_thumb.png?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53207_icon_thumb.png?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53208_icon_thumb.png?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53209_icon_thumb.png?v=1) 13 | 14 | ### Charging Icons 15 | 16 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53212_icon_thumb.gif?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53213_icon_thumb.gif?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53214_icon_thumb.gif?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53215_icon_thumb.gif?v=1)![](https://developer.lametric.com/content/apps/icon_thumbs/53216_icon_thumb.gif?v=1) 17 | 18 | domain: automation 19 | input: 20 | awtrix: 21 | name: AWTRIX Device 22 | description: Select the Awtrix light 23 | selector: 24 | device: 25 | filter: 26 | integration: mqtt 27 | manufacturer: Blueforcer 28 | model: AWTRIX 3 29 | multiple: true 30 | app_name: 31 | name: Awtrix Applicaiton name 32 | description: This is the app name listed in the MQTT topic - it should be unique 33 | selector: 34 | text: 35 | default: phone_battery 36 | battery: 37 | name: Phone or device 38 | description: A phone connected via the mobile app 39 | selector: 40 | entity: 41 | multiple: false 42 | filter: 43 | - integration: mobile_app 44 | device_class: battery 45 | # multiple: false 46 | message_text: 47 | name: Text to Display 48 | description: This is the text to dispally on the screen 49 | selector: 50 | text: 51 | default: iphone 52 | push_icon: 53 | name: Icon Mode 54 | description: > 55 | Please select the pushIcon setting for the icon 56 | 57 | - `0` Icon doesn't move 58 | 59 | - `1` Icon moves with text and will not appear again 60 | 61 | - `2` Icon moves with text but appears again when the text starts to scroll again 62 | selector: 63 | select: 64 | options: 65 | - label: Icon doesn't move (default) 66 | value: "0" 67 | - label: Icon moves with text and will not appear again 68 | value: "1" 69 | - label: Icon moves with text but appears again when the text starts to scroll again 70 | value: "2" 71 | show_below: 72 | name: Show Below x percent 73 | description: Only show the application on the clock when the battery level is below the specified percent 74 | 75 | selector: 76 | number: 77 | min: 1 78 | max: 101 79 | unit_of_measurement: "percent" 80 | 81 | default: 101 82 | 83 | mode: restart 84 | variables: 85 | device_ids: !input awtrix 86 | app_name: !input app_name 87 | show_below: !input show_below 88 | devices_topics: >- 89 | {%- macro get_device_topic(device_id) %} 90 | {{- states((device_entities(device_id) | select('search','device_topic') | list)[0]) }} 91 | {%- endmacro %} 92 | 93 | {%- set ns = namespace(devices=[]) %} 94 | {%- for device_id in device_ids %} 95 | {%- set device=get_device_topic(device_id)|replace(' ','') %} 96 | {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ app_name] %} 97 | {%- endfor %} 98 | {{ ns.devices | reject('match','unavailable') | list}} 99 | 100 | battery_sensor: !input battery 101 | base_icon: "{{states[battery_sensor] }}" 102 | message_text: !input message_text 103 | push_icon: !input push_icon 104 | payload: >- 105 | {"icon":"{{ states[battery_sensor].attributes.icon 106 | | replace('mdi:','') 107 | | replace('90','80') 108 | | replace('70','60') 109 | | replace('50','40') 110 | | replace('30','20')}}", 111 | "text":"{{message_text}}", 112 | "pushIcon":{{push_icon}}, 113 | "progress":"{{states[battery_sensor].state}}","pushIcon":1} 114 | 115 | trigger: 116 | - trigger: time_pattern 117 | minutes: /1 118 | 119 | condition: [] 120 | action: 121 | - repeat: 122 | for_each: "{{ devices_topics }}" 123 | sequence: 124 | - action: mqtt.publish 125 | data: 126 | qos: 0 127 | retain: false 128 | topic: "{{ repeat.item }}" 129 | payload: > 130 | {{payload}} 131 | -------------------------------------------------------------------------------- /blueprints/automation/awtrix_calendar_countdown.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: AWTRIX Calendar 📅️ Countdown Timer 4 | description: > 5 | Monitors the state of a calendar and sets the indicator based on the status of events 6 | 7 | ![](https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/resources/annotated_calendar.png) 8 | domain: automation 9 | input: 10 | awtrix: 11 | name: AWTRIX Device 12 | description: Select the Awtrix light 13 | selector: 14 | device: 15 | filter: 16 | integration: mqtt 17 | manufacturer: Blueforcer 18 | model: AWTRIX 3 19 | multiple: true 20 | calendar: 21 | name: Calendar 22 | selector: 23 | entity: 24 | filter: 25 | domain: calendar 26 | 27 | mode: queued 28 | variables: 29 | calendar: !input calendar 30 | cal_state: "{{states(calendar)}}" 31 | 32 | device_ids: !input awtrix 33 | devices_topics: >- 34 | {%- macro get_device_topic(device_id) %} 35 | {{- states((device_entities(device_id) | select('search','device_topic') | list)[0]) }} 36 | {%- endmacro %} 37 | 38 | {%- set ns = namespace(devices=[]) %} 39 | {%- for device_id in device_ids %} 40 | {%- set device=get_device_topic(device_id)|replace(' ','') %} 41 | {% set ns.devices = ns.devices + [ device ~ '/indicator' ~ light_suffix] %} 42 | {%- endfor %} 43 | {{ ns.devices | reject('match','unavailable') | list}} 44 | 45 | trigger: 46 | - trigger: time_pattern 47 | seconds: /1 48 | id: temporal 49 | 50 | action: 51 | - action: calendar.get_events 52 | target: "{{ calendar }}" 53 | data: 54 | duration: 55 | hours: 8 56 | minutes: 0 57 | secons: 0 58 | response_variable: v2_events 59 | 60 | - action: calendar.list_events 61 | data: 62 | duration: 63 | hours: 8 64 | minutes: 0 65 | seconds: 0 66 | target: 67 | entity_id: "{{ calendar }}" 68 | response_variable: cal_events 69 | - variables: 70 | event_list: > 71 | {%- set time_period_phrases = [{'language': 'en','phrases':{ 'year': ['year', 'years', 'yr'], 'month': ['month', 'months', 'mth'], 'week': ['week', 'weeks', 'wk'], 'day': ['day', 'days', 'day'], 'hour': ['hour', 'hours', 'hr'], 'minute': ['minute', 'minutes', 'min'], 'second': ['second', 'seconds', 'sec'], 'combine': ' and ', 'error': 'Incorrect date'}}]%} 72 | 73 | {# 74 | macro to split a timedelta in years, months, weeks, days, hours, minutes, seconds 75 | used by the relative time plus macro, set up as a separator macro so it can be reused 76 | #} 77 | {%- macro time_split(date, time, compare_date) -%} 78 | {# set defaults for variables #} 79 | {%- set date = date | as_local -%} 80 | {%- set time = time | default(true) | bool(true) -%} 81 | {%- set n = compare_date if compare_date is defined else now() -%} 82 | {%- set n = n if time else today_at() -%} 83 | {%- set a = [n, date] | max -%} 84 | {%- set b = [n, date] | min -%} 85 | {#- set time periods in seconds #} 86 | {%- set m, h, d, w = 60, 3600, 86400, 604800 -%} 87 | {#- set numer of years, and set n to value using this number of years #} 88 | {%- set yrs = a.year - b.year - (1 if a.replace(year=b.year) < b else 0) -%} 89 | {%- set a = a.replace(year=a.year - yrs) -%} 90 | {#- set numer of months, and set n to value using this number of months #} 91 | {%- set mth = (a.month - b.month - (1 if a.day < b.day else 0) + 12) % 12 -%} 92 | {%- set month_new = (((a.month - mth) + 12) % 12) | default(12, true) -%} 93 | {%- set day_max = ((a.replace(day=1, month=month_new) + timedelta(days=31)).replace(day=1) - timedelta(days=1)).day -%} 94 | {%- set a_temp = a.replace(month=month_new, day=[a.day, day_max]|min) -%} 95 | {%- set a = a_temp if a_temp <= a else a_temp.replace(year=a.year-1) -%} 96 | {#- set other time period variables #} 97 | {%- set s = (a - b).total_seconds() -%} 98 | {%- set wks = (s // w) | int -%} 99 | {%- set day = ((s - wks * w) // d) | int -%} 100 | {%- set hrs = ((s - wks * w - day * d) // h) | int -%} 101 | {%- set min = ((s - wks * w - day * d - hrs * h) // m) | int -%} 102 | {%- set sec = (s - wks * w - day * d - hrs * h - min * m) | int -%} 103 | {# output result #} 104 | {{- dict(y=yrs, mo=mth, w=wks, d=day, h=hrs, m=min, s=sec) | to_json -}} 105 | {%- endmacro -%} 106 | 107 | {# macro to output a timedelta in a readable format #} 108 | {%- macro relative_time_plus(date, parts, week, time, abbr, language, compare_date, verbose) -%} 109 | {#- set defaults for input if not entered #} 110 | {%- set date = date | as_datetime if date is string or date is number else date -%} 111 | {%- set compare_date = compare_date if compare_date is defined else now() -%} 112 | {%- set compare_date = compare_date | as_datetime if compare_date is string or compare_date is number else compare_date -%} 113 | {%- set phrases = time_period_phrases -%} 114 | {#- select correct phrases bases on language input #} 115 | {%- set language = language | default() -%} 116 | {%- set languages = phrases | map(attribute='language') | list -%} 117 | {%- set language = iif(language in languages, language, languages | first) -%} 118 | {%- set phr = phrases | selectattr('language', 'eq', language) | map(attribute='phrases') | list | first -%} 119 | {#- check for valid datetime (using as_timestamp) #} 120 | {%- if as_timestamp(date, default='error') != 'error' -%} 121 | {%- set date = date | as_local -%} 122 | {%- set parts = parts | default(1) | int(1) -%} 123 | {%- set week = week | default(true) | bool(true) -%} 124 | {%- set time = time | default(true) | bool(true) -%} 125 | {%- set abbr = abbr | default(false) | bool(false) or verbose | default(false) | bool(false) -%} 126 | {%- set language = language | default('first') -%} 127 | {%- set date = date if time else today_at().replace(year=date.year, month=date.month, day=date.day) -%} 128 | {%- set tp = time_split(date, time, compare_date) | from_json -%} 129 | {#- create mapping #} 130 | {%- set wk = tp.w if week else 0 -%} 131 | {%- set dy = tp.d if week else tp.d + tp.w * 7 -%} 132 | {%- set dur = dict( 133 | yrs = dict(a=tp.y, d=phr.year[2] if abbr else phr.year[1] if tp.y > 1 else phr.year[0]), 134 | mth = dict(a=tp.mo, d=phr.month[2] if abbr else phr.month[1] if tp.mo > 1 else phr.month[0]), 135 | wks = dict(a=wk, d=phr.week[2] if abbr else phr.week[1] if wk > 1 else phr.week[0]), 136 | day = dict(a=dy, d=phr.day[2] if abbr else phr.day[1] if dy > 1 else phr.day[0]), 137 | hrs = dict(a=tp.h, d=phr.hour[2] if abbr else phr.hour[1] if tp.h > 1 else phr.hour[0]), 138 | min = dict(a=tp.m, d=phr.minute[2] if abbr else phr.minute[1] if tp.m > 1 else phr.minute[0]), 139 | sec = dict(a=tp.s, d=phr.second[2] if abbr else phr.second[1] if tp.s > 1 else phr.second[0]) 140 | ) 141 | -%} 142 | {#- find first non zero time period #} 143 | {%- set first = dur.items() | rejectattr('1.a', 'eq', 0) | map(attribute='0') | first -%} 144 | {#- set variable to reject weeks if set and find index of first non zero time period #} 145 | {%- set week_reject = 'wks' if not week -%} 146 | {%- set index = (dur.keys() | reject('eq', week_reject) | list).index(first) -%} 147 | {#-select non zero items based on input #} 148 | {%- set items = (dur.keys() | reject('eq', week_reject) | list)[index:index + parts] -%} 149 | {%- set selection = dur.items() | selectattr('0', 'in', items) | rejectattr('1.a', 'eq', 0) | list -%} 150 | {#- create list of texts per selected time period #} 151 | {%- set ns = namespace(text = []) -%} 152 | {%- for i in selection -%} 153 | {%- set ns.text = ns.text + [ i[1].a ~ ' ' ~ i[1].d] -%} 154 | {%- endfor -%} 155 | {#- join texts in a string, using phr.combine for the last item #} 156 | {{- ns.text[:-1] | join(', ') ~ phr.combine ~ ns.text[-1] if ns.text | count > 1 else ns.text | first -}} 157 | {%- else -%} 158 | {{- phr.error -}} 159 | {%- endif -%} 160 | {%- endmacro -%} 161 | {%- macro event_to_dict(event) %} 162 | {%- set now_ts = now() | as_timestamp -%} 163 | {%- set start_ts = event.start | as_timestamp | int %} 164 | {%- set end_ts = event.end | as_timestamp | int %} 165 | {%- set rel_start = relative_time_plus(event.start,3) %} 166 | {%- set rel_end = relative_time_plus(event.end,3) %} 167 | {%- set has_started = iif(now_ts > start_ts,1,0) -%} 168 | {%- set has_ended = iif(now_ts > end_ts,1,0) -%} 169 | {%- set start_sec = ((start_ts - now_ts) ) | int -%} 170 | {%- set end_sec = ((end_ts - now_ts) ) | int -%} 171 | {%- set start_min = ((start_ts - now_ts) / 60) | int -%} 172 | {%- set end_min = ((end_ts - now_ts) / 60 ) | int -%} 173 | 174 | {%- set starts_in_1 = iif((0 < start_min) and (start_min <= 1),1,0) -%} 175 | {%- set starts_in_5 = iif((0 < start_min) and (start_min <= 5),1,0) -%} 176 | {%- set starts_in_10 = iif((0 < start_min) and (start_min <= 10),1,0) -%} 177 | { 178 | "summary": "{{event.summary}}" 179 | ,"location":{{ ((event.location | replace('\n', ' ')) | to_json)}} 180 | ,"start_ts":{{start_ts}} 181 | ,"end_ts":{{end_ts}} 182 | ,"rel_start":"{{rel_start}}" 183 | ,"rel_end":"{{rel_end}}" 184 | ,"has_started":{{has_started}} 185 | ,"has_ended":{{has_ended}} 186 | ,"start_sec":{{ start_sec}} 187 | ,"end_sec":{{ end_sec}} 188 | ,"start_min":{{ start_min}} 189 | ,"end_min":{{ end_min}} 190 | 191 | ,"starts_in_1": {{starts_in_1}} 192 | ,"starts_in_5": {{starts_in_5}} 193 | ,"starts_in_10": {{starts_in_10}} 194 | 195 | } 196 | {%- endmacro %} 197 | {%- set ns = namespace(parsed_events=[]) %} 198 | {%- for event in cal_events.events %} 199 | {%- set e_dict = event_to_dict(event) %} 200 | 201 | {%- set ns.parsed_events = ns.parsed_events + [ (e_dict | from_json )] %} 202 | {%- endfor -%} 203 | {{ns.parsed_events | sort(attribute='start_ts')}} 204 | locations: >- 205 | {%- set ns = namespace(locations=[]) %} 206 | {%- for event in cal_events.events %} 207 | {% set loc = {"locations": ((event.location | replace('\n', ' ')) | to_json) } %} 208 | {%- set ns.locations = ns.locations + [loc] + [loc] %} 209 | {%- endfor -%} 210 | {{ns.locations}} 211 | 212 | current_events: >- 213 | {%- set ns = namespace(current_events=[]) %} 214 | {%- for event in event_list %} 215 | 216 | {%- if event['start_min'] <= 0 %} 217 | {%- set ns.current_events = ns.current_events + [ event ] %} 218 | {%- endif %} 219 | {%- endfor -%} 220 | {{ns.current_events}} 221 | current_events_count: "{{current_events | length }}" 222 | current_events_text: "{{ current_events | map(attribute='summary') | join(' / ') }}" 223 | time_remaining: >- 224 | {%- if current_events_count > 0 %} 225 | {%- macro convert_s(seconds) -%} 226 | {% set hours = (seconds / 60 / 60 ) % 60 -%} 227 | {% set remaining_minutes = (seconds / 60) % 60 -%} 228 | {% set seconds = (seconds % 60) -%} 229 | {{ "%02d:%02d:%02d" | format(hours, remaining_minutes, seconds) }} 230 | {%- endmacro %} 231 | {{convert_s(current_events[0]['end_sec'])}} 232 | {% endif %} 233 | 234 | progress: >- 235 | {%- set total_time = (-1 * current_events[0]['start_min']) + current_events[0]['end_min'] %} 236 | {%- set done = (-1 * current_events[0]['start_min']) %} 237 | {{done/total_time * 100}} 238 | 239 | payload: >- 240 | 241 | {"stack":false, "text":"{{time_remaining}}", "progress":"{{progress}}" } 242 | 243 | - action: mqtt.publish 244 | data: 245 | qos: 0 246 | topic: awtrix_1/notify 247 | payload: >- 248 | {{payload}} 249 | -------------------------------------------------------------------------------- /blueprints/automation/awtrix_hvac.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: AWTRIX HVAC 🥵 🌡️ 🥶 4 | description: > 5 | Monitor the status of your HVAC system with Awtrix 6 | 7 | 8 | This automation requires two icons to exist (you can select the name below). 9 | 10 | 11 | If you want to use the icons I developed run: 12 | 13 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53270_icon_thumb.gif?v=2)![](https://developer.lametric.com/content/apps/icon_thumbs/53271_icon_thumb.gif?v=2) 14 | 15 | bash -c "$(curl -fsSL https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/icons/upload_icon.sh)" 16 | 17 | And select the IP_ADDRESS of your awtrix device and `hvac` as the icon set to upload 18 | 19 | domain: automation 20 | input: 21 | awtrix: 22 | name: AWTRIX Device 23 | description: Select the Awtrix light 24 | selector: 25 | device: 26 | filter: 27 | integration: mqtt 28 | manufacturer: Blueforcer 29 | model: AWTRIX 3 30 | multiple: true 31 | hvac: 32 | name: Climate Device / HVAC 33 | description: HVAC Device (Ecobee, Nest etc) 34 | selector: 35 | entity: 36 | filter: 37 | domain: climate 38 | heat_icon: 39 | name: Heating icon name 40 | selector: 41 | text: 42 | default: heat 43 | cool_icon: 44 | name: Cooling icon name 45 | selector: 46 | text: 47 | default: cool 48 | app_name: 49 | name: Awtrix Applicaiton name 50 | description: This is the app name listed in the MQTT topic - it should be unique 51 | selector: 52 | text: 53 | default: jeef_hvac 54 | 55 | mode: restart 56 | variables: 57 | device_ids: !input awtrix 58 | app_name: !input app_name 59 | devices_topics: >- 60 | {%- macro get_device_topic(device_id) %} 61 | {{- states((device_entities(device_id) | select('search','device_topic') | list)[0]) }} 62 | {%- endmacro %} 63 | 64 | {%- set ns = namespace(devices=[]) %} 65 | {%- for device_id in device_ids %} 66 | {%- set device=get_device_topic(device_id)|replace(' ','') %} 67 | {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ app_name] %} 68 | {%- endfor %} 69 | {{ ns.devices | reject('match','unavailable') | list}} 70 | 71 | climate: !input hvac 72 | mode: "{{states(climate)}}" 73 | target_temp: "{{ iif(state_attr('climate.downstairs', 'temperature'),state_attr('climate.downstairs', 'temperature'),0,state_attr('climate.downstairs', 'current_temperature')) | round(0)}}" 74 | temp_current: "{{state_attr(climate, 'current_temperature')}}" 75 | temp_current_ceil: "{{ temp_current | round(0,'ceil') }}" 76 | temp_current_floor: "{{ temp_current | round(0,'floor') }}" 77 | 78 | is_running: "{{ not state_attr('climate.downstairs', 'temperature') is none }}" 79 | is_ac_on: "{{ mode == 'cool' and (target_temp < temp_current) }}" 80 | is_heat_on: "{{ mode == 'heat' and (target_temp > temp_current) }}" 81 | 82 | payload: >- 83 | {%- if not is_running %} 84 | {} 85 | {%- elif mode == 'heat' and is_heat_on%} 86 | { "icon": "heat", 87 | "text": [ 88 | {"t":"{{temp_current_floor}}", "c":"#ffffff"}, 89 | {"t":">","c":"#9c9d97"}, 90 | {"t":"{{target_temp}}", "c":"#ffffff"} 91 | ] } 92 | {% elif mode == 'cool' and is_ac_on %} 93 | { "icon": "cool", 94 | "text": [ 95 | {"t":"{{temp_current_ceil}}", "c":"#ffffff"}, 96 | {"t":">","c":"#9c9d97"}, 97 | {"t":"{{target_temp}}", "c":"#ffffff"} 98 | ] } 99 | {% else %} 100 | {} 101 | {% endif %} 102 | 103 | trigger: 104 | - trigger: time_pattern 105 | seconds: /5 106 | 107 | condition: [] 108 | action: 109 | - repeat: 110 | for_each: "{{ devices_topics }}" 111 | sequence: 112 | - action: mqtt.publish 113 | data: 114 | qos: 0 115 | retain: false 116 | topic: "{{ repeat.item }}" 117 | payload: > 118 | {{payload}} 119 | -------------------------------------------------------------------------------- /blueprints/automation/awtrix_uv_hum.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: AWTRIX UV☀️ Humidity💧️ 4 | description: > 5 | Display both a humidity value and the UV index on the awtrix. 6 | 7 | ### Notes about UV 8 | 9 | The Weatherflow UV readings seem to be instantaneous so it may be best to create an average sensor for the readings if you don't want to see wild fluctuations. You can do this by adding the following sensor to your `sensors:` block in `configuration.yaml` 10 | 11 | - platform: statistics 12 | name: "Weatherflow uv index 10 minute average" 13 | entity_id: sensor.weatherflow_uv_index 14 | state_characteristic: mean 15 | max_age: 16 | minutes: 10 17 | 18 | domain: automation 19 | input: 20 | awtrix: 21 | name: AWTRIX Device 22 | description: Select the Awtrix light 23 | selector: 24 | device: 25 | filter: 26 | integration: mqtt 27 | manufacturer: Blueforcer 28 | model: AWTRIX 3 29 | multiple: true 30 | hum: 31 | name: Humidity Sensor 32 | description: > 33 | 34 | A sensor that provides a humidity value 35 | 36 | - `sensor.weatherflow_relative_humidity` 37 | 38 | - `sensor.openweathermap_humidity` 39 | 40 | selector: 41 | entity: 42 | uv: 43 | name: UV Index 44 | description: > 45 | 46 | A sesnor that provides a uv index like: 47 | 48 | - `sensor.weatherflow_uv_index` 49 | 50 | - `sensor.openweathermap_uv_index` 51 | 52 | selector: 53 | entity: 54 | show_forecast: 55 | name: Show Hourly UV Forecast 56 | description: If you have a valid data source to obtain an hourly UV forecast you should use it 57 | selector: 58 | boolean: 59 | default: true 60 | 61 | forecast: 62 | name: Hourly UV Forecast 63 | description: > 64 | Select a forecast with `uv_index` available 65 | 66 | This integration has been tested with: 67 | 68 | - HACS [Weatherflow](https://github.com/briis/hass-weatherflow) integration 69 | 70 | - NOTE: [Openweather](https://www.home-assistant.io/integrations/openweathermap/) DOES NOT HAVE AN HOURLY UV FORECAST 71 | selector: 72 | entity: 73 | filter: 74 | domain: 75 | - weather 76 | multiple: false 77 | 78 | mode: restart 79 | variables: 80 | app_topic: jeef_uv_hum 81 | device_ids: !input awtrix 82 | devices_topics: >- 83 | {%- macro get_device_topic(device_id) %} 84 | {{- states((device_entities(device_id) | select('search','device_topic') | list)[0]) }} 85 | {%- endmacro %} 86 | 87 | {%- set ns = namespace(devices=[]) %} 88 | {%- for device_id in device_ids %} 89 | {%- set device = get_device_topic(device_id)|replace(' ','') %} 90 | {% set ns.devices = ns.devices + [ device ~ '/custom/' ~ app_topic] %} 91 | {%- endfor %} 92 | {{ ns.devices | reject('match','unavailable') | list}} 93 | 94 | humidity_sensor: !input hum 95 | uv_sensor: !input uv 96 | 97 | humidity: "{{states(humidity_sensor) | round}}" 98 | uv: "{{states(uv_sensor) | round}}" 99 | forecast_var: !input forecast 100 | forecast_field: "uv_index" 101 | show_forecast: !input show_forecast 102 | 103 | trigger: 104 | - trigger: time_pattern 105 | minutes: "/5" 106 | 107 | condition: [] 108 | action: 109 | - action: weather.get_forecasts 110 | target: 111 | entity_id: "{{forecast_var}}" 112 | data: 113 | type: hourly 114 | response_variable: forecast_response 115 | 116 | - repeat: 117 | for_each: "{{ devices_topics }}" 118 | sequence: 119 | - action: mqtt.publish 120 | data: 121 | qos: 0 122 | retain: false 123 | topic: "{{ repeat.item }}" 124 | payload: > 125 | {%- set forecast = forecast_response[forecast_var]['forecast'] -%} 126 | 127 | {%- macro get_uv_color(index) -%} {%- if index | float > 10 -%} 128 | #EE82EE 129 | {%- elif index | float >= 8 -%} 130 | #FF0000 131 | {%- elif index | float >= 6 -%} 132 | #FF8C00 133 | {%- elif index | float >= 3 -%} 134 | #FFFF00 135 | {%- else -%} 136 | #00FF00 137 | {%- endif -%} 138 | {%- endmacro -%} 139 | 140 | {%- macro draw_uv_forecast_h(x,y,hours,height) %} 141 | {%- for hour in range(hours) %} 142 | {%- set uv_index = forecast[hour][forecast_field] |int %} 143 | {%- set uv_color = get_uv_color(uv_index) %} 144 | {%- if height == 0 %} 145 | {"dp": [{{x+hour}},{{y}},"{{uv_color }}"]} 146 | {%- else %} 147 | {"dl": [{{x+hour}},{{y}},{{x+hour}},{{y - height}},"{{uv_color}}"]} 148 | {%- endif %} 149 | {%- if hour+1 != hours %},{%endif%} 150 | {%- endfor %} 151 | {%- endmacro %} 152 | 153 | {%- macro get_uv_color(index) -%} {%- if index | float > 10 -%} 154 | #EE82EE 155 | {%- elif index | float >= 8 -%} 156 | #FF0000 157 | {%- elif index | float >= 6 -%} 158 | #FF8C00 159 | {%- elif index | float >= 3 -%} 160 | #FFFF00 161 | {%- else -%} 162 | #00FF00 163 | {%- endif -%} 164 | {%- endmacro -%} 165 | {% set uv_color = get_uv_color(uv) %} 166 | 167 | 168 | 169 | 170 | {#- Calculate UV Offset -#} 171 | {%- if uv|float >= 10-%} 172 | {%- set uv_offset = "23,1" -%} 173 | {%- else %} 174 | {%- set uv_offset = "25,1" -%} 175 | {%- endif %} 176 | 177 | {% macro draw_uv() %} 178 | {"dt":[{{uv_offset}},"{{uv}}","{{uv_color}}"]} 179 | {% endmacro %} 180 | 181 | {%- macro draw_u(x,y,color) -%} 182 | {"dl":[{{x}},{{y}},{{x}},{{y+2}},"{{color}}"]}, 183 | {"dl":[{{x}},{{y+2}},{{x+2}},{{y+2}},"{{color}}"]}, 184 | {"dl":[{{x+2}},{{y}},{{x+2}},{{y+2}},"{{color}}"]} 185 | {%- endmacro -%} 186 | 187 | {%- macro draw_v(x,y,color) -%} 188 | {"dl":[{{x}},{{y}},{{x+1}},{{y+2}},"{{color}}"]}, 189 | {"dl":[{{x+2}},{{y}},{{x+1}},{{y+2}},"{{color}}"]} 190 | {%- endmacro -%} 191 | 192 | { 193 | "draw": [ 194 | {"db":[0,1,8,8,[0,0,15657727,0,0,0,0,65536,0,16777215,15132415,12961791,0,0,0,0,16711422,15657982,15198207,12895742,11315711,65536,0,0,15723775,15132415,15066623,11381503,11185150,0,256,0,15592191,12961534,12961535,11315452,8224464,0,0,2,2,11315966,11315965,8158669,0,0,0,0,0,0,0,0,256,0,0,0,0,65793,0,65536,0,2,256,2]]}, 195 | {"dt":[6,2,"{{humidity}}%"]}, 196 | {{draw_u(19,1,"#00ffaa")}}, 197 | {{draw_v(19,5,"#00ffaa")}}, 198 | {%- if show_forecast %}{{ draw_uv_forecast_h(22,7,8,0) }},{%- endif %} 199 | {{draw_uv()}} 200 | ], 201 | "lifetime": 620, 202 | "lifetimeMode":1 203 | } 204 | -------------------------------------------------------------------------------- /blueprints/automation/climate_alert.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: Climate Alert 3 | description: Notification 4 | domain: automation 5 | input: 6 | climate: 7 | name: Climate Entity 8 | selector: 9 | entity: 10 | filter: 11 | domain: climate 12 | 13 | # mode_heat: 14 | # name: Mode Heat 15 | # default: false 16 | # description: Run the automation when the heat is on 17 | # selector: 18 | # boolean: 19 | # mode_cool: 20 | # name: Mode Cool 21 | # default: false 22 | # description: Run the automation when the aircondition is on 23 | # selector: 24 | # boolean: 25 | # mode_heat_cool: 26 | # name: Mode Heat_cool 27 | # default: false 28 | # description: Run the automation when hvac mode is automatic (heat/cool) 29 | # selector: 30 | # boolean: 31 | temp: 32 | name: Trigger Temp 33 | description: Temp value to trigger the automation. In `heat` mode the target temp must be above this value to trigger the automation, where as in `cool` mode it must be below. 34 | default: 70 35 | selector: 36 | number: 37 | min: 32 38 | max: 100 39 | unit_of_measurement: "°" 40 | mode: box 41 | mode: 42 | description: "Select whether to trigger when Heating or Cooling" 43 | selector: 44 | select: 45 | options: 46 | - "heat" 47 | - "cool" 48 | 49 | notify_device: 50 | name: Device to notify 51 | description: Device needs to run the official Home Assistant app to receive notifications 52 | selector: 53 | device: 54 | filter: 55 | integration: mobile_app 56 | 57 | variables: 58 | mode: !input mode 59 | temp: !input temp 60 | climate: !input climate 61 | 62 | trigger: 63 | - trigger: state 64 | entity_id: !input climate 65 | attribute: temperature 66 | 67 | action: 68 | - alias: "choose alias (name)" 69 | choose: 70 | - conditions: 71 | - condition: or 72 | conditions: 73 | - alias: "Too Hot - Heat" 74 | condition: template 75 | value_template: "{{ mode=='heat' and (states(climate) == 'heat') and (state_attr(climate, 'temperature') > temp)}}" 76 | - alias: "Too Hot - Heat + AC" 77 | condition: template 78 | value_template: "{{ mode=='heat' and (states(climate) == 'heat_cool') and (state_attr(climate, 'temperature') > temp)}}" 79 | sequence: 80 | - device_id: !input notify_device 81 | domain: mobile_app 82 | type: notify 83 | message: >- 84 | 🔥️ Heat set to {{ int(state_attr('climate.downstairs','temperature'))}}° 🌡️ 85 | data: 86 | actions: 87 | - action: DO_NOTHING 88 | title: Leave it as-is! 89 | icon: sfsymbols:checkmark.circle 90 | destructive: false 91 | - action: SET_TO_TEMP 92 | title: Set it to {{ temp }} 93 | destructive: false 94 | icon: sfsymbols::arrow.down.circle 95 | - conditions: 96 | - condition: or 97 | conditions: 98 | - alias: "Too Cold - AC" 99 | condition: template 100 | value_template: "{{ mode=='cool' and (states(climate) == 'cool') and (state_attr(climate, 'temperature') < temp)}}" 101 | - alias: "Too Cold - AC + Heat" 102 | condition: template 103 | value_template: "{{ mode=='cool' and (states(climate) == 'heat_cool') and (state_attr(climate, 'temperature') < temp)}}" 104 | sequence: 105 | - device_id: !input notify_device 106 | domain: mobile_app 107 | type: notify 108 | message: >- 109 | 🥶️ AC set to {{ int(state_attr('climate.downstairs','temperature'))}}° ❄️ 110 | data: 111 | actions: 112 | - action: DO_NOTHING 113 | title: Leave it as-is! 114 | icon: sfsymbols:checkmark.circle 115 | destructive: false 116 | - action: SET_TO_TEMP 117 | title: Set it to {{ temp }} 118 | destructive: false 119 | icon: sfsymbols::arrow.up.circle 120 | - alias: Wait for Input 121 | wait_for_trigger: 122 | - trigger: event 123 | event_type: mobile_app_notification_action 124 | event_data: 125 | action: LEAVE_AS_IS 126 | - trigger: event 127 | event_type: mobile_app_notification_action 128 | event_data: 129 | action: SET_TO_TEMP 130 | - alias: "Change Climate" 131 | choose: 132 | - alias: "Set to target temp" 133 | conditions: "{{ wait.trigger.event.data.action == 'SET_TO_TEMP' }}" 134 | sequence: 135 | - action: climate.set_temperature 136 | target: 137 | entity_id: !input climate 138 | data: 139 | temperature: !input temp 140 | - alias: "Target minus 1" 141 | conditions: "{{ wait.trigger.event.data.action == 'SET_TO_TEMP_MINUS_ONE' }}" 142 | sequence: 143 | - action: climate.set_temperature 144 | target: 145 | entity_id: !input climate 146 | data: 147 | temperature: "{{ int(temp) - 1 }}" 148 | - alias: "Target plus 1" 149 | conditions: "{{ wait.trigger.event.data.action == 'SET_TO_TEMP_PLUS_ONE' }}" 150 | sequence: 151 | - action: climate.set_temperature 152 | target: 153 | entity_id: !input climate 154 | data: 155 | temperature: "{{ int(temp) + 1 }}" 156 | 157 | # - conditions: 158 | # condition 159 | # sequence: 160 | # action 161 | # default: 162 | # action 163 | mode: restart 164 | -------------------------------------------------------------------------------- /blueprints/automation/fireplace_sound.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: Fireplace 🔥️ Sounds 🎶️ 4 | description: > 5 | This blueprint will let you tie together a speaker(s) and a fireplace so you can get nice fireplace sounds when the fire is on 6 | domain: automation 7 | input: 8 | fireplace_sensor: 9 | name: Fireplace Sensor 10 | description: Sensor that has the on/off state of the fireplace 11 | selector: 12 | entity: 13 | filter: 14 | domain: 15 | - binary_sensor 16 | - input_boolean 17 | player: 18 | name: Where to play fireplace sounds 19 | selector: 20 | entity: 21 | filter: 22 | domain: 23 | - media_player 24 | start_vol: 25 | name: Volume settings 26 | description: At what volume should the fire sound play 27 | selector: 28 | number: 29 | min: 0 30 | max: 1 31 | step: .1 32 | default: .27 33 | end_vol: 34 | name: Volume settings 35 | description: What level do you wish to return the volume to after you stop playing the fire sounds. 36 | selector: 37 | number: 38 | min: 0 39 | max: 1 40 | step: .1 41 | default: .27 42 | 43 | media: 44 | name: Select a sound 45 | description: Due to limitations in HA you will also have to re-select the media player above 46 | selector: 47 | media: 48 | mode: queued 49 | 50 | variables: 51 | fireplace_sensor: !input fireplace_sensor 52 | fireplace_state: "{{states(fireplace_sensor)}}" 53 | is_fireplace_on: "{{is_state(fireplace_sensor,'on')}}" 54 | 55 | media: !input media 56 | player: !input player 57 | start_vol: !input start_vol 58 | end_vol: !input end_vol 59 | fire_content_id: "{{ media['media_content_id'] }}" 60 | fire_content_substr: "{{ media['media_content_id'] | replace('media-source://media_source/','') }}" 61 | current_content_id: "{{ state_attr(player,'media_content_id') }}" 62 | is_fire_sound: > 63 | {%- if current_content_id %} 64 | {{ fire_content_substr in current_content_id }} 65 | {%- else %} 66 | {{ false }} 67 | {%- endif %} 68 | 69 | is_playing: "{{is_state(player,'playing')}}" 70 | is_playing_fire_sound: "{{ is_playing and is_fire_sound}}" 71 | action_to_take: >- 72 | {%- if is_fireplace_on and not is_playing %} 73 | {%- if trigger.id == 'stopped' and is_fire_sound %} 74 | do_nothing 75 | {%- else %} 76 | play_music 77 | {%- endif %} 78 | {#- If fireplace is off and we were playing the fire music -#} 79 | {%- elif not is_fireplace_on and (is_playing and is_fire_sound) %} 80 | stop_music 81 | {%- else %} 82 | do_nothing 83 | {%- endif %} 84 | 85 | last_state: > 86 | {{ trigger }} 87 | bool1: "{{ 'from_state' in trigger }}" 88 | bool2: "{{ 'entity_id' in trigger }}" 89 | bool3: "{{ 'id' in trigger }}" 90 | bool4: "{{ 'to_state' in trigger }}" 91 | 92 | dev: > 93 | {%- if 'from_state' in trigger -%} 94 | From: {{trigger.from_state}} 95 | {%- endif -%} 96 | {%- if 'to_state' in trigger -%} 97 | To: {{trigger.to_state}} 98 | {%- endif -%} 99 | trigger: 100 | - trigger: state 101 | entity_id: !input fireplace_sensor 102 | id: fireplace_state 103 | - trigger: state 104 | # Stop playing Music 105 | entity_id: !input player 106 | id: music_stop 107 | from: "playing" 108 | for: 109 | seconds: 1 110 | # - platform: time_pattern 111 | # id: time_pattern 112 | # seconds: /5 113 | # minutes: /5 114 | # - platform: state 115 | # # Start playing music... not sure why i need this one? 116 | # entity_id: !input player 117 | # id: media_start 118 | # to: "playing" 119 | 120 | # Don't do anything for do_nothing 121 | condition: [] 122 | 123 | action: 124 | # Debug stuff 125 | - action: mqtt.publish 126 | data: 127 | qos: 0 128 | retain: false 129 | topic: "dev" 130 | payload: > 131 | bool1: {{bool1}} 132 | bool2: {{bool2}} 133 | bool3: {{bool3}} 134 | bool4: {{bool4}} 135 | 136 | - action: mqtt.publish 137 | data: 138 | qos: 0 139 | retain: false 140 | topic: "dev" 141 | payload: "{{dev}}" 142 | # payload: "{{trigger | to_json}}" 143 | 144 | - choose: 145 | # - conditions: 146 | # - condition: trigger 147 | # id: stop_5_seconds 148 | # sequence: [] 149 | # # Not playing music, and the fire is on 150 | - conditions: 151 | - condition: template 152 | value_template: "{{action_to_take == 'play_music'}}" 153 | sequence: 154 | # Unjoin the groups 155 | - action: media_player.unjoin 156 | target: 157 | entity_id: "{{player}}" 158 | # set volume 159 | - action: media_player.volume_set 160 | data: 161 | volume_level: "{{start_vol}}" 162 | target: 163 | entity_id: "{{player}}" 164 | # play 165 | - action: media_player.play_media 166 | data: 167 | media_content_id: "{{fire_content_id}}" 168 | media_content_type: music 169 | target: 170 | entity_id: "{{player}}" 171 | # Repeat ON 172 | - action: media_player.repeat_set 173 | data: 174 | repeat: one 175 | target: 176 | entity_id: "{{player}}" 177 | 178 | # Stop the MUSIC 179 | - conditions: 180 | - condition: template 181 | value_template: "{{action_to_take == 'stop_music'}}" 182 | sequence: 183 | # Pause/Stop the current "stuff" the current 184 | - action: media_player.media_stop 185 | target: 186 | entity_id: "{{player}}" 187 | - action: media_player.volume_set 188 | data: 189 | volume_level: "{{end_vol}}" 190 | target: 191 | entity_id: "{{player}}" 192 | 193 | - conditions: 194 | - condition: template 195 | value_template: "{{action_to_take == 'do_nothing'}}" 196 | sequence: [] 197 | -------------------------------------------------------------------------------- /blueprints/automation/hue-dimmer.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | blueprint: 3 | name: Hue Dimmer 🎛️ Hue Dimmer 🔅️ Action 💡️ (Zigbee2MQTT) 4 | description: > 5 | ## Overview 6 | 7 | This blueprint will enable a [Hue Dimmer Remote](https://www.zigbee2mqtt.io/devices/324131092621.html) (connected via Zigbee2MQTT) to function as a scene controller. 8 | 9 | ``` 10 | ┌───────┐ 11 | │┌─────┐│ 12 | ││ ON ││ Press ON to cycle through Scenes and Scripts 13 | │├─────┤│ 14 | ││ * ││ 15 | │├─────┤│ 16 | ││ * ││ 17 | │├─────┤│ 18 | ││ OFF ││ Press OFF to cycle through Scenes and Scripts 19 | │└─────┘│ 20 | └───────┘ 21 | ``` 22 | 23 | ## Requirements 24 | 25 | The main requirement of this blueprint is the existance of an **Input Text Helper** named: `zigbee2mqtt_json` 26 | 27 | ## Details 28 | 29 | At each press of the `ON` or `OFF` button the automation will send a request to change to a specific scene as well as launch a specific script. If either of these scenes or scripts exists they will be activated. 30 | 31 | Both Scenes an Scripts follow a specific naming convention: 32 | 33 | For example if you have a device called `Basement Remote` (in Z2M) and it will look for the following scenes/scripts: 34 | 35 | 36 | When iterating through presses of the `ON` button: 37 | 38 | 39 | - `script.basement_remote_on_0` / `scene.basement_remote_on_0` 40 | - `script.basement_remote_on_1` / `scene.basement_remote_on_1` 41 | - `script.basement_remote_on_2` / `scene.basement_remote_on_2` 42 | 43 | 44 | When iterating through the `OFF` button: 45 | 46 | - `script.basement_remote_off_0` / `scene.basement_remote_off_0` 47 | - `script.basement_remote_off_1` / `scene.basement_remote_off_1` 48 | - `script.basement_remote_off_2` / `scene.basement_remote_off_2` 49 | 50 | **⚠️ NOTE ⚠️: The blueprint requires the following input text helper: `input_text.zigbee2mqtt_json` to exist and have a default value of `{}`** 51 | 52 | domain: automation 53 | # icon: "mdi:remote" 54 | input: 55 | device_topic: 56 | name: Z2M Device Name 57 | description: "This will be used to calculate the topic that Z2M is publishg to. So for example if you have the topic `zigbee2mqtt/Basement Remote/action` your device name woudl be: `Basement Remote`" 58 | selector: 59 | text: 60 | on_count: 61 | name: ON count 62 | description: > 63 | The maximum number of scenes available to cycle through. 64 | 65 | 66 | **⚠️NOTE:⚠️** this is a `0` based value so if you have `5` scenes it will look for scenes `0` through `4` 67 | default: 5 68 | selector: 69 | number: 70 | min: 1 71 | max: 10 72 | step: 1 73 | mode: box 74 | off_count: 75 | name: OFF count 76 | description: > 77 | The maximum number of scenes available to cycle through. 78 | 79 | 80 | **⚠️NOTE:⚠️** this is a `0` based value so if you have `5` scenes it will look for scenes `0` through `4` 81 | default: 5 82 | selector: 83 | number: 84 | min: 1 85 | max: 10 86 | step: 1 87 | mode: box 88 | 89 | mode: queued 90 | max: 10 91 | 92 | variables: 93 | # input_text.zigbee2mqtt_json 94 | # current_scene: "{{ state_attr('input_number.remote_basement_scene_selector', 'value') | int }}" 95 | device_topic: !input device_topic 96 | # my_light: !input light_entity 97 | 98 | # Parse the existing helper - if its empty, unset or actually has valid data this should work 99 | stored_json_text: '{{ iif(states(''input_text.zigbee2mqtt_json'') in [None, '''',''{}''], dict({device_topic:{"state":"off","scene":0}}) | to_json, states(''input_text.zigbee2mqtt_json'')) }}' 100 | 101 | # Extract light state 102 | light_state: "{{ (stored_json_text.get(device_topic, dict())).get('state','off') }}" 103 | # Calculate current scene ID a s well as the "rollover mod values for an on or off press" 104 | current_scene: "{{ (stored_json_text.get(device_topic, dict())).get('scene',0) | int }}" 105 | 106 | # Assuming a secondary On or Off is pressed - calculate what the new ID woudl be taking into account rollover 107 | on_count: !input on_count 108 | off_count: !input off_count 109 | on_scene_id: "{{ ((current_scene + 1) % (on_count | int)) | string}}" 110 | off_scene_id: "{{ ((current_scene + 1) % (off_count | int)) | string}}" 111 | 112 | # Extract the command from MQTT payload - and construct the trigger key which is made up 113 | # of the last stored light state + the command 114 | command: "{{ trigger.payload.split('_')[0] }}" 115 | trigger_key: "{{light_state}}:{{command}}" 116 | 117 | # Construct various dictionaries entries for this automation 118 | # Off/On Cycle will use their according data values as well 119 | dict_base: "{{dict({device_topic:{'state':command, 'scene':0}})}}" 120 | dict_on: "{{ dict({device_topic:{'state':command, 'scene':on_scene_id}}) }} " 121 | dict_off: "{{ dict({device_topic:{'state':command, 'scene':off_scene_id}}) }} " 122 | 123 | # These dictionaries above will be combined with a filtered dictionary 124 | # We just want to update the existing dictionaries which is supposed done by making a new dictionary 125 | # out of a filtered dicctionary and one of the oens above 126 | dict_filtered: "{{ dict( (stored_json_text).items() | rejectattr('0','eq',device_topic) | list ) }}" 127 | 128 | # Build out JSON Packets 129 | # 130 | # If we have a state transition we'll send the base_json packet 131 | # if we are Cycling we send either on_json or off_json accordingly 132 | base_json: " {{ dict(dict_base, **dict_filtered) }}" 133 | on_json: " {{ dict(dict_on, **dict_filtered)}}" 134 | off_json: " {{ dict(dict_off, **dict_filtered)}}" 135 | 136 | # Generate Strings arrays 137 | strings_on: "{{[device_topic | lower | replace(' ','_'), command, on_scene_id] }}" 138 | strings_off: "{{[device_topic | lower | replace(' ','_'), command, off_scene_id] }}" 139 | 140 | # Build out the prefix for actions 141 | prefix: "{{ device_topic | lower | replace(' ','_') ~ '_' ~ command ~ '_'}}" 142 | scene_dict: "{% if light_state == 'on' %}{{ dict({'on': on_scene_id, 'off': '0'}) }}{% else %}{{ dict({'on': '0', 'off': off_scene_id}) }}{% endif %}" 143 | scene: "{{ dict({'on':('scene.' ~ (strings_on | join('_'))),'off':('scene.' ~ (strings_off | join('_'))) }) }}" 144 | script: "{{ dict({'on':('script.' ~ (strings_on | join('_'))),'off':('script.' ~ (strings_off | join('_'))) }) }}" 145 | 146 | # Make a list of all scenes/scripts that exist 147 | # this will be used later for a boolean check 148 | all_scenes: "{{ states.scene | map(attribute='entity_id') | list }}" 149 | all_scripts: "{{ states.script | map(attribute='entity_id') | list }}" 150 | 151 | trigger: 152 | - platform: mqtt 153 | topic: zigbee2mqtt/+/action 154 | 155 | condition: 156 | - alias: "Trigger on correct action" 157 | condition: template 158 | value_template: "{{ (trigger.topic == 'zigbee2mqtt/' + device_topic + '/action') and (trigger.payload in ['on_press','off_press'])}}" 159 | 160 | action: 161 | - service: input_text.set_value 162 | target: 163 | entity_id: input_text.zigbee2mqtt_json 164 | data: 165 | value: "{{ base_json }}" 166 | - choose: 167 | # ON 168 | - conditions: 169 | - condition: template 170 | alias: "ON" 171 | value_template: "{{ trigger_key == 'off:on' }}" 172 | sequence: 173 | - alias: "Set Base JSON" 174 | service: input_text.set_value 175 | target: 176 | entity_id: input_text.zigbee2mqtt_json 177 | data: 178 | value: "{{base_json}}" 179 | 180 | # ON_CYCLE 181 | - conditions: 182 | - condition: template 183 | alias: "ON_CYCLE" 184 | value_template: "{{ trigger_key == 'on:on' }}" 185 | sequence: 186 | - alias: "Increment Scene" 187 | service: input_text.set_value 188 | target: 189 | entity_id: input_text.zigbee2mqtt_json 190 | data: 191 | value: "{{on_json}}" 192 | 193 | # OFF 194 | - conditions: 195 | - condition: template 196 | alias: "OFF" 197 | value_template: "{{ trigger_key == 'on:off' }}" 198 | sequence: 199 | - alias: "Set Base JSON" 200 | service: input_text.set_value 201 | target: 202 | entity_id: input_text.zigbee2mqtt_json 203 | data: 204 | value: "{{base_json}}" 205 | 206 | # OFF_CYCLE 207 | - conditions: 208 | - condition: template 209 | alias: "OFF_CYCLE" 210 | value_template: "{{ trigger_key == 'off:off' }}" 211 | sequence: 212 | - alias: "Increment Scene" 213 | service: input_text.set_value 214 | target: 215 | entity_id: input_text.zigbee2mqtt_json 216 | data: 217 | value: "{{off_json}}" 218 | - parallel: 219 | - sequence: 220 | - condition: template 221 | value_template: "{{ 'scene.' ~ prefix ~ scene_dict[command] in all_scenes }}" 222 | - service: scene.turn_on 223 | data: 224 | transition: 1 225 | target: 226 | entity_id: "scene.{{prefix ~ scene_dict[command]}}" 227 | - sequence: 228 | - condition: template 229 | value_template: "{{ 'script.' ~ prefix ~ scene_dict[command] in all_scripts }}" 230 | - service: "script.{{prefix ~ scene_dict[command]}}" 231 | -------------------------------------------------------------------------------- /blueprints/automation/inovelli_door_cover_notification.yaml: -------------------------------------------------------------------------------- 1 | # This blueprint requires the use of two external scripts that must be setup with your configuration. They are available from https://github.com/brianhanifin/Home-Assistant-Config 2 | # 3 | # https://github.com/brianhanifin/Home-Assistant-Config/blob/master/scripts/notifications/inovelli_led/inovelli_led.yaml 4 | # https://github.com/brianhanifin/Home-Assistant-Config/blob/master/scripts/notifications/inovelli_led/inovelli_led_set_defaults.yaml 5 | # 6 | # The "jist" of this blueprint is that it will monitor the status of two items (a lock and a cover) and give you a 4x matrix of color options 7 | # So you can have your inovelli swtich (at a glance) tell you whats up 8 | # 9 | # 10 | # 11 | 12 | blueprint: 13 | name: Inovelli Red Series LZW31-SN Dimmer - LED Notifications based on combo of Door and Garage 14 | description: The idea is to matrix a Door lock status and a garage door (cover) status to give you different light effects. Also it will fire off a moible phone notificaiton. For this blueprint to work, however, you MUST have two separate inovelli scripts installed (inovelli_led.yaml & inovelli_led_set_defaults.yaml). 15 | 16 | domain: automation 17 | input: 18 | lock: 19 | name: Door Lock 20 | description: The lock that we want to trigger on 21 | default: lock.august_smart_lock_pro_3rd_gen 22 | selector: 23 | entity: 24 | domain: lock 25 | cover: 26 | name: Garage Door 27 | default: cover.garage_door 28 | selector: 29 | entity: 30 | domain: cover 31 | light: 32 | name: Inovelli Light 33 | description: zwave_js supported Inovelli lights will show up here 34 | default: light.red_series_dimmer 35 | selector: 36 | entity: 37 | integration: zwave_js 38 | # manufacturer: Inovelli 39 | domain: light 40 | 41 | effect: 42 | description: 'Choose a state transition effect.' 43 | selector: 44 | select: 45 | options: 46 | - "Off" 47 | - Solid 48 | - Chase 49 | - Fast Blink 50 | - Slow Blink 51 | - Blink 52 | - Pulse 53 | - Breath 54 | duration: 55 | description: 'How long should the effect run?' 56 | selector: 57 | select: 58 | options: 59 | - "Off" 60 | - 1 Second 61 | - 2 Seconds 62 | - 3 Seconds 63 | - 4 Seconds 64 | - 5 Seconds 65 | - 6 Seconds 66 | - 7 Seconds 67 | - 8 Seconds 68 | - 9 Seconds 69 | - 10 Seconds 70 | - 15 Seconds 71 | - 20 Seconds 72 | - 25 Seconds 73 | - 30 Seconds 74 | - 35 Seconds 75 | - 40 Seconds 76 | - 45 Seconds 77 | - 50 Seconds 78 | - 55 Seconds 79 | - 60 Seconds 80 | - 2 Minutes 81 | - 3 Minutes 82 | - 4 Minutes 83 | # - 10 Minutes 84 | # - 15 Minutes 85 | # - 30 Minutes 86 | # - 45 Minutes 87 | 88 | door_locked_garage_closed_color: 89 | name: Door Locked / Garage Close Color 90 | default: Green 91 | selector: 92 | select: 93 | options: 94 | - "Off" 95 | - Red 96 | - Orange 97 | - Yellow 98 | - Green 99 | - Cyan 100 | - Teal 101 | - Blue 102 | - Purple 103 | - Light Pink 104 | - Pink 105 | door_locked_garage_open_color: 106 | name: Door Locked / Garage Open 107 | description: Pick a color for the combo of an locked door and an open garage 108 | default: Yellow 109 | selector: 110 | select: 111 | options: 112 | - "Off" 113 | - Red 114 | - Orange 115 | - Yellow 116 | - Green 117 | - Cyan 118 | - Teal 119 | - Blue 120 | - Purple 121 | - Light Pink 122 | - Pink 123 | door_unlocked_garage_closed_color: 124 | name: Door Unlocked / Garage Close Color 125 | default: Purple 126 | selector: 127 | select: 128 | options: 129 | - "Off" 130 | - Red 131 | - Orange 132 | - Yellow 133 | - Green 134 | - Cyan 135 | - Teal 136 | - Blue 137 | - Purple 138 | - Light Pink 139 | - Pink 140 | door_unlocked_garage_open_color: 141 | name: Door Unlocked / Garage Open 142 | description: Pick a color for the combo of an unlocked door and an open garage 143 | default: Red 144 | selector: 145 | select: 146 | options: 147 | - "Off" 148 | - Red 149 | - Orange 150 | - Yellow 151 | - Green 152 | - Cyan 153 | - Teal 154 | - Blue 155 | - Purple 156 | - Light Pink 157 | - Pink 158 | notify_device: 159 | name: Device to notify 160 | description: Device needs to run the official Home Assistant app to receive notifications 161 | selector: 162 | device: 163 | integration: mobile_app 164 | mode: restart 165 | 166 | 167 | trigger: 168 | - platform: state 169 | entity_id: !input cover 170 | to: 'open' 171 | - platform: state 172 | entity_id: !input cover 173 | to: 'closed' 174 | - platform: state 175 | entity_id: !input lock 176 | to: 'locked' 177 | - platform: state 178 | entity_id: !input lock 179 | to: 'unlocked' 180 | 181 | action: 182 | - choose: 183 | - conditions: #Door Locked / Garage Locked 184 | - condition: state 185 | entity_id: !input cover 186 | state: closed 187 | - condition: state 188 | entity_id: !input lock 189 | state: locked 190 | sequence: 191 | - service: script.inovelli_led_set_defaults 192 | data: 193 | entity_id: !input light 194 | color: !input door_locked_garage_closed_color 195 | - service: script.inovelli_led 196 | data: 197 | entity_id: !input light 198 | color: !input door_locked_garage_closed_color 199 | effect: !input effect 200 | duration: !input duration 201 | - device_id: !input notify_device 202 | domain: mobile_app 203 | type: notify 204 | message: All Closed 205 | - conditions: # Door Unlocked / Garage Locked 206 | - condition: state 207 | entity_id: !input cover 208 | state: closed 209 | - condition: state 210 | entity_id: !input lock 211 | state: unlocked 212 | sequence: 213 | - service: script.inovelli_led_set_defaults 214 | data: 215 | entity_id: !input light 216 | color: !input door_unlocked_garage_closed_color 217 | - service: script.inovelli_led 218 | data: 219 | entity_id: !input light 220 | color: !input door_unlocked_garage_closed_color 221 | effect: !input effect 222 | duration: !input duration 223 | - device_id: !input notify_device 224 | domain: mobile_app 225 | type: notify 226 | message: Garage Closed - Door Unlocked 227 | - conditions: # Door Locked / Garage Open 228 | - condition: state 229 | entity_id: !input cover 230 | state: open 231 | - condition: state 232 | entity_id: !input lock 233 | state: locked 234 | sequence: 235 | - service: script.inovelli_led_set_defaults 236 | data: 237 | entity_id: !input light 238 | color: !input door_locked_garage_open_color 239 | - service: script.inovelli_led 240 | data: 241 | entity_id: !input light 242 | color: !input door_locked_garage_open_color 243 | effect: !input effect 244 | duration: !input duration 245 | - device_id: !input notify_device 246 | domain: mobile_app 247 | type: notify 248 | message: Garage Open - Door Locked 249 | - conditions: # Door Open / Garage Open 250 | - condition: state 251 | entity_id: !input cover 252 | state: open 253 | - condition: state 254 | entity_id: !input lock 255 | state: unlocked 256 | sequence: 257 | - service: script.inovelli_led_set_defaults 258 | data: 259 | entity_id: !input light 260 | color: !input door_unlocked_garage_open_color 261 | - service: script.inovelli_led 262 | data: 263 | entity_id: !input light 264 | color: !input door_unlocked_garage_open_color 265 | effect: !input effect 266 | duration: !input duration 267 | - device_id: !input notify_device 268 | domain: mobile_app 269 | type: notify 270 | message: Garage Open - Door Unlocked 271 | -------------------------------------------------------------------------------- /blueprints/automation/state-monitor.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: A State Monitor with Actionable Notifications 3 | description: > 4 | Send notifications to a mobile app at regular intervals when a device reaches a specified state. 5 | Includes actionable notifications to close covers if the entity is a cover. 6 | Messages will arrive in the form of "[ENTITY NAME] is [Condition Name] for X hrs". 7 | domain: automation 8 | input: 9 | monitored_entity: 10 | name: Monitored Entity 11 | description: The entity to monitor for state changes 12 | selector: 13 | entity: 14 | target_state: 15 | name: Target State 16 | description: > 17 | Leave this blank to use the default. The default states are as follows: 18 | - `cover`: **`open`** 19 | - `binary_sensor`: **`on`** 20 | selector: 21 | text: 22 | default: "" 23 | message_state: 24 | name: Condition Name 25 | description: The text to include in notifications (e.g., "Open" or "Still Open") 26 | selector: 27 | text: 28 | default: "Open" 29 | notify_device: 30 | name: Notification Device 31 | description: The mobile app device to send notifications to 32 | selector: 33 | device: 34 | filter: 35 | integration: mobile_app 36 | interval_minutes: 37 | name: Notification Interval (Minutes) 38 | description: How often to send notifications while in target state 39 | default: 30 40 | selector: 41 | number: 42 | min: 1 43 | max: 180 44 | unit_of_measurement: minutes 45 | 46 | variables: 47 | interval_minutes: !input interval_minutes 48 | monitored_entity: !input monitored_entity 49 | notify_device: !input notify_device 50 | message_state: !input message_state 51 | entity_name: "{{ state_attr(monitored_entity, 'friendly_name') or monitored_entity.split('.')[1] | replace('_', ' ') | title }}" 52 | # Derive notify_service from device ID 53 | notify_service: >- 54 | {% set devices = device_entities(notify_device) | list %} 55 | notify.mobile_app_{{devices[0].split('.')[1]}} 56 | unique_tag: "{{ monitored_entity | replace('.', '_') }}_state_monitor" 57 | default_state: >- 58 | {% if monitored_entity.split('.')[0] == 'cover' %} 59 | open 60 | {% elif monitored_entity.split('.')[0] == 'binary_sensor' %} 61 | on 62 | {% else %} 63 | on 64 | {% endif %} 65 | ts: !input target_state 66 | target_state: >- 67 | {% if not ts or ts == '' %} 68 | {{ default_state | trim }} 69 | {% else %} 70 | {{ ts | trim }} 71 | {% endif %} 72 | is_cover: >- 73 | {% if monitored_entity.split('.')[0] == 'cover' %} 74 | true 75 | {% else %} 76 | false 77 | {% endif %} 78 | actions: >- 79 | {% if is_cover == 'true' %} 80 | [{"action": "CLOSE_{{ unique_tag }}", 81 | "title": "Close {{ entity_name }}", 82 | "service": "cover.close_cover", 83 | "service_data": {"entity_id": "{{ monitored_entity }}"}}] 84 | {% else %} 85 | [] 86 | {% endif %} 87 | 88 | trigger: 89 | - trigger: state 90 | entity_id: !input monitored_entity 91 | - trigger: homeassistant 92 | event: start 93 | - trigger: event 94 | event_type: automation_reloaded 95 | 96 | action: 97 | - choose: 98 | - conditions: 99 | - condition: state 100 | entity_id: !input monitored_entity 101 | state: "{{ target_state }}" 102 | sequence: 103 | - variables: 104 | message: "{{ entity_name }} is now {{ message_state }}" 105 | - action: "{{ notify_service }}" 106 | data: 107 | title: "{{ entity_name }}" 108 | message: "{{ message }}" 109 | data: 110 | tag: "{{ unique_tag }}" 111 | actions: "{{ actions }}" 112 | - repeat: 113 | while: 114 | - condition: state 115 | entity_id: !input monitored_entity 116 | state: "{{ target_state }}" 117 | sequence: 118 | - delay: 119 | minutes: "{{ interval_minutes }}" 120 | - variables: 121 | last_changed_str: >- 122 | {{ relative_time(states[monitored_entity].last_changed | default(0)) 123 | | replace('hours', 'hr') 124 | | replace('hour', 'hr') 125 | | replace('seconds', 'sec') 126 | | replace('minutes', 'min') 127 | | replace('minute', 'min') }} 128 | message: "{{ entity_name }} is {{ message_state }} for {{ last_changed_str }}" 129 | - action: "{{ notify_service }}" 130 | data: 131 | title: "{{ entity_name }}" 132 | message: "{{ message }}" 133 | data: 134 | tag: "{{ unique_tag }}" 135 | actions: "{{ actions }}" 136 | default: 137 | - action: "{{ notify_service }}" 138 | data: 139 | message: "clear_notification" 140 | data: 141 | tag: "{{ unique_tag }}" 142 | 143 | mode: restart 144 | -------------------------------------------------------------------------------- /docs/sunset.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/docs/sunset.gif -------------------------------------------------------------------------------- /docs/weather.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/docs/weather.gif -------------------------------------------------------------------------------- /icons/dev/README.md: -------------------------------------------------------------------------------- 1 | What is here? (good question). 2 | 3 | 4 | - `ansiPreview.py` Mirrors an Awtrix screen to the CLI 5 | 6 | [![asciicast](https://asciinema.org/a/uiB03VYXNelbMZBWEBRPqKFA5.svg)](https://asciinema.org/a/uiB03VYXNelbMZBWEBRPqKFA5) 7 | 8 | There are some helper scripts. 9 | 10 | - `gifConverter.py`: This will take a `.gif` file and drop out a set of `.svg` files for each frame of the image. Additionaly it will make an `.html` and a `.md` file linking to those preview images 11 | 12 | - `gifMaker.py`: You give this script an ip address (of your clock) - and it will record the screen frame by frame and write the result to `output.gif`. You also get a live ANSI preview in the command line. 13 | - `makeRGB.py`: This script will take a set of `.gif` files as input and convert them into RGB565 format, as well as giving you various options (A macro or raw data to send to curl) to render the gif as a `draw` command. 14 | 15 | 16 | -------------------------------------------------------------------------------- /icons/dev/ansiPreview.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from PIL import Image, ImageDraw 4 | from typing import List 5 | import asyncio 6 | import sys 7 | import os 8 | 9 | 10 | class ScreenCapture: 11 | def __init__( 12 | self, 13 | endpoint_url: str, 14 | width: int, 15 | height: int, 16 | initial_duration: int, 17 | ) -> None: 18 | self.endpoint_url = endpoint_url 19 | self.width = width 20 | self.height = height 21 | self.initial_duration = initial_duration 22 | 23 | async def capture_frame(self) -> None: 24 | """Capture a frame from the endpoint and display a live preview.""" 25 | frame_count = 0 26 | while True: 27 | response = requests.get(self.endpoint_url) 28 | 29 | # Check if the request was successful 30 | if response.status_code == 200: 31 | # Get the RGB565 color values as a list 32 | rgb565_values = response.json() 33 | 34 | # Create a new PIL image of the original dimensions (32x8) 35 | image = Image.new("RGB", (self.width, self.height)) 36 | draw = ImageDraw.Draw(image) 37 | 38 | # Set the color of each pixel in the image 39 | for y in range(self.height): 40 | for x in range(self.width): 41 | # Convert the decimal RGB565 value to RGB888 42 | rgb565 = rgb565_values[y * self.width + x] 43 | red = (rgb565 & 0xFF0000) >> 16 44 | green = (rgb565 & 0x00FF00) >> 8 45 | blue = rgb565 & 0x0000FF 46 | 47 | # Draw a pixel with the converted RGB value 48 | draw.point((x, y), fill=(red, green, blue)) 49 | 50 | # Scale the image to the desired dimensions (256x64) 51 | scaled_image = image.resize( 52 | (self.width * 4, self.height * 4), resample=Image.NEAREST 53 | ) 54 | 55 | # Print the frame count and live preview 56 | self.print_live_preview(frame_count, image) 57 | 58 | frame_count += 1 59 | 60 | await asyncio.sleep(0.05) # Delay between frame captures 61 | 62 | def print_live_preview(self, frame_count: int, image: Image.Image) -> None: 63 | """Print the live preview of the image, clearing the console screen.""" 64 | os.system("cls" if os.name == "nt" else "clear") 65 | print(f"\033[32mFrames Shown: {frame_count}\033[0m") 66 | width, height = image.size 67 | for y in range(height): 68 | for x in range(width): 69 | r, g, b = image.getpixel((x, y)) 70 | sys.stdout.write(f"\033[48;2;{r};{g};{b}m \033[0m") 71 | sys.stdout.write("\n") 72 | print("\033[32mctrl+c to exit\033[0m") 73 | 74 | 75 | async def capture_loop(screen_capture: ScreenCapture) -> None: 76 | await screen_capture.capture_frame() 77 | 78 | 79 | def main() -> None: 80 | import argparse 81 | 82 | parser = argparse.ArgumentParser(description="Awtrix Clock Screen Capture") 83 | parser.add_argument( 84 | "--ip", 85 | type=str, 86 | help="The IP address of your Awtrix Clock", 87 | ) 88 | 89 | args = parser.parse_args() 90 | 91 | # Prompt user for the IP address if not provided through command line argument 92 | if args.ip is None: 93 | endpoint_ip = input("What is the IP for your Awtrix Clock: ") 94 | else: 95 | endpoint_ip = args.ip 96 | 97 | # Endpoint URL 98 | endpoint_url = f"http://{endpoint_ip}/api/screen" 99 | 100 | # Image dimensions 101 | width = 32 102 | height = 8 103 | 104 | # GIF parameters 105 | initial_duration = 50 # in milliseconds 106 | 107 | # Create ScreenCapture instance 108 | screen_capture = ScreenCapture(endpoint_url, width, height, initial_duration) 109 | 110 | # Create the event loop 111 | loop = asyncio.get_event_loop() 112 | 113 | # Run the capture loop 114 | try: 115 | loop.run_until_complete(capture_loop(screen_capture)) 116 | except KeyboardInterrupt: 117 | pass 118 | 119 | # Close the event loop 120 | loop.close() 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /icons/dev/awtrixEmbed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Awtrix Light Display 5 | 21 | 22 | 23 |
24 | 25 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /icons/dev/awtrixPreview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Awtrix Light Capture 5 | 51 | 207 | 208 | 209 |

Awtrix Light Capture

210 |
211 |
212 | 213 | 214 |
215 | 216 | 225 |
226 | 227 | 232 |
233 | 234 | 244 |
245 | 246 | 247 | 248 |
249 |
250 |
251 |
252 |
253 | 254 | 255 |
256 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /icons/dev/gifConverter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from PIL import Image 4 | 5 | 6 | class GifConverter: 7 | """ 8 | A class that converts a GIF image into SVG files and generates Markdown file referencing the SVG files as images. 9 | """ 10 | 11 | DEFAULT_CELL_SIZE = 16 12 | 13 | def __init__(self, gif_path: str, output_dir: str, cell_size: int): 14 | """ 15 | Initializes the GifConverter class. 16 | 17 | Args: 18 | gif_path (str): Path to the GIF image. 19 | output_dir (str): Output directory for the SVG files and Markdown file. 20 | cell_size (int): Size of each cell in the SVG representation. 21 | """ 22 | self.gif_path = gif_path 23 | self.output_dir = output_dir 24 | self.cell_size = cell_size 25 | self.base_name = os.path.splitext(os.path.basename(self.gif_path))[ 26 | 0 27 | ] # Extract base name 28 | self.gif = None 29 | 30 | def convert_to_svg(self) -> list[str]: 31 | """ 32 | Converts the GIF image to a series of SVG files. 33 | 34 | Returns: 35 | list[str]: List of file paths to the generated SVG files. 36 | """ 37 | # Open the GIF image 38 | self.gif = Image.open(self.gif_path) 39 | 40 | # Create the output directory if it doesn't exist 41 | os.makedirs(self.output_dir, exist_ok=True) 42 | 43 | # Get the base name of the input file 44 | base_name = os.path.splitext(os.path.basename(self.gif_path))[0] 45 | 46 | # Convert each frame to SVG 47 | svg_file_paths = [] 48 | for frame_index in range(self.gif.n_frames): 49 | # Select the current frame 50 | self.gif.seek(frame_index) 51 | frame = self.gif.convert("RGBA") 52 | 53 | # Get the dimensions of the frame 54 | width, height = frame.size 55 | 56 | # Calculate the cell size 57 | if self.cell_size is None: 58 | self.cell_size = self.DEFAULT_CELL_SIZE 59 | 60 | # Create an SVG file path for the current frame 61 | svg_path = os.path.join( 62 | self.output_dir, f"{base_name}_frame_{frame_index}.svg" 63 | ) 64 | svg_file_paths.append(svg_path) 65 | 66 | # Open the SVG file 67 | with open(svg_path, "w") as svg_file: 68 | # Write the SVG header 69 | svg_file.write( 70 | f'\n' 71 | ) 72 | 73 | # Write SVG rectangles for each pixel 74 | for y in range(height): 75 | for x in range(width): 76 | # Get the color of the current pixel 77 | r, g, b, a = frame.getpixel((x, y)) 78 | 79 | # Check if the pixel is transparent or matches the background color 80 | if a == 0 or (r, g, b, a) == self.gif.info.get( 81 | "background", () 82 | ): 83 | # Set the pixel color to black 84 | r, g, b = 0, 0, 0 85 | 86 | # Convert RGB values to hexadecimal 87 | hex_color = f"#{r:02x}{g:02x}{b:02x}" 88 | 89 | # Calculate the position and size of the SVG rectangle 90 | rect_x = x * self.cell_size 91 | rect_y = y * self.cell_size 92 | rect_width = self.cell_size 93 | rect_height = self.cell_size 94 | 95 | # Write the SVG rectangle for the current pixel 96 | svg_file.write( 97 | f'\n' 98 | ) 99 | 100 | # Write the SVG footer 101 | svg_file.write("\n") 102 | 103 | print(f"SVG file '{svg_path}' has been created successfully.") 104 | 105 | return svg_file_paths 106 | 107 | def generate_markdown(self, svg_file_paths: list[str]) -> None: 108 | """ 109 | Generates a Markdown file with image references to the SVG files. 110 | 111 | Args: 112 | svg_file_paths (list[str]): List of file paths to the SVG files. 113 | """ 114 | markdown_path = os.path.join(self.output_dir, f"{self.base_name}.md") 115 | 116 | with open(markdown_path, "w") as markdown_file: 117 | # Write the Markdown syntax for each SVG file as an image tag with a title 118 | for frame_index, svg_file_path in enumerate(svg_file_paths): 119 | image_name = f"{self.base_name}_frame_{frame_index}.svg" 120 | title = f"Frame {frame_index}" 121 | markdown_file.write(f'![{title}]({image_name} "{title}")\n') 122 | 123 | print(f"Markdown file '{markdown_path}' has been created successfully.") 124 | 125 | def gif_to_html(self) -> None: 126 | """ 127 | Converts the GIF image to an HTML file. 128 | 129 | Args: 130 | gif_path (str): Path to the GIF image. 131 | cell_size (int): Size of each cell in the HTML table. 132 | """ 133 | # Open the GIF image 134 | gif = Image.open(self.gif_path) 135 | 136 | # Generate the output HTML file path 137 | output_path = os.path.join(self.output_dir, f"{self.base_name}.html") 138 | 139 | # Create the HTML file 140 | with open(output_path, "w") as html_file: 141 | # Write the HTML header 142 | html_file.write("\n") 143 | html_file.write("\n") 144 | 145 | # Iterate over each frame in the GIF 146 | for frame_index in range(gif.n_frames): 147 | # Select the current frame 148 | gif.seek(frame_index) 149 | frame = gif.convert("RGBA") 150 | 151 | # Get the dimensions of the frame 152 | width, height = frame.size 153 | 154 | # Calculate the cell size 155 | if self.cell_size is None: 156 | cell_size = GifConverter.DEFAULT_CELL_SIZE 157 | 158 | # Write the frame number as the title 159 | html_file.write(f"

Frame Number: {frame_index}

\n") 160 | 161 | # Write the table start tag 162 | html_file.write('\n') 163 | 164 | # Iterate over each pixel in the frame 165 | for y in range(height): 166 | # Write the table row start tag 167 | html_file.write("\n") 168 | for x in range(width): 169 | # Get the color of the current pixel 170 | r, g, b, a = frame.getpixel((x, y)) 171 | 172 | # Check if the pixel is transparent or matches the background color 173 | if a == 0 or (r, g, b, a) == gif.info.get("background", ()): 174 | # Set the pixel color to black 175 | r, g, b = 0, 0, 0 176 | 177 | # Convert RGB values to hexadecimal 178 | hex_color = f"#{r:02x}{g:02x}{b:02x}" 179 | 180 | # Write the table cell with the updated pixel color 181 | html_file.write( 182 | f'\n' 183 | ) 184 | 185 | # Write the table row end tag 186 | html_file.write("\n") 187 | 188 | # Write the table end tag 189 | html_file.write("
\n") 190 | 191 | # Add a line break between frames 192 | html_file.write("
\n") 193 | 194 | # Write the HTML footer 195 | html_file.write("\n") 196 | html_file.write("\n") 197 | 198 | print(f'HTML file "{output_path}" has been created successfully.') 199 | 200 | 201 | def main() -> None: 202 | """ 203 | Main function for executing the GIF to SVG conversion and generating Markdown and HTML files. 204 | """ 205 | if len(sys.argv) < 2: 206 | print("Please provide the path to the GIF image.") 207 | return 208 | 209 | gif_path = sys.argv[1] 210 | cell_size = None 211 | if len(sys.argv) >= 3: 212 | cell_size = int(sys.argv[2]) 213 | 214 | converter = GifConverter(gif_path, "output", cell_size) 215 | svg_file_paths = converter.convert_to_svg() 216 | converter.generate_markdown(svg_file_paths) 217 | converter.gif_to_html() 218 | 219 | 220 | if __name__ == "__main__": 221 | main() 222 | -------------------------------------------------------------------------------- /icons/dev/gifMaker.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | from PIL import Image, ImageDraw 4 | from typing import List 5 | import asyncio 6 | import sys 7 | import os 8 | from datetime import timedelta 9 | 10 | 11 | class ScreenCapture: 12 | def __init__( 13 | self, 14 | endpoint_url: str, 15 | width: int, 16 | height: int, 17 | gif_filename: str, 18 | initial_duration: int, 19 | max_duration: int, 20 | ) -> None: 21 | self.endpoint_url = endpoint_url 22 | self.width = width 23 | self.height = height 24 | self.gif_filename = gif_filename 25 | self.initial_duration = initial_duration 26 | self.max_duration = max_duration 27 | self.gif_frames: List[Image.Image] = [] 28 | 29 | async def capture_frame(self) -> None: 30 | """Capture a frame from the endpoint and add it to the GIF frames.""" 31 | frame_count = 0 32 | while True: 33 | response = requests.get(self.endpoint_url) 34 | 35 | # Check if the request was successful 36 | if response.status_code == 200: 37 | # Get the RGB565 color values as a list 38 | rgb565_values = response.json() 39 | 40 | # Create a new PIL image of the original dimensions (32x8) 41 | image = Image.new("RGB", (self.width, self.height)) 42 | draw = ImageDraw.Draw(image) 43 | 44 | # Set the color of each pixel in the image 45 | for y in range(self.height): 46 | for x in range(self.width): 47 | # Convert the decimal RGB565 value to RGB888 48 | rgb565 = rgb565_values[y * self.width + x] 49 | red = (rgb565 & 0xFF0000) >> 16 50 | green = (rgb565 & 0x00FF00) >> 8 51 | blue = rgb565 & 0x0000FF 52 | 53 | # Draw a pixel with the converted RGB value 54 | draw.point((x, y), fill=(red, green, blue)) 55 | 56 | # Scale the image to the desired dimensions (256x64) 57 | scaled_image = image.resize( 58 | (self.width * 4, self.height * 4), resample=Image.NEAREST 59 | ) 60 | 61 | # Add the current frame to the GIF 62 | self.gif_frames.append(scaled_image) 63 | frame_count += 1 64 | 65 | # Print the frame count and live preview 66 | self.print_live_preview(frame_count, image) 67 | 68 | await asyncio.sleep(0.05) # Delay between frame captures 69 | 70 | def print_live_preview(self, frame_count: int, image: Image.Image) -> None: 71 | """Print the live preview of the image, clearing the console screen.""" 72 | os.system("cls" if os.name == "nt" else "clear") 73 | print(f"\033[32mFrames captured: {frame_count}\033[0m") 74 | width, height = image.size 75 | for y in range(height): 76 | for x in range(width): 77 | r, g, b = image.getpixel((x, y)) 78 | sys.stdout.write(f"\033[48;2;{r};{g};{b}m \033[0m") 79 | sys.stdout.write("\n") 80 | print("\033[32mctrl+c to exit\033[0m") 81 | 82 | def save_as_gif(self) -> None: 83 | """Save the captured frames as a GIF and print the duration.""" 84 | if len(self.gif_frames) > 0: 85 | # Calculate the total duration of the GIF 86 | total_duration = len(self.gif_frames) * self.initial_duration 87 | 88 | # Save the frames as a GIF 89 | self.gif_frames[0].save( 90 | self.gif_filename, 91 | format="GIF", 92 | append_images=self.gif_frames[1:], 93 | save_all=True, 94 | duration=self.initial_duration, 95 | loop=0, 96 | ) 97 | 98 | # Print the duration 99 | duration_str = str(timedelta(milliseconds=total_duration)) 100 | print(f"\nGIF saved successfully. Duration: {duration_str}") 101 | else: 102 | print("\nNo frames captured. GIF not saved.") 103 | 104 | 105 | async def capture_loop(screen_capture: ScreenCapture) -> None: 106 | await screen_capture.capture_frame() 107 | 108 | 109 | def main() -> None: 110 | import argparse 111 | 112 | parser = argparse.ArgumentParser(description="Awtrix Clock Screen Capture") 113 | parser.add_argument( 114 | "--ip", 115 | type=str, 116 | help="The IP address of your Awtrix Clock", 117 | ) 118 | 119 | args = parser.parse_args() 120 | 121 | # Prompt user for the IP address if not provided through command line argument 122 | if args.ip is None: 123 | endpoint_ip = input("What is the IP for your Awtrix Clock: ") 124 | else: 125 | endpoint_ip = args.ip 126 | 127 | # Endpoint URL 128 | endpoint_url = f"http://{endpoint_ip}/api/screen" 129 | 130 | # Image dimensions 131 | width = 32 132 | height = 8 133 | 134 | # GIF parameters 135 | gif_filename = "output.gif" 136 | initial_duration = 50 # in milliseconds 137 | max_duration = 500 # in milliseconds 138 | 139 | # Create ScreenCapture instance 140 | screen_capture = ScreenCapture( 141 | endpoint_url, width, height, gif_filename, initial_duration, max_duration 142 | ) 143 | 144 | # Prompt user to start capturing 145 | input("Press Enter to start capturing frames...") 146 | 147 | # Start time 148 | start_time = time.time() 149 | 150 | # Create the event loop 151 | loop = asyncio.get_event_loop() 152 | 153 | # Run the capture loop 154 | try: 155 | loop.run_until_complete(capture_loop(screen_capture)) 156 | except KeyboardInterrupt: 157 | pass 158 | 159 | # Save frames as GIF 160 | screen_capture.save_as_gif() 161 | 162 | # End time 163 | end_time = time.time() 164 | 165 | # Calculate capture duration 166 | capture_duration = end_time - start_time 167 | print(f"Capture duration: {capture_duration:.2f} seconds") 168 | 169 | 170 | if __name__ == "__main__": 171 | main() 172 | -------------------------------------------------------------------------------- /icons/dev/makeRGB.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PIL import Image 3 | import json 4 | import os 5 | 6 | 7 | class RGBMaker: 8 | def __init__(self, filename): 9 | self.filename = filename 10 | self.stripped_filename = self.strip_extension_and_path(filename) 11 | self.width = None 12 | self.height = None 13 | 14 | def strip_extension_and_path(self, filename): 15 | base_filename = os.path.basename(filename) 16 | stripped_filename = os.path.splitext(base_filename)[0] 17 | return stripped_filename 18 | 19 | def frame_to_rgb(self, frame, background_value=0): 20 | # Convert the frame image to RGBA mode 21 | rgba_frame = frame.convert("RGBA") 22 | 23 | # Get the dimensions of the frame 24 | width, height = rgba_frame.size 25 | 26 | # Create an empty list to store the RGB888 pixel values 27 | rgb888_data = [] 28 | 29 | # Iterate over each pixel in the frame 30 | for y in range(height): 31 | for x in range(width): 32 | # Get the RGBA values of the pixel in the frame 33 | r, g, b, a = rgba_frame.getpixel((x, y)) 34 | 35 | # Replace the background color with the provided background value 36 | if a == 0: 37 | r, g, b = background_value, background_value, background_value 38 | 39 | # Convert the RGB values to RGB565 format 40 | rgb565 = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3) 41 | # Convert the RGB values to RGB888 format 42 | rgb888 = (r << 16) | (g << 8) | b 43 | 44 | # Append the RGB888 value to the list 45 | rgb888_data.append(rgb888) 46 | 47 | return rgb888_data 48 | 49 | def clean_frame(self, frame): 50 | # Clean the frame to ensure all frames have the same size and black background 51 | cleaned_frame = Image.new("RGBA", frame.size, (0, 0, 0)) 52 | cleaned_frame.paste(frame, (0, 0), frame) 53 | return cleaned_frame 54 | 55 | def make_rgb(self, background_value=0): 56 | # Open the GIF image 57 | try: 58 | gif_image = Image.open(self.filename) 59 | except IOError: 60 | print( 61 | f"Error: Unable to open '{self.filename}'. Please make sure it is a valid GIF image." 62 | ) 63 | return [] 64 | 65 | # Create an empty list to store the RGB888 pixel values 66 | rgb888_data_frames = [] 67 | 68 | self.width, self.height = gif_image.size 69 | 70 | # Iterate over each frame in the GIF image 71 | try: 72 | while True: 73 | # Get the current frame 74 | frame = gif_image.convert("RGBA") 75 | 76 | # Clean the frame to ensure all frames have the same size and black background 77 | cleaned_frame = self.clean_frame(frame) 78 | 79 | # Convert the cleaned frame to RGB888 format 80 | rgb888_data = self.frame_to_rgb(cleaned_frame, background_value) 81 | 82 | # Append the current frame's RGB888 data to the list of frames 83 | rgb888_data_frames.append(rgb888_data) 84 | 85 | # Move to the next frame 86 | gif_image.seek(gif_image.tell() + 1) 87 | 88 | except EOFError: 89 | pass 90 | 91 | return rgb888_data_frames 92 | 93 | def print_color_palette_from_data(self, data, background_value=0): 94 | print("Color Palette From Data:") 95 | unique_colors = set(data) # Get unique colors 96 | 97 | # Add the background value to the unique colors set 98 | unique_colors.add(background_value) 99 | 100 | # Count the occurrences of each color 101 | color_counts = {color: data.count(color) for color in unique_colors} 102 | 103 | for rgb888 in unique_colors: 104 | # Extract the RGB components from RGB888 format 105 | r = (rgb888 >> 16) & 0xFF 106 | g = (rgb888 >> 8) & 0xFF 107 | b = rgb888 & 0xFF 108 | 109 | # Convert the RGB components to hexadecimal 110 | hex_value = "0x{:04X}".format(rgb888) 111 | 112 | # Format the columns for alignment 113 | swatch_col = "\033[48;2;{};{};{}m \033[0m".format(r, g, b) 114 | rgb888_col = "{:<7}".format(rgb888) 115 | hex_col = "{:<9}".format(hex_value) 116 | count_col = "{:<5}".format(color_counts[rgb888]) 117 | 118 | # Print the color information, count, and ASCII swatch 119 | print( 120 | "Swatch: {} RGB888: {} Hex: {} Count: {}".format( 121 | swatch_col, rgb888_col, hex_col, count_col 122 | ) 123 | ) 124 | print("\n") 125 | 126 | def print_color_palette_from_image(self): 127 | # Open the image file 128 | try: 129 | image = Image.open(self.filename) 130 | except IOError: 131 | print( 132 | f"Error: Unable to open '{self.filename}'. Please make sure it is a valid image." 133 | ) 134 | return 135 | 136 | # Check if the image has a color palette 137 | if image.mode == "P": 138 | # Get the color palette 139 | palette = image.getpalette() 140 | 141 | print("Color Palette From Image:") 142 | unique_colors = set() 143 | 144 | # Iterate over the palette and extract unique colors 145 | for i in range(0, len(palette), 3): 146 | # Get the RGB values 147 | r, g, b = palette[i : i + 3] 148 | 149 | # Convert RGB to RGB888 format 150 | rgb888 = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3) 151 | 152 | # Add the RGB888 value to the set of unique colors 153 | unique_colors.add(rgb888) 154 | 155 | # Print the unique colors and their ASCII swatches 156 | self.print_color_palette_from_data(unique_colors) 157 | else: 158 | print("The image does not have a color palette.") 159 | 160 | def replace(self, data, old_value, new_value): 161 | # Replace the old value with the new value in the data list 162 | return [new_value if value == old_value else value for value in data] 163 | 164 | def print_ascii_swatches(self, rgb888_data): 165 | print("ASCII Swatches:") 166 | 167 | for y in range(self.height): 168 | for x in range(self.width): 169 | rgb888 = rgb888_data[y * self.width + x] 170 | 171 | # Extract the RGB components from RGB888 format 172 | r = (rgb888 >> 16) & 0xFF 173 | g = (rgb888 >> 8) & 0xFF 174 | b = rgb888 & 0xFF 175 | 176 | # Determine the ANSI color code based on the RGB components 177 | ansi_color_code = 16 + (36 * (r // 51)) + (6 * (g // 51)) + (b // 51) 178 | 179 | # Print the ANSI color escape sequence and a space to represent the swatch 180 | print("\033[48;5;{}m \033[0m".format(ansi_color_code), end="") 181 | 182 | print() # Move to the next line after printing each row 183 | 184 | def print_rgb888_data(self, rgb888_data, colors=True): 185 | print("RGB888 Data ({}x{}):".format(self.width, self.height)) 186 | 187 | max_value_length = len( 188 | str(max(rgb888_data)) 189 | ) # Calculate the maximum length of RGB888 values 190 | 191 | for i in range(0, len(rgb888_data), self.width): 192 | row = rgb888_data[i: i + self.width] 193 | row_str = "" 194 | 195 | for value in row: 196 | value_str = "{:{}}".format(value, max_value_length) 197 | 198 | if colors: 199 | # Extract the RGB components from RGB888 format 200 | r = (value >> 16) & 0xFF 201 | g = (value >> 8) & 0xFF 202 | b = value & 0xFF 203 | 204 | # Convert RGB565 to RGB888 format 205 | r = (r | (r >> 5)) & 0xFF 206 | g = (g | (g >> 6)) & 0xFF 207 | b = (b | (b >> 5)) & 0xFF 208 | 209 | # Determine the ANSI color code based on the RGB components 210 | ansi_color_code = ( 211 | 16 + (36 * (r // 51)) + (6 * (g // 51)) + (b // 51) 212 | ) 213 | 214 | # Create the color escape sequence 215 | color_escape = f"\033[38;5;{ansi_color_code}m" 216 | 217 | value_str = f"{color_escape}{value_str}\033[0m" 218 | 219 | row_str += value_str + ", " 220 | 221 | row_str = row_str.rstrip(", ") 222 | 223 | if i < len(rgb888_data) - self.width: 224 | row_str += "," 225 | print(row_str) 226 | 227 | def generate_test_data(self, rgb888_data): 228 | test_data = { 229 | "stack": False, 230 | "draw": [{"db": [0, 0, self.width, self.height, rgb888_data]}], 231 | } 232 | return test_data 233 | 234 | def generate_macro(self, rgb888_data, frame_index): 235 | macro_name = f"{self.stripped_filename}_{frame_index}" 236 | 237 | header = "{%- macro " + macro_name + "(x,y) %}" 238 | 239 | body = ( 240 | '{"db": [{{x}}, {{y}}, ' 241 | + str(self.width) 242 | + ", " 243 | + str(self.height) 244 | + ", " 245 | + str(rgb888_data) 246 | + "]}" 247 | ) 248 | footer = "{%- endmacro %}" 249 | 250 | return f"{header}\n{body}\n{footer}\n" 251 | 252 | 253 | def main(): 254 | if len(sys.argv) < 2: 255 | print("Please provide the filenames of the GIF images.") 256 | sys.exit(1) 257 | 258 | gif_filenames = sys.argv[1:] 259 | 260 | for gif_filename in gif_filenames: 261 | rgb_maker = RGBMaker(gif_filename) 262 | 263 | # Call the make_rgb function with the GIF filename 264 | rgb888_data_frames = rgb_maker.make_rgb() 265 | 266 | if not rgb888_data_frames: 267 | continue 268 | 269 | frame_count = len(rgb888_data_frames) 270 | 271 | # Print the color palette and RGB565 data for each frame 272 | print(f"--- Color Palette and RGB565 Data for {gif_filename} ---") 273 | 274 | for frame_index, rgb888_data in enumerate(rgb888_data_frames): 275 | print(f"Frame {frame_index + 1} of {frame_count}:") 276 | rgb_maker.print_color_palette_from_data(rgb888_data) 277 | rgb_maker.print_ascii_swatches(rgb888_data) 278 | 279 | print(f"--- Raw Data for Frame {frame_index + 1} of {frame_count} ---") 280 | rgb_maker.print_rgb888_data(rgb888_data, colors=True) 281 | 282 | # Generate the test data dictionary for the frame 283 | test_data = rgb_maker.generate_test_data(rgb888_data) 284 | test_data_json = json.dumps(test_data) 285 | 286 | macro = rgb_maker.generate_macro( 287 | rgb888_data=rgb888_data, frame_index=frame_index 288 | ) 289 | 290 | print("\n--- Drawing Macro Frame {frame_index + 1} of {frame_count} ---") 291 | print(macro) 292 | 293 | print("--- Test Data JSON for Frame {frame_index + 1} of {frame_count} ---") 294 | print(test_data_json) 295 | print() 296 | 297 | 298 | if __name__ == "__main__": 299 | main() 300 | -------------------------------------------------------------------------------- /icons/door_monitor_blinds/blinds_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_blinds/blinds_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_blinds/blinds_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_blinds/blinds_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_blinds/blinds_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_blinds/blinds_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_blinds/blinds_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_blinds/blinds_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_front_door/front_door_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_front_door/front_door_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_front_door/front_door_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_front_door/front_door_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_front_door/front_door_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_front_door/front_door_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_front_door/front_door_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_front_door/front_door_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_garage/garage_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_garage/garage_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_garage/garage_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_garage/garage_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_garage/garage_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_garage/garage_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_garage/garage_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_garage/garage_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_house_shed/house_shed_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_house_shed/house_shed_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_house_shed/house_shed_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_house_shed/house_shed_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_house_shed/house_shed_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_house_shed/house_shed_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_house_shed/house_shed_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_house_shed/house_shed_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_internal_door/internal_door_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_internal_door/internal_door_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_internal_door/internal_door_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_internal_door/internal_door_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_internal_door/internal_door_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_internal_door/internal_door_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_internal_door/internal_door_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_internal_door/internal_door_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_sliding_door/sliding_door_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_sliding_door/sliding_door_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_sliding_door/sliding_door_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_sliding_door/sliding_door_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_sliding_door/sliding_door_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_sliding_door/sliding_door_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_sliding_door/sliding_door_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_sliding_door/sliding_door_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_window/window_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_window/window_close.gif -------------------------------------------------------------------------------- /icons/door_monitor_window/window_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_window/window_error.gif -------------------------------------------------------------------------------- /icons/door_monitor_window/window_is_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_window/window_is_open.gif -------------------------------------------------------------------------------- /icons/door_monitor_window/window_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/door_monitor_window/window_open.gif -------------------------------------------------------------------------------- /icons/hvac/cool.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/hvac/cool.gif -------------------------------------------------------------------------------- /icons/hvac/heat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/hvac/heat.gif -------------------------------------------------------------------------------- /icons/phone/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Static Battery Images 4 | 5 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53204_icon_thumb.png?v=1) 6 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53205_icon_thumb.png?v=1) 7 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53207_icon_thumb.png?v=1) 8 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53208_icon_thumb.png?v=1) 9 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53209_icon_thumb.png?v=1) 10 | 11 | ## Charging Images 12 | 13 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53212_icon_thumb.gif?v=1) 14 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53213_icon_thumb.gif?v=1) 15 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53214_icon_thumb.gif?v=1) 16 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53215_icon_thumb.gif?v=1) 17 | ![](https://developer.lametric.com/content/apps/icon_thumbs/53216_icon_thumb.gif?v=1) 18 | 19 | -------------------------------------------------------------------------------- /icons/phone/battery-100.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-100.gif -------------------------------------------------------------------------------- /icons/phone/battery-20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-20.gif -------------------------------------------------------------------------------- /icons/phone/battery-40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-40.gif -------------------------------------------------------------------------------- /icons/phone/battery-60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-60.gif -------------------------------------------------------------------------------- /icons/phone/battery-80.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-80.gif -------------------------------------------------------------------------------- /icons/phone/battery-charging-100.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-charging-100.gif -------------------------------------------------------------------------------- /icons/phone/battery-charging-20.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-charging-20.gif -------------------------------------------------------------------------------- /icons/phone/battery-charging-40.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-charging-40.gif -------------------------------------------------------------------------------- /icons/phone/battery-charging-60.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-charging-60.gif -------------------------------------------------------------------------------- /icons/phone/battery-charging-80.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/phone/battery-charging-80.gif -------------------------------------------------------------------------------- /icons/upload_icon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Colors 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | YELLOW='\033[0;33m' 8 | NC='\033[0m' # No Color 9 | 10 | # Check if 'jq' is installed 11 | check_jq() { 12 | if ! command -v jq >/dev/null 2>&1; then 13 | echo -e "${RED}Error: 'jq' is not installed. Please install 'jq' to run this script.${NC}" 14 | exit 1 15 | fi 16 | } 17 | 18 | # List icon directories 19 | list_icon_directories() { 20 | local OWNER="jeeftor" 21 | local REPO="HomeAssistant" 22 | local BRANCH="master" 23 | local DIRECTORY="icons" 24 | 25 | # Make the API request to list the directory contents 26 | local response 27 | response=$(curl -s "https://api.github.com/repos/$OWNER/$REPO/contents/$DIRECTORY?ref=$BRANCH") 28 | 29 | # Extract directory names 30 | local directories=($(echo "$response" | jq -r '.[] | select(.type == "dir") | .name')) 31 | 32 | # Return the list of directories 33 | echo "${directories[@]}" 34 | } 35 | 36 | # List icons within a directory 37 | list_icons() { 38 | local OWNER="jeeftor" 39 | local REPO="HomeAssistant" 40 | local BRANCH="master" 41 | local DIRECTORY="icons/$1" 42 | 43 | # Make the API request to list the directory contents 44 | local response 45 | response=$(curl -L -s "https://api.github.com/repos/$OWNER/$REPO/contents/$DIRECTORY?ref=$BRANCH") 46 | 47 | # Extract URLs of files with the .gif extension 48 | local icons=($(echo "$response" | jq -r '.[] | select(.type == "file" and (.name | test("\\.gif$"))) | .download_url')) 49 | 50 | # Return the list of icons 51 | echo "${icons[@]}" 52 | } 53 | 54 | # Verify if a file is a valid GIF 55 | verify_gif() { 56 | local FILE_NAME="$1" 57 | 58 | if file -b --mime-type "$FILE_NAME" | grep -q '^image/gif$'; then 59 | # File is a valid GIF 60 | return 0 61 | else 62 | echo -e "${RED}Error: File $FILE_NAME is NOT a valid GIF file.${NC}" 63 | return 1 64 | fi 65 | } 66 | 67 | # Upload an icon to a clock device 68 | upload_icon() { 69 | local IP_ADDRESS="$1" 70 | local ICON_NAME="$2" 71 | local FILE_NAME="$3" 72 | 73 | URL="http://$IP_ADDRESS/edit" 74 | TEMP_FILE=".$FILE_NAME" 75 | 76 | BASE_URL="https://raw.githubusercontent.com/jeeftor/HomeAssistant/master/icons/" 77 | GIF_FILE="$BASE_URL/$ICON_NAME" 78 | 79 | curl -L -s -X GET "$GIF_FILE" -o "$TEMP_FILE" 80 | 81 | if verify_gif "$TEMP_FILE"; then 82 | curl -X POST -F "file=@$TEMP_FILE;filename=/ICONS/$FILE_NAME" "$URL" 83 | echo -e "${GREEN}Uploaded icon:${NC} $FILE_NAME${NC}" 84 | else 85 | echo -e "${RED}Error: File $FILE_NAME does not appear to be a valid GIF file.${NC}" 86 | echo -e "${RED}Try yourself with:${NC} curl -L -s -X GET \"$GIF_FILE\" -o $TEMP_FILE" 87 | fi 88 | 89 | rm -f "$TEMP_FILE" 90 | } 91 | 92 | # Prompt for IP address if not provided as a command-line argument 93 | prompt_ip_address() { 94 | if [ -z "$1" ]; then 95 | read -rp "Enter the IP address: " IP_ADDRESS 96 | else 97 | IP_ADDRESS="$1" 98 | fi 99 | 100 | # Validate IP address format 101 | if ! [[ $IP_ADDRESS =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 102 | echo -e "${RED}Error: Invalid IP address format.${NC}" 103 | exit 1 104 | fi 105 | } 106 | 107 | # Main script logic 108 | main() { 109 | # Check if 'jq' is installed 110 | check_jq 111 | 112 | # Prompt for IP address 113 | prompt_ip_address "$1" 114 | 115 | # List icon directories 116 | echo -e "${GREEN}Available icon directories:${NC}" 117 | directories=($(list_icon_directories)) 118 | 119 | # Prompt for directory selection 120 | PS3="Select a directory: " 121 | select DIRECTORY_NAME in "${directories[@]}"; do 122 | if [[ -n $DIRECTORY_NAME ]]; then 123 | break 124 | else 125 | echo -e "${YELLOW}Invalid selection. Please try again.${NC}" 126 | fi 127 | done 128 | 129 | # Example usage 130 | ICONS=($(list_icons "$DIRECTORY_NAME")) 131 | 132 | echo -e "${YELLOW}Downloading icons...${NC}" 133 | 134 | for ICON_URL in "${ICONS[@]}"; do 135 | ICON_NAME=$(basename "$ICON_URL") 136 | 137 | upload_icon "$IP_ADDRESS" "$DIRECTORY_NAME/$ICON_NAME" "$ICON_NAME" 138 | done 139 | } 140 | 141 | # Execute the main script 142 | main "$@" 143 | -------------------------------------------------------------------------------- /icons/weather/w-clear-night.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-clear-night.gif -------------------------------------------------------------------------------- /icons/weather/w-cloudy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-cloudy.gif -------------------------------------------------------------------------------- /icons/weather/w-exceptional.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-exceptional.gif -------------------------------------------------------------------------------- /icons/weather/w-fog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-fog.gif -------------------------------------------------------------------------------- /icons/weather/w-hail.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-hail.gif -------------------------------------------------------------------------------- /icons/weather/w-lightning-rainy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-lightning-rainy.gif -------------------------------------------------------------------------------- /icons/weather/w-lightning.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-lightning.gif -------------------------------------------------------------------------------- /icons/weather/w-partlycloudy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-partlycloudy.gif -------------------------------------------------------------------------------- /icons/weather/w-pouring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-pouring.gif -------------------------------------------------------------------------------- /icons/weather/w-rainy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-rainy.gif -------------------------------------------------------------------------------- /icons/weather/w-snowy-rainy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-snowy-rainy.gif -------------------------------------------------------------------------------- /icons/weather/w-snowy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-snowy.gif -------------------------------------------------------------------------------- /icons/weather/w-sunny.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-sunny.gif -------------------------------------------------------------------------------- /icons/weather/w-sunrise.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-sunrise.gif -------------------------------------------------------------------------------- /icons/weather/w-sunset.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-sunset.gif -------------------------------------------------------------------------------- /icons/weather/w-windy-variant.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-windy-variant.gif -------------------------------------------------------------------------------- /icons/weather/w-windy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/weather/w-windy.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_e.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_e.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_n.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_n.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_ne.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_ne.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_nw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_nw.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_s.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_se.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_se.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_sw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_sw.gif -------------------------------------------------------------------------------- /icons/wind_direction/wind_w.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/icons/wind_direction/wind_w.gif -------------------------------------------------------------------------------- /resources/WeatherPreview1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/WeatherPreview1.gif -------------------------------------------------------------------------------- /resources/annotated_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/annotated_calendar.png -------------------------------------------------------------------------------- /resources/aqi_preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/aqi_preview.gif -------------------------------------------------------------------------------- /resources/calendar_preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/calendar_preview.gif -------------------------------------------------------------------------------- /resources/pollen/icon_flower_frame_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_flower_frame_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_flower_frame_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_flower_frame_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_flower_frame_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_grass_frame_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/pollen/icon_grass_frame_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/pollen/icon_grass_frame_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/pollen/icon_grass_frame_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/pollen/icon_grass_frame_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/pollen/icon_tree_frame_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_tree_frame_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_tree_frame_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_tree_frame_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_tree_frame_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/pollen/icon_weed_frame_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/pollen/icon_weed_frame_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/pollen/icon_weed_frame_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/pollen/icon_weed_frame_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/pollen/icon_weed_frame_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/pollen/pollenPreview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/pollen/pollenPreview.gif -------------------------------------------------------------------------------- /resources/uv_preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeeftor/HomeAssistant/1f1bbf0b8d449a7125b5e0f6fa0fa4e85ba1d49b/resources/uv_preview.gif -------------------------------------------------------------------------------- /scripts/inovelli_led.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Calculation References: 3 | # https://nathanfiscus.github.io/inovelli-notification-calc/ 4 | # https://community.inovelli.com/t/home-assistant-2nd-gen-switch-rgb-working/168/62 5 | # https://docs.google.com/spreadsheets/d/1bEpujdvBPZY9Fl61PZLUWuHanD2VAkRORFZ5D9xjLzA/edit?usp=sharing 6 | # 7 | # Changes: 8 | # July 22, 2020: Incorporating changes from Kevin Schlichter. 9 | # https://github.com/kschlichter/Home-Assistant-Inovelli-Red-Dimmer-Switch 10 | # 11 | # September 17, 2020: There are some massive improvements to my version of this code. Here are the highlights: 12 | # 1. Choose - using the recently added choose: feature a separate call has been created for the Z-wave and OZW 13 | # versions of the service call. 14 | # 2. Variables - using 0.115’s new variables: feature the variables sent each service call only have to be 15 | # calculated once. 16 | # 3. Supported Features - I realized that I could probably tell the difference between modules using the 17 | # “supported_features” attribute of each switch. For example my dimmer’s supported features is “33”. 18 | # 19 | # September 18, 2020: Added "model" parameter with options of dimmer, switch, combo_light, combo_fan. This replaces 20 | # supported_features as the combo fan/light switch also had the same supported_features value. 21 | # 22 | # February 24, 2021: Added support for Z-Wave JS in place of OpenZwave (ozw). 23 | # The ozw code is remarked out for those that still need it. 24 | # 25 | # February 27, 2021: 26 | # 1. Added zwave_integration at top of "variables:" section to allow users to define which integration is 27 | # installed ("zwave", "ozw", "zwave_js"). I just don't see a simple way to auto-detect this. 28 | # 2. Added a comment describing the "model" variable. 29 | # 3. Replaced personal "script.debug" service call with universal "persistent_notification.create". 30 | # Unremarking these lines could help you troubleshoot why something isn't working as expected. 31 | # 4. Updated broken spreadsheet link with public copy stored in my Google Docs account. 32 | # Thanks for the heads up Kevin Schlichter! 33 | # 34 | # March 26, 2021 35 | # 1. Added fields to help users experimenting in the Services Developer Tool. 36 | # 37 | # April 3, 2021 38 | # Incorporated @firstof9's changes: 39 | # 1. Set execution mode to "parallel" to all this script to potentially run on more than one devices simultaneously. 40 | # 2. Implement ZWave JS's new zwave_js.bulk_set_partial_config_parameters command. 41 | # Source: https://gist.github.com/firstof9/b88d072a81c54b314fe7ddb901fc5c29 42 | # 43 | mode: parallel 44 | variables: 45 | # REQUIRED to be one of these options: "zwave", "ozw", "zwave_js" 46 | # Advanced: If you'd like to have your device list filtered in the Services Developer Tool, 47 | # then unremark out "integration: zwave_js" under the fields section, and change the integration 48 | # name to match. 49 | zwave_integration: "zwave_js" 50 | 51 | # * Strongly recommended -> Use the passed model type ("dimmer", "switch", "combo_light") when present. 52 | # * If not present, then attempt to identify the type using the "product_name" attribute (which is only 53 | # unfortunately only available in the original zwave integration). 54 | # * Finally, assume the model type is "dimmer". 55 | model: | 56 | {% if model is string %} 57 | {{ model }} 58 | {%- elif state_attr(entity_id, 'product_name') is string %} 59 | {%- if 'LZW31' in state_attr(entity_id, 'product_name') %} 60 | dimmer 61 | {%- elif 'LZW36' in state_attr(entity_id, 'product_name') %} 62 | combo_light 63 | {%- else %} 64 | switch 65 | {%- endif %} 66 | {%- else %} 67 | dimmer 68 | {%- endif %} 69 | parameters: 70 | dimmer: 16 71 | combo_light: 24 72 | combo_fan: 25 73 | switch: 8 74 | node_id: '{{ state_attr(entity_id,"node_id") }}' 75 | color: | 76 | {%- if color is not number %} 77 | {{ color|default("Yellow")|title }} 78 | {%- else %} 79 | {{ color|int }} 80 | {% endif %} 81 | # 1-10 82 | level: "{{ level|default(4)|int }}" 83 | duration: '{{ duration|default("Indefinitely")|title }}' 84 | effect: '{{ effect|default("Blink")|title }}' 85 | colors: 86 | "Off": 0 87 | "Red": 1 88 | "Orange": 21 89 | "Yellow": 42 90 | "Green": 85 91 | "Cyan": 127 92 | "Teal": 145 93 | "Blue": 170 94 | "Purple": 195 95 | "Light Pink": 220 96 | "Pink": 234 97 | durations: 98 | "Off": 0 99 | "1 Second": 1 100 | "2 Seconds": 2 101 | "3 Seconds": 3 102 | "4 Seconds": 4 103 | "5 Seconds": 5 104 | "6 Seconds": 6 105 | "7 Seconds": 7 106 | "8 Seconds": 8 107 | "9 Seconds": 9 108 | "10 Seconds": 10 109 | "15 Seconds": 15 110 | "20 Seconds": 20 111 | "25 Seconds": 25 112 | "30 Seconds": 30 113 | "35 Seconds": 35 114 | "40 Seconds": 40 115 | "45 Seconds": 45 116 | "50 Seconds": 50 117 | "55 Seconds": 55 118 | "60 Seconds": 60 119 | "2 Minutes": 62 120 | "3 Minutes": 63 121 | "4 Minutes": 64 122 | "10 Minutes": 70 123 | "15 Minutes": 75 124 | "30 Minutes": 90 125 | "45 Minutes": 105 126 | "1 Hour": 120 127 | "2 Hours": 122 128 | "Indefinitely": 255 129 | effects_dimmer: 130 | "Off": 0 131 | "Solid": 1 132 | "Chase": 2 133 | "Fast Blink": 3 134 | "Slow Blink": 4 135 | "Blink": 4 136 | "Pulse": 5 137 | "Breath": 5 138 | effects_switch: 139 | "Off": 0 140 | "Solid": 1 141 | "Fast Blink": 2 142 | "Slow Blink": 3 143 | "Blink": 3 144 | "Pulse": 4 145 | "Breath": 4 146 | sequence: 147 | # Preform the Inovelli math. 148 | - variables: 149 | parameter: "{{ parameters[model|lower] }}" 150 | color: "{{ colors[color|title]|int }}" 151 | duration: "{{ durations[duration|title] }}" 152 | effect: | 153 | {% if model == "switch" %} 154 | {{- effects_switch[effect|title] }} 155 | {%- else %} 156 | {{- effects_dimmer[effect|title] }} 157 | {% endif %} 158 | inovelli_math: | 159 | {%- if effect|int > 0 %} 160 | {{ color|int + (level|int * 256) + (duration|int * 65536) + (effect|int * 16777216) }} 161 | {%- else %} 162 | 0 163 | {% endif %} 164 | # Unremark to provide an notification with troubleshooting information. 165 | # - service: persistent_notification.create 166 | # data: 167 | # title: "DEBUG: script.inovelli_led" 168 | # notification_id: "inovelli_led" 169 | # message: | 170 | # zwave_integration: {{ zwave_integration }} 171 | # model: {{ model }} 172 | # color: '{{ color|title }}' 173 | # level: '{{ level }}' 174 | # duration: '{{ duration|title }}' 175 | # effect: '{{ effect|title }}' 176 | # node_id: '{{ node_id }}' 177 | # parameter: '{{ parameter }}' 178 | # value: '{{ inovelli_math }}' 179 | 180 | - choose: 181 | # The Z-Wave JS integration requires this service call. 182 | - conditions: 183 | - '{{ zwave_integration == "zwave_js" }}' 184 | sequence: 185 | # Clear the previous effect. 186 | - service: zwave_js.bulk_set_partial_config_parameters 187 | target: 188 | entity_id: "{{ entity_id }}" 189 | data: 190 | parameter: "{{ parameter }}" 191 | value: 0 192 | 193 | # Start the new effect. 194 | - service: zwave_js.bulk_set_partial_config_parameters 195 | target: 196 | entity_id: "{{ entity_id }}" 197 | data: 198 | parameter: "{{ parameter }}" 199 | value: "{{ inovelli_math }}" 200 | 201 | # The OZW integration requires this service call. 202 | - conditions: 203 | - '{{ zwave_integration == "ozw" }}' 204 | sequence: 205 | # Clear the previous effect. 206 | - service: ozw.set_config_parameter 207 | data: 208 | node_id: "{{ node_id }}" 209 | parameter: "{{ parameter }}" 210 | value: 0 211 | 212 | # Start the new effect. 213 | - service: ozw.set_config_parameter 214 | data: 215 | node_id: "{{ node_id }}" 216 | parameter: "{{ parameter }}" 217 | value: "{{ inovelli_math }}" 218 | 219 | # The Z-wave integration requires this service call. 220 | default: 221 | # Clear the previous effect. 222 | - service: zwave.set_config_parameter 223 | data: 224 | node_id: "{{ node_id }}" 225 | parameter: "{{ parameter }}" 226 | size: 4 227 | value: 0 228 | 229 | # Start the new effect. 230 | - service: zwave.set_config_parameter 231 | data: 232 | node_id: "{{ node_id }}" 233 | parameter: "{{ parameter }}" 234 | size: 4 235 | value: "{{ inovelli_math }}" 236 | 237 | fields: 238 | entity_id: 239 | description: Light or switch which represents 240 | example: light.red_series_dimmer 241 | selector: 242 | entity: 243 | #integration: zwave_js 244 | model: 245 | description: 'Device type: dimmer (default), switch, combo_light' 246 | example: dimmer 247 | selector: 248 | select: 249 | options: 250 | - dimmer 251 | - switch 252 | - combo_light 253 | color: 254 | description: 'Choose a color.' 255 | example: purple 256 | selector: 257 | select: 258 | options: 259 | - "Off" 260 | - Red 261 | - Orange 262 | - Yellow 263 | - Green 264 | - Cyan 265 | - Teal 266 | - Blue 267 | - Purple 268 | - Light Pink 269 | - Pink 270 | effect: 271 | description: 'Choose an effect.' 272 | example: blink 273 | selector: 274 | select: 275 | options: 276 | - "Off" 277 | - Solid 278 | - Chase 279 | - Fast Blink 280 | - Slow Blink 281 | - Blink 282 | - Pulse 283 | - Breath 284 | duration: 285 | description: 'How long should the effect run?' 286 | example: 10 seconds 287 | selector: 288 | select: 289 | options: 290 | - "Off" 291 | - 1 Second 292 | - 2 Seconds 293 | - 3 Seconds 294 | - 4 Seconds 295 | - 5 Seconds 296 | - 6 Seconds 297 | - 7 Seconds 298 | - 8 Seconds 299 | - 9 Seconds 300 | - 10 Seconds 301 | - 15 Seconds 302 | - 20 Seconds 303 | - 25 Seconds 304 | - 30 Seconds 305 | - 35 Seconds 306 | - 40 Seconds 307 | - 45 Seconds 308 | - 50 Seconds 309 | - 55 Seconds 310 | - 60 Seconds 311 | - 2 Minutes 312 | - 3 Minutes 313 | - 4 Minutes 314 | - 10 Minutes 315 | - 15 Minutes 316 | - 30 Minutes 317 | - 45 Minutes 318 | - 1 Hour 319 | - 2 Hours 320 | - Indefinitely -------------------------------------------------------------------------------- /scripts/inovelli_led_off.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | variables: 3 | entity_id: '{{ entity_id|default("light.red_series_dimmer") }}' 4 | 5 | sequence: 6 | - service: script.inovelli_led 7 | data: 8 | entity_id: "{{ entity_id }}" 9 | effect: "Off" 10 | 11 | # Check for another active status. 12 | - service: script.inovelli_led_status_restore -------------------------------------------------------------------------------- /scripts/inovelli_led_set_defaults.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # Incorporates default LED color + dimmer brightness. 3 | # Originally in a script by Kevin Schlichter https://github.com/kschlichter/Home-Assistant-Inovelli-Red-Dimmer-Switch 4 | variables: 5 | # REQUIRED to be one of these options: "zwave", "ozw", "zwave_js" 6 | zwave_integration: "zwave_js" 7 | 8 | # * Strongly recommended -> Use the passed model type ("dimmer", "switch", "combo_light") when present. 9 | # * If not present, then attempt to identify the type using the "product_name" attribute (which is only 10 | # unfortunately only available in the original zwave integration). 11 | # * Finally, assume the model type is "dimmer". 12 | model: | 13 | {% if model is string %} 14 | {{ model }} 15 | {%- elif state_attr(entity_id, 'product_name') is string %} 16 | {%- if 'LZW31' in state_attr(entity_id, 'product_name') %} 17 | dimmer 18 | {%- elif 'LZW36' in state_attr(entity_id, 'product_name') %} 19 | combo_light 20 | {%- else %} 21 | switch 22 | {%- endif %} 23 | {%- else %} 24 | dimmer 25 | {%- endif %} 26 | node_id: '{{ state_attr(entity_id,"node_id") }}' 27 | color: '{{ color|default("Blue") }}' 28 | level_on: '{{ level_on|default("10") }}' 29 | level_off: '{{ level_off|default("3") }}' 30 | parameters: 31 | dimmer: 32 | color: 13 33 | level_on: 14 34 | level_off: 15 35 | combo_light: 36 | color: 18 37 | level_on: 19 38 | level_off: 22 39 | combo_fan: 40 | color: 20 41 | level_on: 21 42 | level_off: 23 43 | switch: 44 | color: 5 45 | level_on: 6 46 | level_off: 7 47 | colors: 48 | "Off": 0 49 | "Red": 1 50 | "Orange": 21 51 | "Yellow": 42 52 | "Green": 85 53 | "Cyan": 127 54 | "Teal": 145 55 | "Blue": 170 56 | "Purple": 195 57 | "Light Pink": 220 58 | "Pink": 234 59 | sequence: 60 | - variables: 61 | color: '{{ colors[color|title] }}' 62 | color_parameter: '{{ parameters[model]["color"] }}' 63 | level_on_parameter: '{{ parameters[model]["level_on"] }}' 64 | level_off_parameter: '{{ parameters[model]["level_off"] }}' 65 | 66 | # - service: script.debug 67 | # data: 68 | # id: 02 69 | # message: | 70 | # node_id: '{{ node_id }}' 71 | # parameter: '{{ parameters[model]["color"] }}' 72 | # color: '{{ color }}' 73 | # level_on: '{{ level_on }}' 74 | # level_off: '{{ level_off }}' 75 | 76 | - choose: 77 | # The Z-Wave JS integration requires this service call. 78 | - conditions: 79 | - '{{ zwave_integration == "zwave_js" }}' 80 | sequence: 81 | - service: zwave_js.set_config_parameter 82 | target: 83 | entity_id: '{{ entity_id }}' 84 | data: 85 | parameter: '{{ color_parameter }}' 86 | value: '{{ color }}' 87 | 88 | - service: zwave_js.set_config_parameter 89 | target: 90 | entity_id: '{{ entity_id }}' 91 | data: 92 | parameter: '{{ level_on_parameter }}' 93 | value: '{{ level_on }}' 94 | 95 | # - service: ozw.set_config_parameter 96 | - service: zwave_js.set_config_parameter 97 | target: 98 | entity_id: '{{ entity_id }}' 99 | data: 100 | parameter: '{{ level_off_parameter }}' 101 | value: '{{ level_off }}' 102 | 103 | # The OZW integration requires this service call. 104 | - conditions: 105 | - '{{ zwave_integration == "ozw" }}' 106 | sequence: 107 | - service: ozw.set_config_parameter 108 | data: 109 | node_id: '{{ node_id }}' 110 | parameter: '{{ color_parameter }}' 111 | value: '{{ color }}' 112 | 113 | - service: ozw.set_config_parameter 114 | data: 115 | node_id: "{{ node_id }}" 116 | parameter: '{{ level_on_parameter }}' 117 | value: '{{ level_on }}' 118 | 119 | # - service: ozw.set_config_parameter 120 | - service: zwave_js.set_config_parameter 121 | data: 122 | node_id: "{{ node_id }}" 123 | parameter: '{{ level_off_parameter }}' 124 | value: '{{ level_off }}' 125 | 126 | # The Z-wave integration requires this service call. 127 | default: 128 | - service: zwave.set_config_parameter 129 | data: 130 | node_id: '{{ node_id }}' 131 | parameter: '{{ color_parameter }}' 132 | size: 2 133 | value: '{{ color }}' 134 | 135 | - service: zwave.set_config_parameter 136 | data: 137 | node_id: "{{ node_id }}" 138 | parameter: '{{ level_on_parameter }}' 139 | size: 1 140 | value: '{{ level_on }}' 141 | 142 | - service: zwave.set_config_parameter 143 | data: 144 | node_id: "{{ node_id }}" 145 | parameter: '{{ level_off_parameter }}' 146 | size: 1 147 | value: '{{ level_off }}' 148 | 149 | fields: 150 | entity_id: 151 | description: Light or switch which represents 152 | example: light.red_series_dimmer 153 | selector: 154 | entity: 155 | #integration: zwave_js 156 | model: 157 | description: 'Device type: dimmer (default), switch, combo_light' 158 | example: dimmer 159 | selector: 160 | select: 161 | options: 162 | - dimmer 163 | - switch 164 | - combo_light 165 | color: 166 | description: 'Choose a color.' 167 | example: purple 168 | selector: 169 | select: 170 | options: 171 | - "Off" 172 | - Red 173 | - Orange 174 | - Yellow 175 | - Green 176 | - Cyan 177 | - Teal 178 | - Blue 179 | - Purple 180 | - Light Pink 181 | - Pink 182 | level_on: 183 | description: LED Brightness when on 184 | example: 10 185 | level_off: 186 | description: LED Brightness when off 187 | example: 3 -------------------------------------------------------------------------------- /scripts/inovelli_led_status_restore.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | variables: 3 | entity_id: '{{ entity_id|default("light.red_series_dimmer") }}' 4 | event_colors: 5 | front_door: "cyan" 6 | garage_door: "purple" 7 | color: | 8 | {% if is_state('lock.front_door','unlocked') %} 9 | {{ event_colors["front_door"] }} 10 | {% elif is_state('cover.garage_door','open ') %} 11 | {{ event_colors["garage_door"] }} 12 | {% endif %} 13 | sequence: 14 | - choose: 15 | - conditions: '{{ color|trim != "" }}' 16 | sequence: 17 | - service: script.inovelli_led 18 | data: 19 | entity_id: "{{ entity_id }}" 20 | color: "{{ color }}" -------------------------------------------------------------------------------- /scripts/inovelli_led_status_start.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | variables: 3 | entity_id: '{{ entity_id|default("light.red_series_dimmer") }}' 4 | model: | 5 | {% set models = { 6 | light.red_series_dimmer: "dimmer" 7 | } %} 8 | {{ models[entity_id] }} 9 | color: '{{ color|default("red") }}' 10 | effect: '{{ effect|default("chase") }}' 11 | duration: '{{ duration|default("15 seconds") }}' 12 | level: '{{ level|default("8") }}' 13 | state_entity: '{{ state_entity|default("none") }}' 14 | state: '{{ state|default("false") }}' 15 | sequence: 16 | - service: script.inovelli_led 17 | data: 18 | entity_id: "{{ entity_id }}" 19 | model: "{{ model }}" 20 | color: "{{ color }}" 21 | effect: "{{ effect }}" 22 | duration: "{{ duration }}" 23 | level: "{{ level }}" 24 | 25 | # If the state remains after a short delay then change the effect to something less attention grabbing. 26 | - delay: 27 | seconds: 14 28 | - condition: template 29 | value_template: "{{ states(state_entity) == state }}" 30 | 31 | - service: script.inovelli_led 32 | data_template: 33 | entity_id: "{{ entity_id }}" 34 | model: "{{ model }}" 35 | color: "{{ color }}" 36 | effect: solid 37 | duration: indefinitely 38 | level: 4 --------------------------------------------------------------------------------