---
Brian HanifinDIY Home Automation with Home Assistant.daily1
https://brianhanifin.com
Thu, 18 Feb 2021 08:45:00 -0800DIY Irrigation Controller: Home Assistant IntegrationBrian Hanifin<p><em>Articles in this series:</em></p>
<ol>
<li><em><a href="../diy-irrigation-controller-esphome-home-assistant/">Hardware, Electronics, and ESPHome code</a></em></li>
<li><em><a href="../diy-irrigation-controller-lovelace-user-interface-home-assistant/">Lovelace User Interface</a></em></li>
<li><em>Entities & Simplified User Interface</em></li>
</ol>
<h2 id="home-assistant-entities--simplified-user-interface">Home Assistant Entities & Simplified User Interface</h2>
<p>Your many questions helped me realize that I totally dropped the ball when I attempted to share how I
created the Lovelace User Interface (UI) for my DIY Irrigation Project. Not only did I overly complicate
the Lovelace code with custom components, but I also omitted how I created the necessary entities in
Home Assistant.</p>
<p>I now present you with the <strong>simplified and complete</strong> code to properly integrate Home Assistant with
my ESPHome DIY Irritation Controller project.</p>
<h3 id="add-supporting-entities">Add Supporting Entities</h3>
<h4 id="using-helpers-configuration-ui">Using Helpers Configuration UI</h4>
<p>Home Assistant’s <strong>Helpers Configuration UI</strong> <a href="https://www.home-assistant.io/blog/2020/03/18/release-107/#helpers-configuration-panel">introduced in 2020</a>
is the easiest way to add these <code class="language-yaml highlighter-rouge"><span class="s">input_text</span></code> and <code class="language-yaml highlighter-rouge"><span class="s">input_number</span></code> entities. If you choose to go this route the YAML code
below will be a useful reference.</p>
<h4 id="using-a-package-file">Using a “Package” File</h4>
<p>I have chosen instead to split my Irrigation Controller entities and automation into a “package file”.</p>
<ol>
<li>First we need to create new file named <code class="language-yaml highlighter-rouge"><span class="s">irrigation.yaml</span></code> in your Home Assistant <code class="language-yaml highlighter-rouge"><span class="s">/config</span></code> folder.</li>
<li>Copy and paste the following code into this file to get started.</li>
</ol>
<blockquote>
<i>Note:</i> In this example you will see that I have two irrigation zones configured. If you have more than two zones
you will need to add enough `input_text` and `input_number` entities to accommodate your additional zones.
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="c1"># Lovelace UI to set a list of irrigation cycle times.</span>
<span class="na">input_text</span><span class="pi">:</span>
<span class="na">irrigation_zone1_times</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">List of Times (eg. 08:00,12:00,15:00)</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:clock-outline</span>
<span class="na">irrigation_zone2_times</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">List of Times (eg. 11:00,15:30)</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:clock-outline</span>
<span class="c1"># Lovelace UI to set the duration of each irrigation cycle.</span>
<span class="na">input_number</span><span class="pi">:</span>
<span class="na">irrigation_zone1_duration</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Duration in Minutes</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:timer-sand</span>
<span class="na">min</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">max</span><span class="pi">:</span> <span class="m">60</span>
<span class="na">step</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">unit_of_measurement</span><span class="pi">:</span> <span class="s2">"</span><span class="s">minutes"</span>
<span class="na">irrigation_zone2_duration</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Duration in Minutes</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:timer-sand</span>
<span class="na">min</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">max</span><span class="pi">:</span> <span class="m">60</span>
<span class="na">step</span><span class="pi">:</span> <span class="m">1</span>
<span class="na">unit_of_measurement</span><span class="pi">:</span> <span class="s2">"</span><span class="s">minutes"</span>
<span class="c1">### Optional offline notifications. Uncomment this automation if you'd like an notification </span>
<span class="c1">### should the device be disconnected from the network for two hours!</span>
<span class="c1"># automation:</span>
<span class="c1"># # Warn me if the system ever goes offline for more than two hours!</span>
<span class="c1"># - alias: irrigation_system_offline</span>
<span class="c1"># initial_state: true</span>
<span class="c1"># trigger:</span>
<span class="c1"># - platform: state</span>
<span class="c1"># entity_id: binary_sensor.irrigation_controller_status</span>
<span class="c1"># to: 'off'</span>
<span class="c1"># for: '02:00:00'</span>
<span class="c1"># action:</span>
<span class="c1"># - service: persistent_notification.create</span>
<span class="c1"># data:</span>
<span class="c1"># title: "Irrigation System Offline"</span>
<span class="c1"># message: "The Irrigation System has been offline for 2 hours!"</span>
<span class="c1"># notification_id: "offline"</span>
<span class="c1"># - service: notify.mobile_app_iphone_brian</span>
<span class="c1"># data:</span>
<span class="c1"># title: "Irrigation System Offline"</span>
<span class="c1"># message: "The Irrigation System has been offline for 2 hours"</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h4 id="include-the-package-file-in-configurationyaml">Include the “Package” file in configuration.yaml</h4>
<blockquote>
<i>Note:</i> You can skip this section if you are adding the above entities to Home Assistant another way.
</blockquote>
<p>See the <a href="https://www.home-assistant.io/docs/configuration/packages/">Home Assistant Packages Documentation</a>
for a deeper explanation.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="na">homeassistant</span><span class="pi">:</span>
<span class="na">packages</span><span class="pi">:</span>
<span class="na">irrigation</span><span class="pi">:</span> <span class="kt">!include</span> <span class="s">irrigation.yaml</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="simplified-lovelace-user-interface">Simplified Lovelace User Interface</h3>
<p>This version of the interface simply uses the built in Entities Card and optionally a Glance Card.</p>
<h4 id="create-irrigation-zone-user-interfaces">Create Irrigation Zone User Interfaces</h4>
<p>You will need to add an Entities Card for each Irrigation Zone.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="na">type</span><span class="pi">:</span> <span class="s">entities</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">switch.irrigation_zone1</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_zone1_next</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Next 🕑</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_zone1_remaining</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Remaining ⏳</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">input_text.irrigation_zone1_times</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">input_number.irrigation_zone1_duration</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Zone </span><span class="m">1</span>
<span class="na">show_header_toggle</span><span class="pi">:</span> <span class="no">false</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h4 id="optional-create-irrigation-controller-status-card">Optional: Create Irrigation Controller Status Card</h4>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="na">type</span><span class="pi">:</span> <span class="s">glance</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">binary_sensor.irrigation_controller_status</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Status</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_controller_uptime</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Uptime</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_controller_wifi_signal</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">WiFi Signal</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Controller Status</span>
</pre></td></tr></tbody></table></code></pre></div></div>
Thu, 18 Feb 2021 08:45:00 -0800
https://brianhanifin.com/posts/diy-irrigation-controller-lovelace-ui-update/
https://brianhanifin.com/posts/diy-irrigation-controller-lovelace-ui-update/Outdoor Illuminance Template SensorBrian Hanifin<p>Phil Bruckner (@pnbruckner) created a terrific outdoor <a href="https://github.com/pnbruckner/ha-illuminance">Illuminance sensor</a> component. Unfortunately it seemed
that every time he has added support for a new weather service, support for that service eventually get pulled for one reason
or another.</p>
<h2 id="introducing-the-outdoor-illuminance-educated-guessor">Introducing the <em>Outdoor Illuminance Educated Guessor</em></h2>
<p>I took a look at the code and decided it would be a nice challenge to convert his Python code into a Home Assistant sensor
template. So far I believe I have succeeded. I made use of Phil’s <a href="https://github.com/pnbruckner/ha-sun2">sun2 component</a> to gather today’s sunrise and sunset
values. I manually attempted to create a fairly universal <code class="language-yaml highlighter-rouge"><span class="s">condition_factors</span></code> table and used some replacement filters in an
attempt to normalize the conditions between weather services.</p>
<blockquote>
<i>Note:</i> The intent of this code is to give all of you (that aren't comfortable with Python) the ability to customize the
condition factors and even the sun factor in any way that works best for you. If you find a weather condition that this code
doesn't cover, add it to your personal template!
</blockquote>
<h2 id="code">Code</h2>
<p>The template code is performing very well! I started this as a personal challenge just to see if I could do it, and I did.
I don’t plan on updating the code much in the future. Consider this your starting point!</p>
<p>By the way, I ran it side-by-side with <a href="https://github.com/nkm8/ha-illuminance">nkm8’s ha-illuminance fork</a> and the above chart was the result
overnight! The reason for the slight difference in timing is ha-illuminance estimates dawn and dusk, while I am getting, what
I assume is a more accurate time from @pnbruckner’s sun2 component. Due to this comparison I modified the template code to
keep the lowest lx value at 10 instead of 0.</p>
<h3 id="sensoroutdoor_illuminance---template-sensor">sensor.outdoor_illuminance - template sensor</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="c1"># pnbruckner's sensor component as a template.</span>
<span class="c1"># https://github.com/pnbruckner/ha-illuminance/blob/master/custom_components/illuminance/sensor.py</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">sensors</span><span class="pi">:</span>
<span class="na">outdoor_illuminance</span><span class="pi">:</span>
<span class="na">friendly_name</span><span class="pi">:</span> <span class="s">Outdoor Illuminance Educated Guessor</span>
<span class="na">icon_template</span><span class="pi">:</span> <span class="s">mdi:brightness-auto</span>
<span class="na">unit_of_measurement</span><span class="pi">:</span> <span class="s">lx</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{%- set factors = namespace(condition='',sun='') %}</span>
<span class="s">{#- Retrieve the current condition and normalize the value #}</span>
<span class="s">{%- set current_condition = states("weather.accuweather") %}</span>
<span class="s">{%- set current_condition = current_condition|lower|replace("partly cloudy w/ ","")|replace("mostly cloudy w/ ","")|replace("freezing","")|replace("and","")|replace(" ", "")|replace("-", " ")|replace("_", " ")|replace("(","")|replace(")","") %}</span>
<span class="s">{#- Assign a seemingly arbitrary number to the condition factor #}</span>
<span class="s">{%- set condition_factors = {</span>
<span class="s">"10000": ("clear", "clearnight", "sunny", "windy", "exceptional"),</span>
<span class="s">"7500": ("partlycloudy", "partlysunny", "mostlysunny", "mostlyclear", "hazy", "hazysunshine", "intermittentclouds"),</span>
<span class="s">"2500": ("cloudy", "mostlycloudy"),</span>
<span class="s">"1000": ("fog", "rainy", "showers", "snowy", "snowyheavy", "snowyrainy", "flurries", "chanceflurries", "chancerain", "chancesleet", "drearyovercast", "sleet"),</span>
<span class="s">"200": ("hail", "lightning", "tstorms")</span>
<span class="s">} %}</span>
<span class="s">{%- for factor in condition_factors if current_condition in condition_factors[factor] %}</span>
<span class="s">{%- set factors.condition = factor %}</span>
<span class="s">{%- endfor %}</span>
<span class="s">{#- Compute Sun Factor #}</span>
<span class="s">{%- set right_now = states.sensor.time.last_updated.timestamp() %}</span>
<span class="s">{%- set sunrise = states("sensor.sunrise") | as_timestamp %}</span>
<span class="s">{%- set sunrise_begin = states("sensor.dawn") | as_timestamp %}</span>
<span class="s">{%- set sunrise_end = sunrise + (40 * 60) %}</span>
<span class="s">{%- set sunset = states("sensor.sunset") | as_timestamp %}</span>
<span class="s">{%- set sunset_begin = sunset - (40 * 60) %}</span>
<span class="s">{%- set sunset_end = states("sensor.dusk") | as_timestamp %}</span>
<span class="s">{%- if sunrise_end < right_now and right_now < sunset_begin %}</span>
<span class="s">{%- set factors.sun = 1 %}</span>
<span class="s">{%- elif sunset_end < right_now or right_now < sunrise_begin %}</span>
<span class="s">{%- set factors.sun = 0 %}</span>
<span class="s">{%- elif right_now <= sunrise_end %}</span>
<span class="s">{%- set factors.sun = (right_now - sunrise_begin) / (60*60) %}</span>
<span class="s">{%- else %}</span>
<span class="s">{%- set factors.sun = (sunset_end - right_now) / (60*60) %}</span>
<span class="s">{%- endif %}</span>
<span class="s">{%- set factors.sun = 1 if factors.sun > 1 else factors.sun %}</span>
<span class="s">{# Take an educated guess #}</span>
<span class="s">{%- set illuminance = (factors.sun|float * factors.condition|float) | round %}</span>
<span class="s">{%- set illuminance = 10 if illuminance < 10 else illuminance %}</span>
<span class="s">{{ illuminance }}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="sun2-sensor-config">sun2 sensor config</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="nn">---</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">sun2</span>
<span class="na">monitored_conditions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">sunrise</span>
<span class="pi">-</span> <span class="s">sunset</span>
<span class="pi">-</span> <span class="s">dawn</span>
<span class="pi">-</span> <span class="s">dusk</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="optional-multiple-weather-condition-sensors">Optional: Multiple Weather Condition Sensors</h2>
<p>If your primary weather source goes “unavailable” sometimes (I’m looking at you AccuWeather), you can modify a few lines of code to add one or more backup sensors.</p>
<h3 id="current_condition-original-lines">current_condition: original lines</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- set factors = namespace(condition=''</span><span class="pi">,</span><span class="nv">sun='') %</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1">#- Retrieve the current condition and normalize the value #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set current_condition = states("weather.accuweather") %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set current_condition = current_condition|lower|replace("partly cloudy w/ "</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("mostly cloudy w/ "</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("freezing"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("and"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace(" "</span><span class="pi">,</span> <span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("-"</span><span class="pi">,</span> <span class="s2">"</span><span class="nv"> </span><span class="s">"</span><span class="nv">)|replace("_"</span><span class="pi">,</span> <span class="s2">"</span><span class="nv"> </span><span class="s">"</span><span class="nv">)|replace("("</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace(")"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">) %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="current_condition-new-lines-with-2-backup-condition-sensors">current_condition: new lines (with 2 backup condition sensors)</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- set factors = namespace(condition=''</span><span class="pi">,</span><span class="nv">sun=''</span><span class="pi">,</span><span class="nv">current_condition='') %</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1">#- Retrieve the current condition and normalize the value #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set weather_sensors =</span> <span class="pi">[</span>
<span class="s2">"</span><span class="s">weather.accuweather"</span><span class="pi">,</span>
<span class="s2">"</span><span class="s">sensor.openweathermap_condition"</span><span class="pi">,</span>
<span class="s2">"</span><span class="s">sensor.cc_climacell_weather_condition"</span>
<span class="pi">]</span> <span class="err">%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- for sensor in weather_sensors if states(sensor) != "unknown" %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set factors.current_condition = states(sensor) if factors.current_condition == "" else "" %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endfor %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set current_condition = factors.current_condition|lower|replace("partly cloudy w/ "</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("mostly cloudy w/ "</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("freezing"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("and"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace(" "</span><span class="pi">,</span> <span class="s2">"</span><span class="s">"</span><span class="nv">)|replace("-"</span><span class="pi">,</span> <span class="s2">"</span><span class="nv"> </span><span class="s">"</span><span class="nv">)|replace("_"</span><span class="pi">,</span> <span class="s2">"</span><span class="nv"> </span><span class="s">"</span><span class="nv">)|replace("("</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">)|replace(")"</span><span class="pi">,</span><span class="s2">"</span><span class="s">"</span><span class="nv">) %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
Fri, 25 Sep 2020 10:00:00 -0700
https://brianhanifin.com/posts/outdoor-illuminance-template-sensor/
https://brianhanifin.com/posts/outdoor-illuminance-template-sensor/Inovelli Red Series Status LED Script UpdateBrian Hanifin<p>This is a follow up to my previous article <a href="/posts/inovelli-dimmer-status-led-home-assistant/">Inovelli Z-Wave Dimmer Status LED in Home Assistant</a>. If you are
interested in the basics of how to use this script to send notification light animations to your
<a href="https://www.amazon.com/gp/product/B07T26MVYC/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B07T26MVYC&linkCode=as2&tag=brianhanifi0d-20&linkId=f70336e0578d04d9c39812db7ced730a">Inovelli (Red Series) Z-Wave Dimmer</a> start there. In this article I will explain the
improvements made to support all three Red Series in wall switches.</p>
<h2 id="added-compatibility">Added compatibility</h2>
<p>Here is an example call to <code class="language-yaml highlighter-rouge"><span class="s">script.inovelli_led</span></code> with the new <code class="language-yaml highlighter-rouge"><span class="s">model</span></code> parameter set to <code class="language-yaml highlighter-rouge"><span class="s">dimmer</span></code> (valid options are switch,
dimmer, combo_light, or combo_fan). This information allows one script to support the
<a href="https://www.amazon.com/gp/product/B07T26MVYC/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B07T26MVYC&linkCode=as2&tag=brianhanifi0d-20&linkId=f70336e0578d04d9c39812db7ced730a">Red Series Dimmer</a>, the <a href="https://www.amazon.com/gp/product/B07T26MVYC/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B07T26MVYC&linkCode=as2&tag=brianhanifi0d-20&linkId=f70336e0578d04d9c39812db7ced730a">Red Series Switch</a>, as well as the <a href="https://www.amazon.com/gp/product/B08665WJ2B/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B08665WJ2B&linkCode=as2&tag=brianhanifi0d-20&linkId=2f2e7797c8b657345aab59d1c90c57ae">Red Series Light & Fan controller</a>. (Note: the OZW integration does not add zwave objects, so I am passing the light object instead.)</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">script.inovelli_led</span>
<span class="na">data</span><span class="pi">:</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">light.family_room</span>
<span class="na">model</span><span class="pi">:</span> <span class="s">dimmer</span>
<span class="na">color</span><span class="pi">:</span> <span class="s">purple</span>
<span class="na">duration</span><span class="pi">:</span> <span class="s">10 seconds</span>
<span class="na">effect</span><span class="pi">:</span> <span class="s">blink</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="advanced-scripting-using-choose--variables">Advanced scripting using <code class="language-yaml highlighter-rouge"><span class="s">choose</span></code> & <code class="language-yaml highlighter-rouge"><span class="s">variables</span></code></h2>
<ul>
<li><a href="https://www.home-assistant.io/blog/2020/07/22/release-113/#automations--scripts-chooser">0.113’s choose feature</a> allowed me to separate the calls to the Z-Wave and OZW services.</li>
<li><a href="https://www.home-assistant.io/blog/2020/09/17/release-115/#variables">0.115’s variables feature</a> allowed me to avoid duplicating the the Inovelli Math calculation for each
service call.</li>
</ul>
<h2 id="variables-part-1"><code class="language-yaml highlighter-rouge"><span class="s">variables</span></code>: Part 1</h2>
<p>This section of variables are defined before the <code class="language-yaml highlighter-rouge"><span class="s">sequence</span></code> section.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
</pre></td><td class="rouge-code"><pre><span class="na">variables</span><span class="pi">:</span>
<span class="na">model</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{% if model is string %}</span>
<span class="s">{{ model }}</span>
<span class="s">{%- elif state_attr(entity_id, 'product_name') is string %}</span>
<span class="s">{%- if 'LZW31' in state_attr(entity_id, 'product_name') %}</span>
<span class="s">dimmer</span>
<span class="s">{%- elif 'LZW36' in state_attr(entity_id, 'product_name') %}</span>
<span class="s">combo_light</span>
<span class="s">{%- else %}</span>
<span class="s">switch</span>
<span class="s">{%- endif %}</span>
<span class="s">{%- else %}</span>
<span class="s">dimmer</span>
<span class="s">{%- endif %}</span>
<span class="na">parameters</span><span class="pi">:</span>
<span class="na">dimmer</span><span class="pi">:</span> <span class="m">16</span>
<span class="na">combo_light</span><span class="pi">:</span> <span class="m">24</span>
<span class="na">combo_fan</span><span class="pi">:</span> <span class="m">25</span>
<span class="na">switch</span><span class="pi">:</span> <span class="m">8</span>
<span class="na">color</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{%- if color is not number %}</span>
<span class="s">{{ color|default("Yellow") }}</span>
<span class="s">{%- else %}</span>
<span class="s">{{ color|int }}</span>
<span class="s">{% endif %}</span>
<span class="c1"># 1-10</span>
<span class="na">level</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">level|default(4)</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">duration</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">duration|default("Indefinitely")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">effect</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">effect|default("Blink")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">colors</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">Off"</span><span class="err">:</span> <span class="m">0</span>
<span class="s2">"</span><span class="s">Red"</span><span class="err">:</span> <span class="m">1</span>
<span class="s2">"</span><span class="s">Orange"</span><span class="err">:</span> <span class="m">21</span>
<span class="s2">"</span><span class="s">Yellow"</span><span class="err">:</span> <span class="m">42</span>
<span class="s2">"</span><span class="s">Green"</span><span class="err">:</span> <span class="m">85</span>
<span class="s2">"</span><span class="s">Cyan"</span><span class="err">:</span> <span class="m">127</span>
<span class="s2">"</span><span class="s">Teal"</span><span class="err">:</span> <span class="m">145</span>
<span class="s2">"</span><span class="s">Blue"</span><span class="err">:</span> <span class="m">170</span>
<span class="s2">"</span><span class="s">Purple"</span><span class="err">:</span> <span class="m">195</span>
<span class="s2">"</span><span class="s">Light</span><span class="nv"> </span><span class="s">Pink"</span><span class="err">:</span> <span class="m">220</span>
<span class="s2">"</span><span class="s">Pink"</span><span class="err">:</span> <span class="m">234</span>
<span class="na">durations</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">Off"</span><span class="err">:</span> <span class="m">0</span>
<span class="s2">"</span><span class="s">1</span><span class="nv"> </span><span class="s">Second"</span><span class="err">:</span> <span class="m">1</span>
<span class="s2">"</span><span class="s">2</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">2</span>
<span class="s2">"</span><span class="s">3</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">3</span>
<span class="s2">"</span><span class="s">4</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">4</span>
<span class="s2">"</span><span class="s">5</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">5</span>
<span class="s2">"</span><span class="s">6</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">6</span>
<span class="s2">"</span><span class="s">7</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">7</span>
<span class="s2">"</span><span class="s">8</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">8</span>
<span class="s2">"</span><span class="s">9</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">9</span>
<span class="s2">"</span><span class="s">10</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">10</span>
<span class="s2">"</span><span class="s">15</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">15</span>
<span class="s2">"</span><span class="s">20</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">20</span>
<span class="s2">"</span><span class="s">25</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">25</span>
<span class="s2">"</span><span class="s">30</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">30</span>
<span class="s2">"</span><span class="s">35</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">35</span>
<span class="s2">"</span><span class="s">40</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">40</span>
<span class="s2">"</span><span class="s">45</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">45</span>
<span class="s2">"</span><span class="s">50</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">50</span>
<span class="s2">"</span><span class="s">55</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">55</span>
<span class="s2">"</span><span class="s">60</span><span class="nv"> </span><span class="s">Seconds"</span><span class="err">:</span> <span class="m">60</span>
<span class="s2">"</span><span class="s">2</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">62</span>
<span class="s2">"</span><span class="s">3</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">63</span>
<span class="s2">"</span><span class="s">4</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">64</span>
<span class="s2">"</span><span class="s">10</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">70</span>
<span class="s2">"</span><span class="s">15</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">75</span>
<span class="s2">"</span><span class="s">30</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">90</span>
<span class="s2">"</span><span class="s">45</span><span class="nv"> </span><span class="s">Minutes"</span><span class="err">:</span> <span class="m">105</span>
<span class="s2">"</span><span class="s">1</span><span class="nv"> </span><span class="s">Hour"</span><span class="err">:</span> <span class="m">120</span>
<span class="s2">"</span><span class="s">2</span><span class="nv"> </span><span class="s">Hours"</span><span class="err">:</span> <span class="m">122</span>
<span class="s2">"</span><span class="s">Indefinitely"</span><span class="err">:</span> <span class="m">255</span>
<span class="na">effects_dimmer</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">Off"</span><span class="err">:</span> <span class="m">0</span>
<span class="s2">"</span><span class="s">Solid"</span><span class="err">:</span> <span class="m">1</span>
<span class="s2">"</span><span class="s">Chase"</span><span class="err">:</span> <span class="m">2</span>
<span class="s2">"</span><span class="s">Fast</span><span class="nv"> </span><span class="s">Blink"</span><span class="err">:</span> <span class="m">3</span>
<span class="s2">"</span><span class="s">Slow</span><span class="nv"> </span><span class="s">Blink"</span><span class="err">:</span> <span class="m">4</span>
<span class="s2">"</span><span class="s">Blink"</span><span class="err">:</span> <span class="m">4</span>
<span class="s2">"</span><span class="s">Pulse"</span><span class="err">:</span> <span class="m">5</span>
<span class="s2">"</span><span class="s">Breath"</span><span class="err">:</span> <span class="m">5</span>
<span class="na">effects_switch</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">Off"</span><span class="err">:</span> <span class="m">0</span>
<span class="s2">"</span><span class="s">Solid"</span><span class="err">:</span> <span class="m">1</span>
<span class="s2">"</span><span class="s">Fast</span><span class="nv"> </span><span class="s">Blink"</span><span class="err">:</span> <span class="m">2</span>
<span class="s2">"</span><span class="s">Slow</span><span class="nv"> </span><span class="s">Blink"</span><span class="err">:</span> <span class="m">3</span>
<span class="s2">"</span><span class="s">Blink"</span><span class="err">:</span> <span class="m">3</span>
<span class="s2">"</span><span class="s">Pulse"</span><span class="err">:</span> <span class="m">4</span>
<span class="s2">"</span><span class="s">Breath"</span><span class="err">:</span> <span class="m">4</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="variables-part-2"><code class="language-yaml highlighter-rouge"><span class="s">variables</span></code>: Part 2</h2>
<p>Variables can also be defined or redefined in the <code class="language-yaml highlighter-rouge"><span class="s">sequence</span></code> section as well. Here I am assigning the final values,
and using those values to preform the Inovelli Math.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
</pre></td><td class="rouge-code"><pre><span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Preform the Inovelli math.</span>
<span class="pi">-</span> <span class="na">variables</span><span class="pi">:</span>
<span class="na">parameter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">parameters[model|default("dimmer")]</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">color</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">colors[color|default("purple")|title]</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">duration</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">durations[duration|default("10</span><span class="nv"> </span><span class="s">Seconds")|title]</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">effect</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{% if model == "switch" %}</span>
<span class="s">{{- effects_switch[effect|default("Blink")|title]|int }}</span>
<span class="s">{%- else %}</span>
<span class="s">{{- effects_dimmer[effect|default("Blink")|title]|int }}</span>
<span class="s">{% endif %}</span>
<span class="na">inovelli_math</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{%- if effect|int > 0 %}</span>
<span class="s">{{ color|int + (level|int * 256) + (duration|int * 65536) + (effect|int * 16777216) }}</span>
<span class="s">{%- else %}</span>
<span class="s">0</span>
<span class="s">{% endif %}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="choose-between-the-z-wave-or-ozw-integration"><code class="language-yaml highlighter-rouge"><span class="s">choose</span></code> between the Z-Wave or OZW Integration</h2>
<p>Finally, the service calls. If the entity_id begins with <code class="language-yaml highlighter-rouge"><span class="s">zwave</span></code> then use the <code class="language-yaml highlighter-rouge"><span class="s">zwave.set_config_parameter</span></code> service.
Otherwise use the <code class="language-yaml highlighter-rouge"><span class="s">ozw.set_config_parameter</span></code> service.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre> <span class="pi">-</span> <span class="na">choose</span><span class="pi">:</span>
<span class="c1"># The Z-wave integration requires this service call.</span>
<span class="pi">-</span> <span class="na">conditions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">entity_id.split(".")[0]</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"zwave"</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Clear the previous effect.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">zwave.set_config_parameter</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">node_id</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">state_attr(entity_id,"node_id")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">parameter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">parameter</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">size</span><span class="pi">:</span> <span class="m">4</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0'</span>
<span class="c1"># Start the new effect.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">zwave.set_config_parameter</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">node_id</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">state_attr(entity_id,"node_id")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">parameter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">parameter</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">size</span><span class="pi">:</span> <span class="m">4</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">inovelli_math</span><span class="nv"> </span><span class="s">}}'</span>
<span class="c1"># The OZW integration requires this service call.</span>
<span class="na">default</span><span class="pi">:</span>
<span class="c1"># Clear the previous effect.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">ozw.set_config_parameter</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">node_id</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">state_attr(entity_id,"node_id")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">parameter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">parameter</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0'</span>
<span class="c1"># Start the new effect.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">ozw.set_config_parameter</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">node_id</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">state_attr(entity_id,"node_id")</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">parameter</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">parameter</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">value</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">inovelli_math</span><span class="nv"> </span><span class="s">}}'</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="fixing-reliability">Fixing reliability</h2>
<p>I found that sending an “off” command to the service ensures the new effect is applied reliably. I did not discover
this issue until I was testing these new changes and I could not figure out why nothing would happen sometimes and
other times it would work just fine. As it turns out a new effect has to be assigned to the switch before it can
show the previous effect again. This must have begun occuring after my switch to the OZW integration. I suppose that
makes sense, OZW is storing the parameters in MQTT. Anyway, this simple change has made new effects reliable every time!</p>
Fri, 18 Sep 2020 15:00:00 -0700
https://brianhanifin.com/posts/inovelli-red-series-status-led-update/
https://brianhanifin.com/posts/inovelli-red-series-status-led-update/IKEA Trådfri Remote: ZHA Double (and Triple) ClickBrian Hanifin<p>I recently picked up an <a href="https://www.ikea.com/us/en/p/tradfri-remote-control-00443130/">IKEA Trådfri Remote</a> to try out.
My computer is in the Living Room and I thought it would be nice to have a multi function remote to manage the fan and
lights around me.</p>
<h2 id="updates">Updates</h2>
<ul>
<li><strong>2020-09-05</strong>
<ul>
<li><a href="#source">Source</a>: Added section to share snapshots of full multiple remote automation and my full script.</li>
</ul>
</li>
<li><strong>2020-09-06</strong>
<ul>
<li><a href="#additional-functionality">Additional functionality</a>: Added section to document the double-click and long press
functionality I added to the top and bottom buttons on the remote.</li>
<li><a href="#source">Source</a>: Updated the link to an updated snapshot of my full multiple remote automation.</li>
</ul>
</li>
</ul>
<h2 id="5-button-remote">5 button remote</h2>
<p>The remote has five buttons to do whatever I want with. Using Home Assistant this become a custom “Scene Controller”.
Initially I setup the large power button in the middle to toggle the fan on and off. The top and bottom “brightness”
buttons turn the primary light group on and off, and the right and left arrow buttons turn the secondary lamp on and off.</p>
<p>My wife asked me if she could adjust the light brightness with the remote as well. That led me to discover that <code class="language-yaml highlighter-rouge"><span class="s">zha_event</span></code>
captures hold events for the 4 light control buttons. A long press on one of those buttons either raises or lowers the
assigned light’s brightness by 20%.</p>
<p>Nice! That brings me up to 9 “scenes” that I can control with this remote.</p>
<h2 id="double-click">Double click?</h2>
<p>Well, I thought it would be nice to be able to toggle the TV on and off. After checking though <code class="language-yaml highlighter-rouge"><span class="s">zha_event</span></code> does not
capture double taps of any of the IKEA remote’s buttons. Darn.</p>
<p>One thought kept nagging at the back of my mind though. Although <code class="language-yaml highlighter-rouge"><span class="s">zha_event</span></code> didn’t provide a unique command for a
“double click” event, I noticed that double the center power button did fire two events in quick succession. I knew there
was probably a way to exploit that.</p>
<h2 id="how-to-store-the-information-needed-for-later-comparison">How to store the information needed for later comparison?</h2>
<p>After a few hours (and a couple of breaks) I figured it out! Storing the previous click’s timestamp to compare it with the
current click’s timestamp. The trick was to find a way to store the data. At first I used the custom_component
<a href="https://github.com/rogro82/hass-variables">hass-variables</a> to store the timestamp of the current click, device_ieee,
command, and the difference in time between the previous click and the current click. While that worked great and gave my
data structure, I wanted to make it easier for everyone to use.</p>
<p>I created a “Helper” entity named <code class="language-yaml highlighter-rouge"><span class="s">input_text.zha_click</span></code>. In it I store a comma separate list of the values needed.
Here is a table with some captured click data:</p>
<table>
<thead>
<tr>
<th style="text-align: center">device_ieee</th>
<th style="text-align: center">command</th>
<th style="text-align: center">previous_click</th>
<th style="text-align: center">click_count</th>
<th style="text-align: center"><em>click_delta</em></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">ec:1b:bd:ff:fe:23:9c:ee</td>
<td style="text-align: center">toggle</td>
<td style="text-align: center">1599275643.719181</td>
<td style="text-align: center">1</td>
<td style="text-align: center"><em>6.6513881683349609</em></td>
</tr>
<tr>
<td style="text-align: center">ec:1b:bd:ff:fe:23:9c:ee</td>
<td style="text-align: center">toggle</td>
<td style="text-align: center">1599276764.579644</td>
<td style="text-align: center">2</td>
<td style="text-align: center"><em>0.2137620449066162</em></td>
</tr>
<tr>
<td style="text-align: center">ec:1b:bd:ff:fe:23:9c:ee</td>
<td style="text-align: center">toggle</td>
<td style="text-align: center">1599276776.025289</td>
<td style="text-align: center">3</td>
<td style="text-align: center"><em>0.16803312301635742</em></td>
</tr>
</tbody>
</table>
<p>The “click_delta” has been remarked out from my current code, but it was very helpful to help me understand the timing
during testing! For example it told me 1 second not only felt too long to wait for my fan to turn on, but it also
felt like too long to wait for a second click. I found half of a second to be a pretty sweet spot between the two.</p>
<h2 id="example-automation">Example Automation</h2>
<p>Below is an example of the automation I use. In this section of the code that handles the large button in the middle.
Theoretically it can have an unlimited number of clicks. I have held it to 3 for now: 1x - Toggle Fan, 2x - Toggle TV,
3x - Toggle Front Door Lock.</p>
<blockquote>
<i>Note:</i> I chose `mode: restart` so a second click within the allotted delay time (0.5 seconds) would cancel the default
single click action.
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
</pre></td><td class="rouge-code"><pre><span class="na">automation</span><span class="pi">:</span>
<span class="na">alias</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZHA:</span><span class="nv"> </span><span class="s">IKEA</span><span class="nv"> </span><span class="s">Remote</span><span class="nv"> </span><span class="s">Click</span><span class="nv"> </span><span class="s">Handler"</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">zha_ikea_remote_click</span>
<span class="na">initial_state</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">restart</span> <span class="c1"># Force restart to pickup double click</span>
<span class="na">trigger</span><span class="pi">:</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">event</span>
<span class="na">event_type</span><span class="pi">:</span> <span class="s">zha_event</span>
<span class="na">event_data</span><span class="pi">:</span>
<span class="na">data.device_ieee</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ec:1b:bd:ff:fe:23:9c:ee"</span>
<span class="na">action</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">choose</span><span class="pi">:</span>
<span class="c1"># Middle Button: "Power"</span>
<span class="pi">-</span> <span class="na">conditions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">condition</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">trigger.event.data.command</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"toggle"</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Store the previous click values and decide if this click was a double click or not.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">script.zha_store_click</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">device</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">trigger.event.data.device_ieee</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">command</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">trigger.event.data.command</span><span class="nv"> </span><span class="s">}}'</span>
<span class="pi">-</span> <span class="na">choose</span><span class="pi">:</span>
<span class="c1"># Double click</span>
<span class="pi">-</span> <span class="na">conditions</span><span class="pi">:</span>
<span class="c1"># Click count: returned by 4th value</span>
<span class="pi">-</span> <span class="na">condition</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{% set click_count = states("input_text.zha_click").split(",")[3]|int %}</span>
<span class="s">{{ click_count == 2 }}</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Delay 0.5 second to allow for a second click to cancel the second action.</span>
<span class="pi">-</span> <span class="na">delay</span><span class="pi">:</span>
<span class="na">seconds</span><span class="pi">:</span> <span class="m">0.5</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">switch.toggle</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">switch.tv_family_room</span>
<span class="c1"># Triple click</span>
<span class="pi">-</span> <span class="na">conditions</span><span class="pi">:</span>
<span class="c1"># Click count: returned by 4th value</span>
<span class="pi">-</span> <span class="na">condition</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">{% set click_count = states("input_text.zha_click").split(",")[3]|int %}</span>
<span class="s">{{ click_count == 3 }}</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Delay 0.5 second to allow for a second click to cancel the third action.</span>
<span class="pi">-</span> <span class="na">delay</span><span class="pi">:</span>
<span class="na">seconds</span><span class="pi">:</span> <span class="m">0.5</span>
<span class="pi">-</span> <span class="na">choose</span><span class="pi">:</span>
<span class="c1"># The door us locked</span>
<span class="pi">-</span> <span class="na">conditions</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">condition</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="s1">'</span><span class="s">{{</span><span class="nv"> </span><span class="s">states("lock.front_door")|lower|trim</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"locked"</span><span class="nv"> </span><span class="s">}}'</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">lock.unlock</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">lock.front_door</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">input_boolean.turn_on</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">input_boolean.leave_unlocked</span>
<span class="c1"># The door is unlocked</span>
<span class="na">default</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">input_boolean.turn_off</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">input_boolean.leave_unlocked</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">lock.lock</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">lock.front_door</span>
<span class="c1"># Single Click</span>
<span class="na">default</span><span class="pi">:</span>
<span class="c1"># Delay 0.5 second to allow for a second click to cancel the first action.</span>
<span class="pi">-</span> <span class="na">delay</span><span class="pi">:</span>
<span class="na">seconds</span><span class="pi">:</span> <span class="m">0.5</span>
<span class="c1"># Toggle the fan.</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">switch.toggle</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">switch.family_room_fan</span>
<span class="c1">##########################################################</span>
<span class="c1"># Omitting handlers for the other 4 buttons for brevity. #</span>
<span class="c1">##########################################################</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="reusable-logic-in-a-script">Reusable logic in a script</h2>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre><span class="c1"># The click data is stored in CSV fomat in the order: device_ieee, command, click, click_count</span>
<span class="c1">#</span>
<span class="c1"># Note: you can extract the data from the input_text list like so.</span>
<span class="c1"># {% set data = states("input_text.zha_click").split(",") %}</span>
<span class="c1"># {% set previous_device = data[0] %}</span>
<span class="c1"># {% set previous_command = data[1] %}</span>
<span class="c1"># {% set previous_click = data[2] %}</span>
<span class="c1"># {% set click_count = data[3] %}</span>
<span class="na">script</span><span class="pi">:</span>
<span class="na">zha_store_click</span><span class="pi">:</span>
<span class="na">mode</span><span class="pi">:</span> <span class="s">queued</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="c1"># Store the previous click values, and whether or not the current click is a double click.</span>
<span class="c1"># * second click must occur within 0.5 second of the first click</span>
<span class="c1"># * must be the same remote</span>
<span class="c1"># * must be sending the same command</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">input_text.set_value</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">input_text.zha_click</span>
<span class="c1"># CSV Order: device_ieee, command, click, click_count</span>
<span class="na">value</span><span class="pi">:</span> <span class="pi">>-</span>
<span class="s">{%- set data = states("input_text.zha_click").split(",") %}</span>
<span class="s">{%- set previous_device = data[0]|trim %}</span>
<span class="s">{%- set previous_command = data[1]|trim %}</span>
<span class="s">{%- set click = as_timestamp(now())|float %}</span>
<span class="s">{%- set previous_click = data[2]|float %}</span>
<span class="s">{%- set click_delta = click - previous_click %}</span>
<span class="s">{%- set click_count = data[3]|int %}</span>
<span class="s">{{- device|trim }},</span>
<span class="s">{{- command|trim }},</span>
<span class="s">{{- click }},</span>
<span class="s">{%- if click_delta <= 0.5 and previous_device == device|trim and previous_command == command %}</span>
<span class="s">{{- click_count + 1 }}</span>
<span class="s">{%- else %}</span>
<span class="s">{{- 1 }}</span>
<span class="s">{%- endif %}</span>
<span class="c1"># ,{{ click_delta }}</span>
<span class="c1"># ^^^ Unremark the last line to show the timing between clicks during debugging.</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="additional-functionality">Additional functionality</h2>
<p>So far I only covered how to capture multi-clicking on the center button. This also works on the other 4 buttons as well!
This means we can use this remote for a practically unlimited number of actions!</p>
<p>So now we can use this remote for:</p>
<ul>
<li>5 single click actions</li>
<li>4 long press actions</li>
<li>5 double click actions</li>
<li>5 triple click actions</li>
<li>etc.</li>
</ul>
<p>For example, I setup my top and bottom buttons to:</p>
<ul>
<li>1 click = turn on/off primary lamp</li>
<li>2 clicks = turn on/off table lamp</li>
<li>Long press up = increase primary and/or table lamp brightness by 20%</li>
<li>Long press down = decrease primary and/or table lamp brightness by 20%</li>
</ul>
<h2 id="source">Source</h2>
<p>You can find full versions of my universal ZHA remote automation and script at the links below. My full automation handles multiple
remotes instead of just the one in the below example.</p>
<ul>
<li>
<p>Snapshot 2020-09-06: <a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/0dced1b953f6b921ec3ddc4e600a7b3345e48f37/automations/buttons/zha_button_click.yaml">automation.zha_button_click</a></p>
</li>
<li>
<p>Snapshot 2020-09-05: <a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/0b8fe58881201655cce7a56e4317d5b221b869b3/scripts/buttons/zha_store_click.yaml">script.zha_store_click</a></p>
</li>
</ul>
<h2 id="discuss">Discuss</h2>
<p>Post any questions <a href="https://community.home-assistant.io/t/ikea-tradfri-remote-zha-double-and-triple-click/224535">on the Home Assistant Community post</a> or below and I will be happy to answer any questions.</p>
Fri, 04 Sep 2020 20:00:00 -0700
https://brianhanifin.com/posts/zigbee-home-automation-home-assistant-ikea-tradfri-double-click/
https://brianhanifin.com/posts/zigbee-home-automation-home-assistant-ikea-tradfri-double-click/Jekyll Local Testing using Docker on UnraidBrian Hanifin<h2 id="this-site-runs-a-software-called-jekyll">This site runs a software called <a href="https://jekyllrb.com/">Jekyll</a></h2>
<p>It requires a copy of the software to be installed on your local computer to preview the article you are writing.
When I started this website I had to install Jekyll on my Windows 10 PC. I didn’t like that this required me to
install things like Ruby, RubyGems, GCC, and Make. As infrequently as I write an article, I would forget the
correct command line to run the webserver. Is it <code class="language-yaml highlighter-rouge"><span class="s">jekyll serve</span></code> or <code class="language-yaml highlighter-rouge"><span class="s">bundler jekyll serve</span></code>… ah forget it I just
won’t post today.</p>
<h2 id="can-jekyll-run-on-docker">Can Jekyll run on Docker?</h2>
<p>Docker sure seems like the ideal candidate to run Jekyll. If I could get Jekyll installed in Docker on my Unraid server,
I could point it to my github folder and have it automatically start the webserver.</p>
<p>After some research I discovered <a href="https://github.com/BretFisher/jekyll-serve">BretFisher’s jekyll-serve project on Github</a>.
I was able to create a Unraid Docker template which accomplishes my goals!</p>
<h2 id="docker-unraid-template">Docker Unraid Template</h2>
<p>I am sharing my docker template configuration below more for my own reference, but I won’t go into any more detail
for now. If you have further questions please ask in the comments at the bottom of this post.</p>
<h3 id="basic-view">Basic view</h3>
<p><img src="/assets/img/2020-09-03-unraid-docker-jekyll/unraid-docker-jekyll-serve.png" alt="Partial Unraid docker template screenshot" /></p>
<h3 id="advanced-view">Advanced view</h3>
<p><img src="/assets/img/2020-09-03-unraid-docker-jekyll/unraid-docker-jekyll-serve-advanced.png" alt="Partial Unraid docker template screenshot" /></p>
<h2 id="xml-user-template">XML user template</h2>
<p>Docker user templates are stored on the Unraid filesystem at <code class="language-yaml highlighter-rouge"><span class="s">/boot/config/plugins/dockerMan/templates-user/</span></code>.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
</pre></td><td class="rouge-code"><pre><span class="cp"><?xml version="1.0"?></span>
<span class="nt"><Container</span> <span class="na">version=</span><span class="s">"2"</span><span class="nt">></span>
<span class="nt"><Name></span>jekyll-serve-brianhanifin.com<span class="nt"></Name></span>
<span class="nt"><Repository></span>bretfisher/jekyll-serve<span class="nt"></Repository></span>
<span class="nt"><Registry></span>https://hub.docker.com/r/bretfisher/jekyll-serve/<span class="nt"></Registry></span>
<span class="nt"><Network></span>bridge<span class="nt"></Network></span>
<span class="nt"><MyIP/></span>
<span class="nt"><Shell></span>sh<span class="nt"></Shell></span>
<span class="nt"><Privileged></span>false<span class="nt"></Privileged></span>
<span class="nt"><Support></span>https://hub.docker.com/r/bretfisher/jekyll-serve/<span class="nt"></Support></span>
<span class="nt"><Project/></span>
<span class="nt"><Overview></span>The Latest Jekyll in a Docker Container For Easy SSG Development. Converted By Community Applications Always verify this template (and values) against the dockerhub support page for the container<span class="nt"></Overview></span>
<span class="nt"><Category/></span>
<span class="nt"><WebUI></span>http://[IP]:[PORT:4000]/<span class="nt"></WebUI></span>
<span class="nt"><TemplateURL/></span>
<span class="nt"><Icon></span>http://brianhanifin.com/assets/img/brian-emoji.png<span class="nt"></Icon></span>
<span class="nt"><ExtraParams/></span>
<span class="nt"><PostArgs/></span>
<span class="nt"><CPUset/></span>
<span class="nt"><DateInstalled></span>1599166188<span class="nt"></DateInstalled></span>
<span class="nt"><DonateText/></span>
<span class="nt"><DonateLink/></span>
<span class="nt"><Description></span>The Latest Jekyll in a Docker Container For Easy SSG Development. Converted By Community Applications Always verify this template (and values) against the dockerhub support page for the container<span class="nt"></Description></span>
<span class="nt"><Networking></span>
<span class="nt"><Mode></span>bridge<span class="nt"></Mode></span>
<span class="nt"><Publish></span>
<span class="nt"><Port></span>
<span class="nt"><HostPort></span>4000<span class="nt"></HostPort></span>
<span class="nt"><ContainerPort></span>4000<span class="nt"></ContainerPort></span>
<span class="nt"><Protocol></span>tcp<span class="nt"></Protocol></span>
<span class="nt"></Port></span>
<span class="nt"></Publish></span>
<span class="nt"></Networking></span>
<span class="nt"><Data></span>
<span class="nt"><Volume></span>
<span class="nt"><HostDir></span>/mnt/user/media/brianhanifin.com/<span class="nt"></HostDir></span>
<span class="nt"><ContainerDir></span>/site<span class="nt"></ContainerDir></span>
<span class="nt"><Mode></span>rw<span class="nt"></Mode></span>
<span class="nt"></Volume></span>
<span class="nt"></Data></span>
<span class="nt"><Environment/></span>
<span class="nt"><Labels/></span>
<span class="nt"><Config</span> <span class="na">Name=</span><span class="s">"Port"</span> <span class="na">Target=</span><span class="s">"4000"</span> <span class="na">Default=</span><span class="s">""</span> <span class="na">Mode=</span><span class="s">"tcp"</span> <span class="na">Description=</span><span class="s">"Container Port: 4000"</span> <span class="na">Type=</span><span class="s">"Port"</span> <span class="na">Display=</span><span class="s">"always"</span> <span class="na">Required=</span><span class="s">"false"</span> <span class="na">Mask=</span><span class="s">"false"</span><span class="nt">></span>4000<span class="nt"></Config></span>
<span class="nt"><Config</span> <span class="na">Name=</span><span class="s">"Config"</span> <span class="na">Target=</span><span class="s">"/site"</span> <span class="na">Default=</span><span class="s">""</span> <span class="na">Mode=</span><span class="s">"rw"</span> <span class="na">Description=</span><span class="s">"Container Path: /site"</span> <span class="na">Type=</span><span class="s">"Path"</span> <span class="na">Display=</span><span class="s">"always"</span> <span class="na">Required=</span><span class="s">"false"</span> <span class="na">Mask=</span><span class="s">"false"</span><span class="nt">></span>/mnt/user/media/brianhanifin.com/<span class="nt"></Config></span>
<span class="nt"></Container></span>
</pre></td></tr></tbody></table></code></pre></div></div>
Thu, 03 Sep 2020 16:00:00 -0700
https://brianhanifin.com/posts/unraid-docker-jekyll/
https://brianhanifin.com/posts/unraid-docker-jekyll/DIY Irrigation Controller: Lovelace User InterfaceBrian Hanifin<p><em>Articles in this series:</em></p>
<ol>
<li><em><a href="../diy-irrigation-controller-esphome-home-assistant/">Hardware, Electronics, and ESPHome code</a></em></li>
<li><em>Lovelace User Interface</em></li>
<li><em><a href="../diy-irrigation-controller-lovelace-ui-update/">Entities & Simplified User Interface</a></em></li>
</ol>
<h2 id="diy-irrigation-controller-part-2">DIY Irrigation Controller Part 2</h2>
<p>While my the Irrigation Controller can be manually started and stopped with physical buttons, the primary user interface was always intended to be built in Lovelace.</p>
<h3 id="reliability-update">Reliability Update</h3>
<p>Before I describe how I created the interface I have great news! In the 2 months since I put the controller into service it has been rock solid! At the moment the Uptime is up to 248 hours since the last time it restarted. The only times it has been restarted has been once to upload a tweak to my ESPHome code, and the second one was for a neighborhood-wide power outage!!! :)</p>
<h3 id="building-a-user-interface">Building A User Interface</h3>
<p>Each Zone consists of the following:</p>
<ol>
<li>Manual on/off switch.</li>
<li>Status Display showing: Is It Running, Next Scheduled Time, and Time Remaining.</li>
<li>Scheduler: field to enter a comma separated list of times, and a slider to select the runtime in minutes.</li>
</ol>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
</pre></td><td class="rouge-code"><pre><span class="na">type</span><span class="pi">:</span> <span class="s">vertical-stack</span>
<span class="na">cards</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">markdown</span>
<span class="na">style</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">ha-card {</span>
<span class="s">background: none;</span>
<span class="s">box-shadow: none;</span>
<span class="s">letter-spacing: 0.06em;</span>
<span class="s">margin: 0 0 -1em 0;</span>
<span class="s">padding: 0;</span>
<span class="s">}</span>
<span class="na">content</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">**🌱 Drip System**</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">entities</span>
<span class="na">show_header_toggle</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:paper-buttons-row</span>
<span class="na">buttons</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">switch.irrigation_zone1</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Start Cycle</span>
<span class="na">state_icons</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">off"</span><span class="err">:</span> <span class="s">mdi:play</span>
<span class="s">"on"</span><span class="err">:</span> <span class="s">mdi:stop</span>
<span class="na">style</span><span class="pi">:</span>
<span class="na">button</span><span class="pi">:</span>
<span class="na">background</span><span class="pi">:</span> <span class="s">lightgray</span>
<span class="na">border-radius</span><span class="pi">:</span> <span class="s">9999px</span>
<span class="na">font-weight</span><span class="pi">:</span> <span class="s">bold</span>
<span class="na">state_styles</span><span class="pi">:</span>
<span class="s2">"</span><span class="s">off"</span><span class="err">:</span>
<span class="na">button</span><span class="pi">:</span>
<span class="na">color</span><span class="pi">:</span> <span class="s">red</span>
<span class="na">ripple</span><span class="pi">:</span>
<span class="na">color</span><span class="pi">:</span> <span class="s">red</span>
<span class="s2">"</span><span class="s">on"</span><span class="err">:</span>
<span class="na">button</span><span class="pi">:</span>
<span class="na">color</span><span class="pi">:</span> <span class="s">green</span>
<span class="na">ripple</span><span class="pi">:</span>
<span class="na">color</span><span class="pi">:</span> <span class="s">green</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:text-divider-row</span>
<span class="na">text</span><span class="pi">:</span> <span class="s">Status</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:multiple-entity-row</span>
<span class="na">entity</span><span class="pi">:</span> <span class="s">binary_sensor.irrigation_zone1</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">💧 Watering</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[[icon]]"</span>
<span class="na">show_state</span><span class="pi">:</span> <span class="no">false</span>
<span class="na">secondary_info</span><span class="pi">:</span>
<span class="na">entity</span><span class="pi">:</span> <span class="s">binary_sensor.irrigation_zone1</span>
<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_zone1_next</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">⏭️ Next</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_zone1_remaining</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">⏳ Remaining</span>
<span class="na">unit</span><span class="pi">:</span> <span class="s">min</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:fold-entity-row</span>
<span class="na">head</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">custom:text-divider-row</span>
<span class="na">text</span><span class="pi">:</span> <span class="s">Scheduler</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:text-input-row</span>
<span class="na">entity</span><span class="pi">:</span> <span class="s">input_text.irrigation_zone1_times</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_zone1_duration</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Duration in Minutes</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:timer-sand</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">custom:slider-entity-row</span>
<span class="na">entity</span><span class="pi">:</span> <span class="s">input_number.irrigation_zone1_duration</span>
<span class="na">full_row</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">hide_state</span><span class="pi">:</span> <span class="no">true</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="controller-status">Controller Status</h3>
<p>At the bottom I include “glance” sensors displaying the status of the controller.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre> <span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">glance</span>
<span class="na">entities</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">binary_sensor.irrigation_controller_status</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Status</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_controller_uptime</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Uptime</span>
<span class="pi">-</span> <span class="na">entity</span><span class="pi">:</span> <span class="s">sensor.irrigation_controller_wifi_signal</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">WiFi Signal</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Let me know if you have any questions.</p>
<div style="margin-top:3em;">
<style type="text/css">
div#amzn-native-ad-0 {
left: 0 !important;
}
</style>
<script type="text/javascript">
amzn_assoc_placement = "adunit0";
amzn_assoc_search_bar = "true";
amzn_assoc_tracking_id = "brianhanifi0d-20";
amzn_assoc_ad_mode = "manual";
amzn_assoc_ad_type = "smart";
amzn_assoc_marketplace = "amazon";
amzn_assoc_region = "US";
amzn_assoc_title = "Components mentioned in this article";
amzn_assoc_linkid = "37a4f3f0677f4d551439a4f5d4e1c92b";
amzn_assoc_asins = "B0793NYYPZ,B0007N5LJK,B000VYGMF2,B001H1NGOI";
</script>
<script src="//z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US"></script>
</div>
<hr />
Tue, 28 Apr 2020 13:00:00 -0700
https://brianhanifin.com/posts/diy-irrigation-controller-lovelace-user-interface-home-assistant/
https://brianhanifin.com/posts/diy-irrigation-controller-lovelace-user-interface-home-assistant/DIY Irrigation ControllerBrian Hanifin<p><em>Articles in this series:</em></p>
<ol>
<li><em>Hardware, Electronics, and ESPHome code</em></li>
<li><em><a href="../diy-irrigation-controller-lovelace-user-interface-home-assistant/">Lovelace User Interface</a></em></li>
<li><em><a href="../diy-irrigation-controller-lovelace-ui-update/">Entities & Simplified User Interface</a></em></li>
</ol>
<div style="float:right;"><img src="/assets/img/2020-02-13-irrigation-controller/06t.jpg" /></div>
<p>This project took a long time to put together. There were a lot more frustrations than I expected. At times I had concerns about being able to pull it off. But I am prouder of this accomplishment, than anything else I have done with Home Assistant!!!</p>
<h2 id="impetus">Impetus</h2>
<h3 id="my-first-attempt-at-a-solution">My first attempt at a solution</h3>
<p>One of my earlier Home Automation purchases was a simple $100 WiFi irrigation controller which hooked onto a hose faucet and the irrigation schedule could be controlled via the cloud. The controller lives on the far side of the house and requires navigating a minefield… left by our dogs. So I was excited about the prospect of not having to walk over there as often to adjust the schedule.</p>
<h3 id="are-my-plants-getting-watered">“Are my plants getting watered?”</h3>
<p>Unfortunately this product suffered from a number of problems, most of which are not the fault of the designer, but were in fact my fault for choosing the easy way out. The primary problems I had were: the batteries only lasted a few months and were difficult the change, and the WiFi was not as reliable as I expected (even after installing a UniFi Wireless Access Point about 10 feet away in a closet nearby). For a long time the only way I knew the plants were not getting water was when my wife would ask “Are my plants getting watered?”</p>
<h3 id="home-assistant-to-the-rescue">Home Assistant to the rescue?</h3>
<p>In 2018 I started playing with Home Assistant and I added a warning to the Home Assistant UI when the irrigation controller was offline. Unfortunately, the darn thing bounced between being online and offline so often I ignored it. I would rationalize that its almost always rights itself, so there is no need to do anything about it this time… right? By the time it occurred to me that I could activate a notification only when it had been offline for 2 hours straight, I was already sick and tired of fighting with this piece of technology.</p>
<h2 id="inspiration">Inspiration</h2>
<h3 id="esphome">ESPHome</h3>
<p>Last year I started exploring writing code to control my own devices with <a href="https://esphome.io">ESPHome</a>. After living with a bunch of Tuya, Sonoff, Shelly devices, and an ESP32 board, I realized how reliable these devices were. It occurs to me that I could probably create a more reliable Irrigation Controller.</p>
<p>After seeing <a href="https://community.home-assistant.io/t/my-garden-irrigation/99686">several</a> <a href="https://github.com/bruxy70/Irrigation-with-display">other people</a> create Irrigation Controllers out of a <a href="https://amzn.to/3etA8Kp">Sonoff 4CH Pro R2</a>, I decided instead of spending $200-300 on a fancy Irrigation Controller that I had no control over, I could create my own… for around $50 (including <a href="https://amzn.to/37q81VN">this 24vac transformer</a> to power the solenoids)!</p>
<hr />
<h2 id="diy-irrigation-controller">DIY Irrigation Controller</h2>
<h3 id="ultimate-goals">Ultimate Goals</h3>
<ol>
<li>Create a reliable Irrigation Controller.</li>
<li>Make it self sufficient:</li>
</ol>
<ul>
<li>Does not require Home Assistant to start a schedule session.</li>
<li>Use Home Assistant only to edit the schedule, manually start a cycle, and to monitor progress.</li>
</ul>
<h3 id="irrigation-controller-supplies">Irrigation Controller Supplies</h3>
<ul>
<li><a href="https://amzn.to/3etA8Kp">Sonoff 4CH Pro R2</a> (<- the new R3 should work)</li>
<li><a href="https://amzn.to/37q81VN">Elk TRG2440 24VAC, 40 VA AC Transformer</a></li>
<li><a href="https://amzn.to/38nwIDC">Irrigation Controller Outdoor Enclosure</a> (Optional: if you already have a safe place for your controller).</li>
<li>Electrical extension cord: I ended up cutting a spare extension cord I had, I stripped the ends of the wires to power the Sonoff.</li>
</ul>
<h2 id="what-was-needed-regardless-of-the-controller-used">What was needed regardless of the controller used</h2>
<p>These items would have been needed if I had purchased a $200-$300 off the shelf controller. Which is why I didn’t include these in the cost estimate for my $50 DIY Irrigation Controller project.</p>
<h3 id="irrigation-system">Irrigation System</h3>
<p>Note: I already existing Drip System pipes running to this location, so I only need to replace the control solenoid portion.</p>
<ul>
<li><a href="https://amzn.to/2UOBTs1">Orbit 3-Valve Heavy Duty Preassembled Manifold</a></li>
<li>Outdoor wiring to connect the solenoids to the Sonoff.</li>
<li>Water resistant wire nuts to connect the wires to the solenoid wires (included in the manifold kit from above).</li>
</ul>
<h2 id="replacing-the-old-controller">Replacing the old controller</h2>
<div style="float:right;"><img src="/assets/img/2020-02-13-irrigation-controller/01t.jpg" /></div>
<h3 id="providing-power">Providing Power</h3>
<p>Unfortunately I did not have access to an outlet anywhere near where I needed to install the Irrigation Controller. So my first task was to figure out where I could tap into power to get an outlet for the controller. I won’t go into more details on this step, you should probably hire an electrician.</p>
<h3 id="connecting-the-water">Connecting the water</h3>
<p>This was the single most troublesome step. To sum it up: mistakes were made, it took me 3 attempts (with a 2 month delay in between attempt #2 and attempt #3), but is now leak free!</p>
<hr />
<h2 id="programming-the-controller">Programming the controller</h2>
<p>The basic operation – turning relays on and off – isn’t much different than turning a floor fan on and off with an ESPHome controlled smart plug. However this controller has to be able to manage up to four relays at once not just one.</p>
<h3 id="countdown-timer">Countdown timer</h3>
<p>Rather than using a simple <a href="https://esphome.io/guides/automations.html#delay-action"><code class="language-yaml highlighter-rouge"><span class="s">delay action</span></code></a>, I used <a href="https://github.com/bruxy70/Irrigation-with-display">@broxy70’s countdown timer code</a>, so I could display the remaining time in Home Assistant. The following is a snippet of code that tracks the Zone1’s time remaining and exposes the current value as a sensor. This code will also turn off the relay when the countdown reaches zero.</p>
<blockquote>
<p>Note: <code class="language-yaml highlighter-rouge"><span class="s">lambda</span></code> is raw Arduino/C++ code.</p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
</pre></td><td class="rouge-code"><pre><span class="na">globals</span><span class="pi">:</span>
<span class="c1"># Irrigation time remaining</span>
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">remaining_time1</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">int</span>
<span class="na">restore_value</span><span class="pi">:</span> <span class="s">no</span>
<span class="na">initial_value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">300"</span>
<span class="c1"># Store previous values to verify change.</span>
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">remaining_time1_previous</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">int</span>
<span class="na">restore_value</span><span class="pi">:</span> <span class="s">no</span>
<span class="na">initial_value</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
<span class="na">sensor</span><span class="pi">:</span>
<span class="c1"># Countdown sensors.</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Irrigation Zone1 Remaining</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_remaining</span>
<span class="na">lambda</span><span class="pi">:</span> <span class="s2">"</span><span class="s">return</span><span class="nv"> </span><span class="s">0;"</span>
<span class="na">accuracy_decimals</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">unit_of_measurement</span><span class="pi">:</span> <span class="s">minutes</span>
<span class="na">icon</span><span class="pi">:</span> <span class="s">mdi:timer</span>
<span class="na">on_value</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">if</span><span class="pi">:</span>
<span class="na">condition</span><span class="pi">:</span>
<span class="na">lambda</span><span class="pi">:</span> <span class="s">return id(remaining_time1) == 0;</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">switch.turn_off</span><span class="pi">:</span> <span class="s">relay1</span>
<span class="na">switch</span><span class="pi">:</span>
<span class="c1"># Relays which trigger solenoids</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">gpio</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">relay1</span>
<span class="na">pin</span><span class="pi">:</span> <span class="s">$relay1_gpio</span>
<span class="na">on_turn_on</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="c1"># Start the countdown timer.</span>
<span class="pi">-</span> <span class="na">globals.set</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">remaining_time1</span>
<span class="na">value</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="s">return id(irrigation_zone1_duration).state * 60;</span>
<span class="c1"># Show the remaining time.</span>
<span class="pi">-</span> <span class="na">sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_remaining</span>
<span class="na">state</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="s">return id(irrigation_zone1_duration).state;</span>
<span class="c1"># Show the "Next Time" as "now".</span>
<span class="pi">-</span> <span class="na">text_sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_next</span>
<span class="na">state</span><span class="pi">:</span> <span class="s2">"</span><span class="s">now"</span>
<span class="na">on_turn_off</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_remaining</span>
<span class="na">state</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0"</span>
<span class="c1"># Update the next scheduled run time.</span>
<span class="pi">-</span> <span class="na">text_sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_next</span>
<span class="na">state</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="pi">|-</span>
<span class="s">return update_next_runtime(id(irrigation_zone1_times).state);</span>
<span class="c1"># Update the countdown timers every 5 seconds.</span>
<span class="na">interval</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">interval</span><span class="pi">:</span> <span class="s">5s</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">lambda</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">if (id(remaining_time1) > 0) {</span>
<span class="s">// Store the previous time.</span>
<span class="s">id(remaining_time1_previous) = id(remaining_time1);</span>
<span class="s">// When the relay is on.</span>
<span class="s">if (id(relay1).state) {</span>
<span class="s">// Decrement the timer.</span>
<span class="s">id(remaining_time1) -= 5;</span>
<span class="s">// Turn off the relay when the time reaches zero.</span>
<span class="s">if (id(remaining_time1) <= 0) {</span>
<span class="s">id(relay1).turn_off();</span>
<span class="s">id(remaining_time1) = 0;</span>
<span class="s">}</span>
<span class="s">}</span>
<span class="s">// Update the remaining time display.</span>
<span class="s">if (id(remaining_time1_previous) != id(remaining_time1)) {</span>
<span class="s">id(irrigation_zone1_remaining).publish_state( (id(remaining_time1)/60) + 1 );</span>
<span class="s">}</span>
<span class="s">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<div style="float:right;"><img src="/assets/img/2020-02-13-irrigation-controller/ui1t.png" /></div>
<h3 id="home-assistant-user-interface">Home Assistant User Interface</h3>
<p>One of my goals is to “Use Home Assistant only to edit the schedule, manually start a cycle, and to monitor progress.” If you’ve used ESPHome to control a relay in a smart plug before, you know how expose the relay as a switch in Home Assistant. The code above demonstrates how to keep track of a countdown timer, and expose the value as a sensor to Home Assistant. As you can see in the screenshot, each zone has a manual Start/Stop Cycle button, a “⏳ Remaining” sensor, and a way to edit the schedule.</p>
<h3 id="storing-the-schedule">Storing the schedule</h3>
<p>It was relatively easy to design the User Interface. But now I have to store the values on the controller. I decided to use a comma separated list of start times. It seemed like the most straight forward way to store a varying number of start times per zone. The duration is set by a slider that ranges from 0 to 60 minutes. The following is a snippet of code that retrieves Zone1’s schedule from Home Assistant and stores them as separate internal sensors.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre><span class="na">sensor</span><span class="pi">:</span>
<span class="c1"># Retrieve durations settings from the Home Assistant UI.</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">homeassistant</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">ui_zone1_duration</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">input_number.irrigation_zone1_duration</span>
<span class="na">on_value</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_duration</span>
<span class="na">state</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="s">return id(ui_zone1_duration).state;</span>
<span class="c1"># Store durations.</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Irrigation Zone1 Duration</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_duration</span>
<span class="na">text_sensor</span><span class="pi">:</span>
<span class="c1"># Retrieve list of times from the Home Assistant UI.</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">homeassistant</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">ui_zone1_times</span>
<span class="na">entity_id</span><span class="pi">:</span> <span class="s">input_text.irrigation_zone1_times</span>
<span class="na">on_value</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">text_sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_times</span>
<span class="na">state</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="s">return id(ui_zone1_times).state;</span>
<span class="c1"># Store time lists.</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Irrigation Zone1 Times</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_times</span>
<span class="na">on_value</span><span class="pi">:</span>
<span class="na">then</span><span class="pi">:</span>
<span class="c1"># Update the next scheduled run time.</span>
<span class="pi">-</span> <span class="na">text_sensor.template.publish</span><span class="pi">:</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">irrigation_zone1_next</span>
<span class="na">state</span><span class="pi">:</span> <span class="kt">!lambda</span> <span class="pi">|-</span>
<span class="s">return update_next_runtime(id(irrigation_zone1_times).state);</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="running-the-schedule">Running the schedule</h3>
<p>This required me to ask questions and learn modern C++ programming. I have not done much with any C programming language since I took a C class in community college in the mid ’90s. The portion of the code which triggers the schedule check is somewhat straight forward.</p>
<blockquote>
<p>Note: this part of the code syncronizes the Irrigation Controller’s clock with my Home Assistant server’s clock. This could where the goal of running without Home Assistant could fail. I may change to <a href="https://esphome.io/components/time.html"><code class="language-yaml highlighter-rouge"><span class="na">platform</span><span class="pi">:</span> <span class="s">sntp</span></code></a> later.</p>
</blockquote>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c1"># Time based automations.</span>
<span class="na">time</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">platform</span><span class="pi">:</span> <span class="s">homeassistant</span>
<span class="na">id</span><span class="pi">:</span> <span class="s">homeassistant_time</span>
<span class="na">on_time</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">seconds</span><span class="pi">:</span> <span class="m">0</span>
<span class="na">minutes</span><span class="pi">:</span> <span class="s">/1</span>
<span class="na">then</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">lambda</span><span class="pi">:</span> <span class="pi">|-</span>
<span class="s">if (scheduled_runtime(id(irrigation_zone1_next).state.c_str())) {</span>
<span class="s">id(irrigation_zone1).turn_on();</span>
<span class="s">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="irrigationh-c-custom-library">irrigation.h: C++ custom library</h3>
<p>The following code contains two functions: <code class="language-yaml highlighter-rouge"><span class="s">bool scheduled_runtime(string);</span></code> and <code class="language-yaml highlighter-rouge"><span class="s">string update_next_runtime(string);</span></code>. The above code calls <code class="language-yaml highlighter-rouge"><span class="s">scheduled_runtime()</span></code> once every minute. When a relay is turned off, <code class="language-yaml highlighter-rouge"><span class="s">update_next_runtime()</span></code> updates the next runtime sensor.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="cp">#include</span> <span class="cpf">"esphome.h"</span><span class="cp">
</span><span class="n">using</span> <span class="n">namespace</span> <span class="n">std</span><span class="p">;</span>
<span class="c1">// Declare functions before calling them.</span>
<span class="n">bool</span> <span class="nf">scheduled_runtime</span><span class="p">(</span><span class="n">string</span><span class="p">);</span>
<span class="n">string</span> <span class="nf">update_next_runtime</span><span class="p">(</span><span class="n">string</span><span class="p">);</span>
<span class="n">bool</span> <span class="nf">scheduled_runtime</span><span class="p">(</span><span class="n">string</span> <span class="n">time</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Retrieve the current time.</span>
<span class="k">auto</span> <span class="n">time_now</span> <span class="o">=</span> <span class="n">id</span><span class="p">(</span><span class="n">homeassistant_time</span><span class="p">).</span><span class="n">now</span><span class="p">();</span>
<span class="kt">int</span> <span class="n">time_hour</span> <span class="o">=</span> <span class="n">time_now</span><span class="p">.</span><span class="n">hour</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">time_minute</span> <span class="o">=</span> <span class="n">time_now</span><span class="p">.</span><span class="n">minute</span><span class="p">;</span>
<span class="c1">// Split the hour and minutes.</span>
<span class="kt">int</span> <span class="n">next_hour</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">).</span><span class="n">c_str</span><span class="p">());</span>
<span class="kt">int</span> <span class="n">next_minute</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">).</span><span class="n">c_str</span><span class="p">());</span>
<span class="c1">//ESP_LOGD("scheduled_runtime()", "now: %i:%i", next_hour, next_minute);</span>
<span class="k">return</span> <span class="p">(</span><span class="n">time_hour</span> <span class="o">==</span> <span class="n">next_hour</span> <span class="o">&&</span> <span class="n">time_minute</span> <span class="o">==</span> <span class="n">next_minute</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">string</span> <span class="nf">update_next_runtime</span><span class="p">(</span><span class="n">string</span> <span class="n">time_list</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Initialize variables.</span>
<span class="n">vector</span><span class="o"><</span><span class="n">string</span><span class="o">></span> <span class="n">times</span><span class="p">;</span>
<span class="n">vector</span><span class="o"><</span><span class="n">string</span><span class="o">></span> <span class="n">next_time</span><span class="p">;</span>
<span class="kt">char</span> <span class="o">*</span> <span class="n">token</span><span class="p">;</span>
<span class="c1">// Split the list of run times into an array.</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">strtok</span><span class="p">(</span><span class="o">&</span><span class="n">time_list</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">","</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span><span class="n">token</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">times</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">token</span><span class="p">);</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">strtok</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="s">","</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Stop now if the list does not contain more than one time.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">times</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o"><=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">time_list</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Retrieve the current time.</span>
<span class="k">auto</span> <span class="n">time_now</span> <span class="o">=</span> <span class="n">id</span><span class="p">(</span><span class="n">homeassistant_time</span><span class="p">).</span><span class="n">now</span><span class="p">();</span>
<span class="kt">int</span> <span class="n">time_hour</span> <span class="o">=</span> <span class="n">time_now</span><span class="p">.</span><span class="n">hour</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">time_minute</span> <span class="o">=</span> <span class="n">time_now</span><span class="p">.</span><span class="n">minute</span><span class="p">;</span>
<span class="c1">// Initialize variables.</span>
<span class="kt">int</span> <span class="n">next_hour</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">next_minute</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">loop_count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">time_count</span> <span class="o">=</span> <span class="n">times</span><span class="p">.</span><span class="n">size</span><span class="p">()</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="c1">// Compare the list of times with the current time, and return the next in the list.</span>
<span class="c1">//ESP_LOGD("update_next_runtime", "now: %i:%i", hour, minute);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">string</span> <span class="n">time</span> <span class="o">:</span> <span class="n">times</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Retrieve the next scheduled time from the list.</span>
<span class="n">next_hour</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">).</span><span class="n">c_str</span><span class="p">());</span>
<span class="n">next_minute</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">).</span><span class="n">c_str</span><span class="p">());</span>
<span class="c1">//ESP_LOGD("update_next_runtime", "next_hour: %s", time.c_str());</span>
<span class="k">if</span> <span class="p">(</span><span class="n">time_hour</span> <span class="o"><</span> <span class="n">next_hour</span> <span class="o">||</span> <span class="p">(</span><span class="n">time_hour</span> <span class="o">==</span> <span class="n">next_hour</span> <span class="o">&&</span> <span class="n">time_minute</span> <span class="o"><</span> <span class="n">next_minute</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// Return this time if the next hour is greater than the current hour.</span>
<span class="k">return</span> <span class="n">times</span><span class="p">[</span><span class="n">loop_count</span><span class="p">].</span><span class="n">c_str</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="c1">// When we reach the end of our schedule for the day, return the first time of tomorrow.</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">time_count</span> <span class="o">==</span> <span class="n">loop_count</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">times</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">c_str</span><span class="p">();</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Increment the loop counter and array index.</span>
<span class="n">loop_count</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">index</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="s">"unknown"</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>A couple of years ago I would not have believed that I could build my own Irrigation Controller that was more reliable than a $100 off the shelf unit. My Irrigation Controller has proved me wrong with an uptime of 145 hours so far! I have learned a lot from this project, and am very pleased with the result! My wife’s plants are happy to be watered every day and my pool pump is happy to no longer be sucking in air because the water got too low.</p>
<h3 id="source-code">Source Code</h3>
<p>The ESPHome source code to my Irrigation Project, and all of my other ESPHome projects can be found <a href="https://github.com/brianhanifin/esphome-config">in this GitHub Repository</a>. Look specifically at <code class="language-yaml highlighter-rouge"><span class="s">irrigation.yaml</span></code> and <code class="language-yaml highlighter-rouge"><span class="s">irrigation.h</span></code>.</p>
<p>The source code to my Home Assistant Configuration can be found <a href="https://github.com/brianhanifin/Home-Assistant-Config">in this GitHub Repository</a>.</p>
<h2 id="home-assistant-community-thanks">Home Assistant Community Thanks</h2>
<p>Thank you to Home Assistant Community members: @jlax47, @nickrout, @glmnet, and @risk. Your assistance was crucial to my success with this project!</p>
<div style="margin-top:3em;">
<style type="text/css">
div#amzn-native-ad-0 {
left: 0 !important;
}
</style>
<script type="text/javascript">
amzn_assoc_placement = "adunit0";
amzn_assoc_search_bar = "true";
amzn_assoc_tracking_id = "brianhanifi0d-20";
amzn_assoc_ad_mode = "manual";
amzn_assoc_ad_type = "smart";
amzn_assoc_marketplace = "amazon";
amzn_assoc_region = "US";
amzn_assoc_title = "Components mentioned in this article";
amzn_assoc_linkid = "37a4f3f0677f4d551439a4f5d4e1c92b";
amzn_assoc_asins = "B0793NYYPZ,B0007N5LJK,B000VYGMF2,B001H1NGOI";
</script>
<script src="//z-na.amazon-adsystem.com/widgets/onejs?MarketPlace=US"></script>
</div>
<hr />
Thu, 13 Feb 2020 11:30:00 -0800
https://brianhanifin.com/posts/diy-irrigation-controller-esphome-home-assistant/
https://brianhanifin.com/posts/diy-irrigation-controller-esphome-home-assistant/Inovelli Z-Wave Dimmer Status LED in Home AssistantBrian Hanifin<div style="float: right"><img src="//ir-na.amazon-adsystem.com/e/ir?t=brianhanifi0d-20&l=am2&o=1&a=B07S1BMMGH" /><img src="//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&MarketPlace=US&ASIN=B07S1BMMGH&ServiceVersion=20070822&ID=AsinImage&WS=1&Format=_SL160_&tag=brianhanifi0d-20" /></div>
<p>Recently I ordered <a href="https://www.amazon.com/gp/product/B07S1BMMGH/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B07S1BMMGH&linkCode=as2&tag=brianhanifi0d-20&linkId=e9eab63e3bd22d91d96bc880228508a1">Inovelli (Red Series) Z-Wave Dimmer</a> from Amazon. I primarily got it because for the ability to take actions based on up to 5 taps up (and down). I set it up to toggle one lamp with one tap, and toggle all three lamps with a double tap.</p>
<h2 id="updates">Updates</h2>
<ul>
<li><strong>2020-09-19</strong>
<ul>
<li><a href="/posts/inovelli-red-series-status-led-update/">Inovelli Red Series Status LED Script Update</a>: A followup article which explores how this script has been completely
rewritten to support both the Z-wave and OZW integrations. This rewrite also includes support for the Red Series Fan &
Light Controller.</li>
</ul>
</li>
</ul>
<p>This dimmer has a really slick led strip embedded. As it turns they can preform animations and change colors. I am experimenting with using it as a secondary status notifier. So far I’m using Cyan to indicate an unlocked front door and Purple to indicate an open Garage Door.</p>
<p>Unfortunately the LED feature requires some mathematical gymnastics to pull off. Luckily I found <a href="https://community.inovelli.com/t/home-assistant-2nd-gen-switch-rgb-working/168/62">a discussion on the Inovelli forum</a> which provided me with two amazing resources. 1.) @nathanfiscus’s amazing <a href="https://nathanfiscus.github.io/inovelli-notification-calc/">Inovelli Toolbox</a> and 2.) Inovelli’s <a href="https://docs.google.com/spreadsheets/u/1/d/1SGJrJHCUtz8AzznWL_mLCTJjjr2U0IpltcUkRr7N_6M/edit?usp=sharing">Google Spreadsheet</a>. I ended up taking the calculations from the spreadsheet and converting them into Jinja code to do the calculations.</p>
<p>Anyway, here is the script I created to control the Status LED.</p>
<h2 id="tip">Tip</h2>
<p>If you get an error with the <code class="language-yaml highlighter-rouge"><span class="s">entity_id</span></code> @flyingsubs suggests you may need to shorten the name of your <code class="language-yaml highlighter-rouge"><span class="s">zwave.{really_long_light_switch_name_and_model_blah_blah_blah}</span></code> entity. Apparently too long of an entity_name can cause a problem.</p>
<h2 id="scripts">Scripts</h2>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/9dcac14f7b05d7ccb62383626eac5a21.js"> </script>
<h2 id="automations">Automations</h2>
<noscript><pre>400: Invalid request</pre></noscript>
<script src="https://gist.github.com/7464297a1e7a96839cc439695968bcca.js"> </script>
Tue, 21 Jan 2020 18:45:00 -0800
https://brianhanifin.com/posts/inovelli-dimmer-status-led-home-assistant/
https://brianhanifin.com/posts/inovelli-dimmer-status-led-home-assistant/Home Assistant Template Macros: Date and TimeBrian Hanifin<p>Over time I have created a large library of date and time manipulation code which are used in my
automations and scripts. I plan to update this post with the snippets as I add to my library.</p>
<h2 id="date">Date</h2>
<h3 id="standard-examples">Standard Examples</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span> <span class="nv">set date = as_timestamp(now())|timestamp_custom("%A %B %-d</span><span class="pi">,</span> <span class="err">%</span><span class="nv">Y") %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set datetime = as_timestamp(now()) | timestamp_custom("%I</span><span class="pi">:</span><span class="err">%</span><span class="nv">M</span><span class="pi">:</span><span class="err">%</span><span class="nv">S %p %b/%d/%Y"</span><span class="pi">,</span> <span class="nv">true) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set now_string = now().strftime("%Y-%m-%d") %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set month_name = now().strftime("%B") %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set day_name = now().strftime("%A")|lower %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set this_month = now().month %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set this_day = now().day %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set this_year = now().year %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="timedelta">timedelta()</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1"># Calculate fifteen days ago.</span>
<span class="pi">{{</span> <span class="nv">( now() - timedelta(days=15) ).date()</span> <span class="pi">}}</span>
<span class="c1"># Calculate one week from today.</span>
<span class="pi">{{</span> <span class="nv">( now() + timedelta(weeks=1) ).date()</span> <span class="pi">}}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="dayofweek_numberdayofweek">dayofweek_number(dayofweek)</h3>
<p>ex. Get the number which represents Thursday.
<code class="language-yaml highlighter-rouge"><span class="pi">{{</span> <span class="nv">dayofweek_number("Thursday")</span> <span class="pi">}}</span></code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- macro dayofweek_number(dayofweek) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if dayofweek == "Sunday" or dayofweek == "Sun" -%</span><span class="pi">}</span>
<span class="m">0</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Monday" or dayofweek == "Mon" -%</span><span class="pi">}</span>
<span class="m">1</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Tuesday" or dayofweek == "Tue" -%</span><span class="pi">}</span>
<span class="m">2</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Wednesday" or dayofweek == "Wed" -%</span><span class="pi">}</span>
<span class="m">3</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Thursday" or dayofweek == "Thu" -%</span><span class="pi">}</span>
<span class="m">4</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Friday" or dayofweek == "Fri" -%</span><span class="pi">}</span>
<span class="m">5</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif dayofweek == "Saturday" or dayofweek == "Sat" -%</span><span class="pi">}</span>
<span class="m">6</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endmacro -%</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="last_dayofmonthmonth-year">last_dayofmonth(month, year)</h3>
<p>ex. Get the last day of February 2020.
<code class="language-yaml highlighter-rouge"><span class="pi">{{</span> <span class="nv">last_dayofmonth(2</span><span class="pi">,</span> <span class="nv">2020)</span> <span class="pi">}}</span></code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- macro last_dayofmonth(month</span><span class="pi">,</span> <span class="nv">year) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set daysinmonths =</span> <span class="pi">[</span><span class="nv">31</span><span class="pi">,</span><span class="nv">28</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">]</span> <span class="nv">-%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set month = month|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set year = year|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Simplified leap year calculation. See https://www.mathsisfun.com/leap-years.html #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set isleapyear = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set monthindex = month-1 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if month == 2 and isleapyear -%</span><span class="pi">}</span>
<span class="pi">{{</span> <span class="nv">daysinmonths</span><span class="pi">[</span><span class="nv">monthindex</span><span class="pi">]</span><span class="nv">+1</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- else -%</span><span class="pi">}</span>
<span class="pi">{{</span> <span class="nv">daysinmonths</span><span class="pi">[</span><span class="nv">monthindex</span><span class="pi">]</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endmacro -%</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="nth_dayofmonthnth-dayofweek-month-year">nth_dayofmonth(nth, dayofweek, month, year)</h3>
<p>ex. Get the nth Monday of May.<br />
1st: <code class="language-yaml highlighter-rouge"><span class="pi">{{</span> <span class="nv">nth_dayofmonth(1</span><span class="pi">,</span> <span class="s2">"</span><span class="s">Monday"</span><span class="pi">,</span> <span class="nv">5)</span> <span class="pi">}}</span></code><br />
2nd: <code class="language-yaml highlighter-rouge"><span class="pi">{{</span> <span class="nv">nth_dayofmonth(2</span><span class="pi">,</span> <span class="s2">"</span><span class="s">Monday"</span><span class="pi">,</span> <span class="nv">5</span><span class="pi">,</span> <span class="nv">2020)</span> <span class="pi">}}</span></code><br />
Last: <code class="language-yaml highlighter-rouge"><span class="pi">{{</span> <span class="nv">nth_dayofmonth("last"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">Monday"</span><span class="pi">,</span> <span class="nv">5)</span> <span class="pi">}}</span></code></p>
<p>Reference: <a href="https://www.bennadel.com/blog/1446-getting-the-nth-occurrence-of-a-day-of-the-week-for-a-given-month.htm">bennadel.com</a></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- macro nth_dayofmonth(nth</span><span class="pi">,</span> <span class="nv">dayofweek</span><span class="pi">,</span> <span class="nv">month</span><span class="pi">,</span> <span class="nv">year=now().year) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set dayofweek = dayofweek_number(dayofweek)|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set firstdateofmonth = strptime(year ~"-"~ month ~"-1"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">%Y-%m-%d"</span><span class="nv">) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set firstdayofmonth = dayofweek_number(firstdateofmonth.strftime("%A"))|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Determine the first occurrence of the day. #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if firstdayofmonth == 1 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set firstoccurrence = dayofweek -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- elif firstdayofmonth < dayofweek -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set firstoccurrence = (dayofweek - dayofweek_number(firstdayofmonth)) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- else -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set firstoccurrence = (7 - firstdayofmonth + dayofweek) + 1 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if nth is number -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Determine the nth occurrence of the dayofweek. #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set nthoccurrence = firstoccurrence + 7 * (nth-1) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- else -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1">#</span>
<span class="nv">Determine the LAST occurrence of the dayofweek.</span>
<span class="nv">Reference</span><span class="pi">:</span> <span class="nv">https</span><span class="pi">:</span><span class="nv">//cflib.org/udf/GetLastOccOfDayInMonth</span>
<span class="c1">#}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set lastdayofmonth = last_dayofmonth(month</span><span class="pi">,</span> <span class="nv">year)|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set lastdayname = strptime(year ~"-"~ month ~"-"~ lastdayofmonth</span><span class="pi">,</span> <span class="s2">"</span><span class="s">%Y-%m-%d"</span><span class="nv">).strftime("%A") -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set lastdaynumber = dayofweek_number(lastdayname)|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set daydifference = lastdaynumber - dayofweek -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Add a week if the result is negative. #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if daydifference < 0 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set daydifference = daydifference + 7 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set nthoccurrence = lastdayofmonth - daydifference -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Return the day with the month and year so it can be useful. #}</span>
<span class="pi">{{</span> <span class="nv">strptime(month ~"/"~ nthoccurrence ~"/"~ year</span><span class="pi">,</span> <span class="s2">"</span><span class="s">%m/%d/%Y"</span><span class="nv">)</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endmacro -%</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="time">Time</h2>
<h3 id="standard-examples-1">Standard Examples</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="c1"># Current time using templates instead of requiring sensor.time. #}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set current_time = "%02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d"|format(now().hour</span><span class="pi">,</span> <span class="nv">now().minute</span><span class="pi">,</span> <span class="nv">now().second) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Convert datetime to a timestamp. #}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set current_time = as_timestamp(now())|timestamp_custom("%I</span><span class="pi">:</span><span class="err">%</span><span class="nv">M %p") %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set unix_timestamp = as_timestamp(now())|int %</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># 24 hour version #}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set hour = now().hour %</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># 12 hour version #}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set hour = now().strftime("%I")|int %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set hour = iif(now().hour>12</span><span class="pi">,</span> <span class="nv">now().hour-12</span><span class="pi">,</span> <span class="nv">now().hour) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set minutes = now().minute %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set seconds = now().second %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set ampm = now().strftime("%p") %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="between-hours">Between hours</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="c1"># Standard way</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">if 6 <= now().hour and now().hour < 12 %</span><span class="pi">}</span>
<span class="m">0.25</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">elif 12 <= now().hour and now().hour < 17 %</span><span class="pi">}</span>
<span class="m">0.40</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">else %</span><span class="pi">}</span>
<span class="m">0.20</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">endif %</span><span class="pi">}</span>
<span class="c1"># Shorter way</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">if 6 <= now().hour < 12 %</span><span class="pi">}</span>
<span class="m">0.25</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">elif 12 <= now().hour < 17 %</span><span class="pi">}</span>
<span class="m">0.40</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">else %</span><span class="pi">}</span>
<span class="m">0.20</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">endif %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="add-or-subtract-time">Add or subtract time</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1"># 15 minutes ago.</span>
<span class="pi">{{</span> <span class="nv">as_timestamp(now()) - timedelta(minutes=15)</span> <span class="pi">}}</span>
<span class="c1"># 12 hours from now.</span>
<span class="pi">{{</span> <span class="nv">as_timestamp(now()) + timedelta(hours=12)</span> <span class="pi">}}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="difference-between-two-times">Difference between two times</h3>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c1"># How long has it been since the last update?</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set seconds_difference = (as_timestamp(now()) - as_timestamp(last_update)) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set minutes_difference = (seconds_difference) / 60 %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set hours_difference = (seconds_difference) / 3600 %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="add-or-subtract-time-1">Add or subtract time</h3>
<p>Pass a negative number to substract minutes instead of adding.
<code class="language-yaml highlighter-rouge"><span class="s">add_minutes(start_time, -15)]</span></code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span> <span class="nv">macro add_minutes(start_time</span><span class="pi">,</span> <span class="nv">minutes_to_add) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set now_datetime = now() %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set now_time = "%02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d"|format(now_datetime.hour</span><span class="pi">,</span> <span class="nv">now_datetime.minute</span><span class="pi">,</span> <span class="nv">now_datetime.second) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set new_datetime = now_datetime | replace(now_time</span><span class="pi">,</span> <span class="nv">start_time) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set new_datetime = as_datetime(new_datetime) + timedelta(minutes=minutes_to_add) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set new_time = "%02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d</span><span class="pi">:</span><span class="err">%</span><span class="nv">02d"|format(new_datetime.hour</span><span class="pi">,</span> <span class="nv">new_datetime.minute</span><span class="pi">,</span> <span class="nv">new_datetime.second) %</span><span class="pi">}</span>
<span class="pi">{{</span> <span class="nv">new_time</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">endmacro %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set minutes_to_add = 5 %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set boys = add_time(states("sensor.boys_room_next_alarm")</span><span class="pi">,</span> <span class="nv">minutes_to_add) %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span> <span class="nv">set brian = add_time(states("sensor.wakeup_brian_time")</span><span class="pi">,</span> <span class="nv">minutes_to_add) %</span><span class="pi">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="bonus-generate-a-list-of-dates">Bonus: generate a list of dates</h3>
<p>I use the template code below to generate a list of dates to add summer break to a school day sensor.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
</pre></td><td class="rouge-code"><pre><span class="pi">{</span><span class="err">%</span><span class="nv">- macro last_dayofmonth(month</span><span class="pi">,</span> <span class="nv">year) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set daysinmonths =</span> <span class="pi">[</span><span class="nv">31</span><span class="pi">,</span><span class="nv">28</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">]</span> <span class="nv">-%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set month = month|default(0)|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set year = year|default(0)|int -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="c1"># Simplified leap year calculation. See https://www.mathsisfun.com/leap-years.html #}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set isleapyear = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set monthindex = month-1 -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- if month == 2 and isleapyear -%</span><span class="pi">}</span>
<span class="pi">{{</span> <span class="nv">daysinmonths</span><span class="pi">[</span><span class="nv">monthindex</span><span class="pi">]</span><span class="nv">+1</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- else -%</span><span class="pi">}</span>
<span class="pi">{{</span> <span class="nv">daysinmonths</span><span class="pi">[</span><span class="nv">monthindex</span><span class="pi">]</span> <span class="pi">}}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endif -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- endmacro -%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set year = now().year %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set months =</span> <span class="pi">[</span><span class="nv">6</span><span class="pi">,</span><span class="nv">7</span><span class="pi">,</span><span class="nv">8</span><span class="pi">]</span> <span class="err">%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set days =</span> <span class="pi">[</span><span class="nv">1</span><span class="pi">,</span><span class="nv">2</span><span class="pi">,</span><span class="nv">3</span><span class="pi">,</span><span class="nv">4</span><span class="pi">,</span><span class="nv">5</span><span class="pi">,</span><span class="nv">6</span><span class="pi">,</span><span class="nv">7</span><span class="pi">,</span><span class="nv">8</span><span class="pi">,</span><span class="nv">9</span><span class="pi">,</span><span class="nv">10</span><span class="pi">,</span><span class="nv">11</span><span class="pi">,</span><span class="nv">12</span><span class="pi">,</span><span class="nv">13</span><span class="pi">,</span><span class="nv">14</span><span class="pi">,</span><span class="nv">15</span><span class="pi">,</span><span class="nv">16</span><span class="pi">,</span><span class="nv">17</span><span class="pi">,</span><span class="nv">18</span><span class="pi">,</span><span class="nv">19</span><span class="pi">,</span><span class="nv">20</span><span class="pi">,</span><span class="nv">21</span><span class="pi">,</span><span class="nv">22</span><span class="pi">,</span><span class="nv">23</span><span class="pi">,</span><span class="nv">24</span><span class="pi">,</span><span class="nv">25</span><span class="pi">,</span><span class="nv">26</span><span class="pi">,</span><span class="nv">27</span><span class="pi">,</span><span class="nv">28</span><span class="pi">,</span><span class="nv">29</span><span class="pi">,</span><span class="nv">30</span><span class="pi">,</span><span class="nv">31</span><span class="pi">]</span> <span class="err">%</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- for month in months %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- set lastday = last_dayofmonth(month</span><span class="pi">,</span> <span class="nv">year)|int %</span><span class="pi">}</span>
<span class="pi">{</span><span class="err">%</span><span class="nv">- for day in days if day <= lastday %</span><span class="pi">}</span>
<span class="nv">- "</span><span class="pi">{{</span> <span class="nv">year ~"-"~ "%02d"|format(month) ~"-"~ "%02d"|format(day)</span> <span class="pi">}}</span><span class="s2">"</span>
<span class="s">{%-</span><span class="nv"> </span><span class="s">endfor</span><span class="nv"> </span><span class="s">%}</span>
<span class="s">{%-</span><span class="nv"> </span><span class="s">endfor</span><span class="nv"> </span><span class="s">%}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>configuration.yaml snippet</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="na">binary_sensor</span><span class="pi">:</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">workday</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">School day</span>
<span class="na">country</span><span class="pi">:</span> <span class="s">US</span>
<span class="na">province</span><span class="pi">:</span> <span class="s">CA</span>
<span class="na">excludes</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">sat</span><span class="pi">,</span> <span class="nv">sun</span><span class="pi">,</span> <span class="nv">holiday</span><span class="pi">]</span>
<span class="na">remove_holidays</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Susan B. Anthony Day</span>
<span class="na">add_holidays</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-04"</span> <span class="c1"># Spring recess</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-05"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-06"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-07"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-08"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-11"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-12"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-13"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-14"</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">2022-04-15"</span>
</pre></td></tr></tbody></table></code></pre></div></div>
Sat, 18 Jan 2020 20:00:00 -0800
https://brianhanifin.com/posts/home-assistant-date-time-template-macros/
https://brianhanifin.com/posts/home-assistant-date-time-template-macros/Voice Event Reminders with a User InterfaceBrian Hanifin<p>Some of my favorite “quality of life” automations are: 1.) an LED strip that turns on when anyone
enters the kitchen, 2.) bedside lights that help us wake up in the morning, and 3.) Alexa voice alerts
that remind us when it is about time to leave for events (school, water polo, circus class, etc).</p>
<p>I got caught up on the Node-Red hype train for a short while, but that ride didn’t last long. To
begin the new decade I decided to retire Node-Red for good. To do so required me to move my many
Event Reminders over to YAML and I wanted to really push my skills with this project.</p>
<h2 id="updates">Updates</h2>
<ul>
<li><strong>2022-01-22</strong>
<ul>
<li>Fix broken links to my Github repository.</li>
</ul>
</li>
</ul>
<h2 id="project-requirements">Project Requirements</h2>
<p><a href="/assets/img/2020-01-06/event-reminder1.png"><img src="/assets/img/2020-01-06/event-reminder1-thumbnail.png" alt="event-reminder1-thumbnail" style="float:right; margin-left: 1em;" /></a></p>
<ol>
<li>Schedule Alexa to alert us when an event draws near.</li>
<li>Provide a User Interface (UI) with the following features for each event:
<ul>
<li>Enable/Disable button</li>
<li>“Skip Next” button</li>
<li>Days of the Week selection</li>
<li>First and Second Announcement times</li>
<li>First and Second Announcement text</li>
<li>List of Echo devices and device groups the announcement should be heard on</li>
</ul>
</li>
<li>The Automations must be reliable! The school principal doesn’t seem to understand that my kids are late because automated voice assistant didn’t remind me to take them to school. ;)</li>
</ol>
<h3 id="user-interface">User Interface</h3>
<p>Before I wrote the automations, I started to lay out the UI so I knew what entities I needed to create.
You can see the final results of this planning in the image above. I ended up using the following six
custom Lovelace plugins, all installed via the <a href="https://github.com/hacs/integration">Home Assistant Community Store (HACS)</a>.</p>
<ol>
<li><a href="https://github.com/custom-cards/decluttering-card">custom:<strong>decluttering-card</strong></a></li>
<li><a href="https://github.com/gadgetchnnel/lovelace-text-input-row">custom:<strong>text-input-row</strong></a></li>
<li><a href="https://github.com/custom-cards/button-card">custom:<strong>button-card</strong></a></li>
<li><a href="https://github.com/custom-cards/button-entity-row">custom:<strong>button-entity-row</strong></a></li>
<li><a href="https://github.com/thomasloven/lovelace-fold-entity-row">custom:<strong>fold-entity-row</strong></a></li>
<li><a href="https://github.com/custom-cards/text-divider-row">custom:<strong>text-divider-row</strong></a></li>
</ol>
<p>I consider the first two to be necessary for this project to be feasible. The last four could be
omitted if you prefer. The decluttering card makes a template for each of my (currently) 10 events,
while the text card allows the text field to span the entire width of the parent card.</p>
<p>If you’d like to see the code behind the UI, here are the files in my repository to look at.</p>
<ul>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/views/05_event_reminders.yaml">/lovelace/views/05_event_reminders.yaml</a></li>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/templates/event_reminder.yaml">/lovelace/templates/event_reminder.yaml</a></li>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/templates/heading.yaml">/lovelace/templates/heading.yaml</a></li>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/templates/button_row7.yaml">/lovelace/templates/button_row7.yaml</a></li>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/templates/button.yaml">/lovelace/templates/button.yaml</a></li>
<li><a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/lovelace/templates/button_pill2.yaml">/lovelace/templates/button_pill2.yaml</a></li>
</ul>
<p>The detail on the UI design beyond the intended scope of this article. However, I will share that
the Enable button simply enables and disables the automation directly. As always feel free to ask
questions in the comment section below, and who knows I may do a follow up article detailing the UI.</p>
<h3 id="automation">Automation</h3>
<p>Here is the base automation I wrote to trigger the Alexa voice reminders. First I trigger the
automation when either the First or Second time equal the current time. This saves a lot of code
duplication as you will see. Then I check if the alert is supposed to be spoken on the current day,
Finally I call <code class="language-yaml highlighter-rouge"><span class="s">script.event_reminder_announce</span></code> to pass the First or Second Announcement text along
to be spoken.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">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
40
41
</pre></td><td class="rouge-code"><pre><span class="na">automation</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">alias</span><span class="pi">:</span> <span class="s">event_reminder_1</span>
<span class="na">trigger</span><span class="pi">:</span>
<span class="na">platform</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="pi">>-</span>
<span class="s">{{ is_state('sensor.event_reminder_1_1',states('sensor.time'))</span>
<span class="s">or is_state('sensor.event_reminder_1_2',states('sensor.time')) }}</span>
<span class="na">condition</span><span class="pi">:</span>
<span class="na">condition</span><span class="pi">:</span> <span class="s">and</span>
<span class="na">conditions</span><span class="pi">:</span>
<span class="c1"># Is today one of the selected days?</span>
<span class="pi">-</span> <span class="na">condition</span><span class="pi">:</span> <span class="s">template</span>
<span class="na">value_template</span><span class="pi">:</span> <span class="pi">></span>
<span class="s">{% set day_name = now().strftime("%A")|lower -%}</span>
<span class="s">{%- if day_name == 'monday' and is_state('input_boolean.event_reminder_1_mon','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'tuesday' and is_state('input_boolean.event_reminder_1_tue','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'wednesday' and is_state('input_boolean.event_reminder_1_wed','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'thursday' and is_state('input_boolean.event_reminder_1_thu','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'friday' and is_state('input_boolean.event_reminder_1_fri','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'saturday' and is_state('input_boolean.event_reminder_1_sat','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- elif day_name == 'sunday' and is_state('input_boolean.event_reminder_1_sun','on') -%}</span>
<span class="s">{{true}}</span>
<span class="s">{%- else -%}</span>
<span class="s">{{false}}</span>
<span class="s">{%- endif %}</span>
<span class="na">action</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">script.event_reminder_announce</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">media_player</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">states('input_select.event_reminder_1_echo')</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">message</span><span class="pi">:</span> <span class="pi">>-</span>
<span class="s">{% if is_state('sensor.event_reminder_1_1',states('sensor.time')) %}</span>
<span class="s">{{ states('input_text.event_reminder_1_1') }}</span>
<span class="s">{% else %}</span>
<span class="s">{{ states('input_text.event_reminder_1_2') }}</span>
<span class="s">{% endif %}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>There is a little more to the final code that handles when the Skip Next button is enabled. But that just
complicates things a little bit.</p>
<h3 id="announcement-scripts">Announcement Scripts</h3>
<h3 id="scriptevent_reminder_announce"><code class="language-yaml highlighter-rouge"><span class="s">script.event_reminder_announce</span></code></h3>
<p>This script replaces the friendly media player name with the entity_id of the target Echo device, and
passes the message along to <code class="language-yaml highlighter-rouge"><span class="s">script.say</span></code>.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="na">script</span><span class="pi">:</span>
<span class="na">event_reminder_announce</span><span class="pi">:</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">script.say</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">message</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">media_player</span><span class="pi">:</span> <span class="pi">></span>
<span class="s">{%- set room = media_player|lower|replace(' ','_')|replace('[','')|replace(']','') %}</span>
<span class="s">{%-</span>
<span class="s">set alexa = {</span>
<span class="s">"bedroom" : "media_player.master_bedroom",</span>
<span class="s">"boys_bedroom": "media_player.boys_room",</span>
<span class="s">"downstairs": "media_player.downstairs",</span>
<span class="s">"kitchen/garage": "group.alexa_welcome",</span>
<span class="s">"garage" : "media_player.garage",</span>
<span class="s">"kitchen" : "media_player.kitchen",</span>
<span class="s">"family_room" : "media_player.family_room",</span>
<span class="s">"play_room": "media_player.play_room",</span>
<span class="s">"upstairs": "media_player.upstairs",</span>
<span class="s">"upstairs_bathroom": "media_player.upstairs_bathroom"</span>
<span class="s">}</span>
<span class="s">-%}</span>
<span class="s">{{ alexa[room] }}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h3 id="scriptsay"><code class="language-yaml highlighter-rouge"><span class="s">script.say</span></code></h3>
<p>The following script is an ultra simplified version of my speech script, which uses the custom
<a href="https://github.com/custom-components/alexa_media_player">Alexa Media Player</a> <a href="https://github.com/custom-components/alexa_media_player/wiki/Configuration%3A-Notification-Component">voice notification feature</a>. This component
is amazing and can also be installed with HACS.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre> <span class="na">say</span><span class="pi">:</span>
<span class="na">sequence</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">service</span><span class="pi">:</span> <span class="s">notify.alexa_media</span>
<span class="na">data_template</span><span class="pi">:</span>
<span class="na">data</span><span class="pi">:</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">announce</span>
<span class="na">method</span><span class="pi">:</span> <span class="s">all</span>
<span class="na">title</span><span class="pi">:</span> <span class="pi">></span>
<span class="s">{%- if title is not string -%}</span>
<span class="s">Home Assistant</span>
<span class="s">{%- else -%}</span>
<span class="s">{{ title }}</span>
<span class="s">{%- endif -%}</span>
<span class="na">message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">message</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">media_player</span><span class="nv"> </span><span class="s">}}"</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="supporting-entities">Supporting Entities</h2>
<p>The tedious part was creating all 15 of the <code class="language-yaml highlighter-rouge"><span class="s">input_boolean</span></code>, <code class="language-yaml highlighter-rouge"><span class="s">input_datetime</span></code>, <code class="language-yaml highlighter-rouge"><span class="s">input_select</span></code>,
<code class="language-yaml highlighter-rouge"><span class="s">input_text</span></code>, and <code class="language-yaml highlighter-rouge"><span class="s">sensor</span></code> entities. After creating my first two reminders, I realized it was going
to take forever to do this 10 times! I am a big fan of the way @frenck organizes his configuration
files (essentially, each entity gets a separate file). That’s when I realized I needed to package
all of the automations, scripts, and entities together… well, in a <a href="https://www.home-assistant.io/docs/configuration/packages">package</a>.</p>
<h3 id="packages">Packages</h3>
<p>You can find all of the packages for this project in the <a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/integrations/event_reminders/">/integrations/event_reminders</a>
folder. The newest revision of the code I shared above can be found in
<a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/integrations/event_reminders/event_reminder_1_package.yaml">event_reminder_1_package.yaml</a>. The speech scripts can be found in
<a href="https://github.com/brianhanifin/Home-Assistant-Config/blob/ce419ffc8dfb861c4310be9239d2949e6edd70bc/integrations/event_reminders/event_reminder_common_package.yaml">event_reminder_common_package.yaml</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>After using this code reliably for a few days I am really proud of how it turned out. I tried to keep
this article relatively brief so I didn’t bore you with every detail. If you enjoyed this overview
of my most ambitious project to date, please leave a comment below. Questions are also welcomed
on the <a href="https://community.home-assistant.io/u/brianhanifin">Home Assistant Community</a> forum, <a href="https://twitter.com/brianhanifin">Twitter</a>, <a href="https://github.com/brianhanifin/Home-Assistant-Config">my GitHub repo</a> or comments
on this blog.</p>
Mon, 06 Jan 2020 20:00:00 -0800
https://brianhanifin.com/posts/voice-event-reminders-with-user-interface/
https://brianhanifin.com/posts/voice-event-reminders-with-user-interface/