<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>home automation on Wouter Bulten</title>
    <link>https://www.wouterbulten.nl/tags/home-automation/</link>
    <description>Recent content in home automation on Wouter Bulten</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <copyright>Copyright Wouter Bulten (except otherwise noted) &amp;copy; 2023 • All rights reserved.
</copyright>
    <lastBuildDate>Thu, 06 Jul 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://www.wouterbulten.nl/tags/home-automation/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Home Automation / Home Assistant setup with recommended hardware: The four-year update</title>
      <link>https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/</link>
      <pubDate>Thu, 06 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/</guid>
      <description>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</description>
      <content:encoded><![CDATA[<p>Four years ago, I upgraded my Home Automation setup from a Raspberry-Pi based system to a more powerful and robust system running on an Intel Nuc. Since then, many things changed in the home automation world, both on the software and hardware side. Still, even some of the early choices have paid out in the end and remained fairly stable.</p>
<p>In this post, I want to share an update on my current Home Assistant setup, including all the hardware I&rsquo;m currently (still) using. It has been around two years since I <a href="/posts/home-assistant-smart-home-hardware-setup/">last wrote about my setup</a>, and a lot has changed since then - new devices have been released, I&rsquo;ve experimented with different integrations, and I&rsquo;ve learned a lot along the way.</p>
<p><strong>Quickly jump to:</strong></p>
<ul>
<li><a href="#controllers">Controller</a></li>
<li><a href="#sensors">Sensors</a></li>
<li><a href="#lights">Lights</a></li>
<li><a href="#heating">Heating</a></li>
<li><a href="#plugs">Plugs</a></li>
</ul>
<h2 id="what-did-not-change">What did (not) change?</h2>
<p>After several years in this home automation adventure, many things changed. However, some things in my setup also didn&rsquo;t. Those patterns - the things that apparently work - are a great way to use as input to evaluate new approaches in the future. Things that &ldquo;stuck&rdquo; probably did something well. Some first learnings:</p>
<ul>
<li><strong>Home Assistant remains my go-to tool</strong>: The basis remained the same all these years: It&rsquo;s still powered by Home Assistant, running locally and on the same Intel Nuc. As a platform, HA became more robust and easy to use, which also translates to more fun developing, maintaining, and using our smart home. I did switch from a customer Docker-based setup to Home Assistant OS running through Proxmox. Updating the various add-ons using Docker Compose did take manual effort, and when life is busy, I only sometimes had the time to do that. While with HA OS, such actions are a simple click of a button (automatic backup included!). The downside is that it&rsquo;s a single point of failure—however, the pros of efficiently managing the whole system outweigh that.</li>
<li><strong>Buy the best you can</strong>: Often also used as advice for buying cooking ingredients, spending a little more on premium devices worked out well. More expensive is not always better, but paying for features like stability, continuous software updates, and no cloud dependencies is worthwhile. For example, my Hue lights, now often updated through OTA updates in Zigbee2mqtt, are a stable core component of my setup. Or, spending a bit more on motion sensors that work well saves frustration when a sensor drops from the network. Of course, it doesn&rsquo;t automatically mean that more expensive devices are automatically better, but it is something that I take into account.</li>
<li><strong>Keep it simple</strong>: Home automation is firstmost a hobby. This means trying many things, including hardware and software, and eventually breaking things and starting over. I&rsquo;m currently at the fifth or so generation of my setup. Still, the opposite is often true for home automation as part of life: it must be stable and run reliably. Reducing the number of vendors (e.g., for lights) and ensuring every automation fulfills a need was key to finding a good balance there.</li>
<li><strong>Automate a lot, prepare for the exception</strong>: Currently, the number of buttons, dimmers, and switches in my house has dramatically reduced. Most lights work automatically based on motion sensors, light measurements, and time of day. No need to dim lights, start a scene or flip a switch. HA ensures that lights go on, dim during the evening and that our living room transitions from &ldquo;working lights&rdquo; to a colorful scene in the evening. Still, our mobile dashboard has a page with &ldquo;kill switches&rdquo; to turn off automations and disable specific automatic transitions. There is always an edge case that you cannot prepare for. A simple switch to turn off a &ldquo;smart&rdquo; feature can save the day.</li>
</ul>
<p>These learnings all resulted in a new (or are the result of?) set of hardware that I&rsquo;m currently using. The next few sections go more into detail on these choices.</p>

   <h2 id="controllers">Controllers</h1>
   <p>The &ldquo;controller&rdquo; is the central hub of my system. Years ago, I started with a single Raspberry Pi but, after a while, switched to a more powerful machine with a dedicated SSD. Since then, the hardware has been stable; I&rsquo;m still using the same device, though the software has changed. It&rsquo;s quite an investment compared to a Pi, but the flexibility and the additional power does benefit in the long run. <br>   My first setup using the Nuc used <a href="/posts/home-automation-setup-docker-compose/">Docker compose</a> but since then I&rsquo;ve switched to Home Assistant OS. While the Docker compose setup worked quite well, the ability to update add-ons easily from HA makes maintenance less of a hassle. By using <a href="https://www.proxmox.com/en/">Proxmox</a> to run a virtual machine for <a href="https://www.home-assistant.io/installation/alternative">Home Assistant OS</a> I retain the flexibility to add additional services later on the same device.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/2BkXyNK" rel="nofollow"><img src="/assets/images/ha/hardware/intelnuc.png" loading="lazy" alt="Intel NUC"></a>
         <figcaption>Intel NUC</figcaption>
      </figure>

      <h3 id="hardware-intel-nuc" class="hardware-item-title">Intel NUC</h3>

      <div class="hardware-item-content">
         <p>The main controller of the system. I use <a href="https://www.proxmox.com/en/">Proxmox</a> to run a virtual machine on which I installed <a href="https://www.home-assistant.io/installation/alternative">Home Assistant OS</a>. By using Proxmox, I have the flexibility to add additional services later on the same machine. All the HA-related services run as add-ons in Home Assistant; this makes managing them very easy. <br>Using a <a href="https://amzn.to/3wnMZG2">Zigbee stick</a> in combination with <a href="https://www.zigbee2mqtt.io/">Zigbee2mqtt</a> gives full flexibility in using smart home devices. The Nuc, therefore, also acts as my main hub for the Zigbee network.</p>

         
         <p>Consisting of:</p>
         <ul>
            
            <li><a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel NUC8i5BEK Bean Canyon</a></li>
            
            <li><a href="https://amzn.to/2nStXIi" rel="nofollow">Samsung SSD 970 EVO Plus 250 GB</a></li>
            
            <li><a href="https://amzn.to/35Dj1zf" rel="nofollow">HyperX Impact 16GB DDR4 2666MHz</a></li>
            
            <li><a href="https://amzn.to/3wnMZG2" rel="nofollow">Sonoff Zigbee 3.0 Gateway based on EFR32MG21</a></li>
            
            <li><a href="https://amzn.to/3WJPXQd" rel="nofollow">USB 3.0 cable to extend the Zigbee radio</a></li>
            
            <li><a href="https://www.home-assistant.io/integrations/dsmr/" rel="nofollow">USB-to-serial P1 Cable for energy monitoring</a></li>
            
         </ul>
         

         <p><small><em>Pros:</em> Powerfull, runs all your services on one device. Home Assistant is more responsive, especially with a large network of devices. Can also run other services.</small></p>
         <p><small><em>Cons:</em> A lot more expensive than a single Pi. Higher learning curve to set up.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Intel NUC see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel nuc</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://www.proxmox.com/en/" rel="nofollow">Proxmox</a></li>
            
            <li><a href="https://www.home-assistant.io/" rel="nofollow">Home Assistant</a></li>
            
            <li><a href="https://www.home-assistant.io/docs/ecosystem/appdaemon/" rel="nofollow">AppDaemon</a></li>
            
            <li><a href="https://nodered.org/" rel="nofollow">Node-RED</a></li>
            
            <li><a href="https://github.com/hassio-addons/addon-adguard-home" rel="nofollow">Adguard</a></li>
            
            <li><a href="https://www.zigbee2mqtt.io/" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="heating">Heating</h1>
   <p>Home automation needs inputs (<em>sensors</em>) and outputs (<em>devices</em>). The lights part of my house is fairly covered, but other exciting ways exist to actuate parts of your house. Heating is one of these and can increase comfort and optimize energy use actively.<br>This part of my home automation adventure is still limited, but I plan to improve it gradually over time.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3XDwXF0" rel="nofollow"><img src="/assets/images/ha/hardware/SRTS-A01.jpg" loading="lazy" alt="Aqara Smart Radiator Thermostat E1"></a>
         <figcaption>Aqara Smart Radiator Thermostat E1</figcaption>
      </figure>

      <h3 id="hardware-aqara-smart-radiator-thermostat-e1" class="hardware-item-title">Aqara Smart Radiator Thermostat E1</h3>

      <div class="hardware-item-content">
         <p>I wanted to control a single radiator without requiring a cloud-based setup. The Aqara radiator was a great candidate due to its compatibility with Zigbee2mqtt, availability, and relatively sleek design.<br>Connecting this device was not the easiest, and I encountered a few network drops. It&rsquo;s a great device on paper, but more testing is required for the final verdict.</p>

         

         <p><small><em>Pros:</em> Nice design. Compatible with many radiators; a few adapters are supplied in the box.</small></p>
         <p><small><em>Cons:</em> I am still testing this device and encountered some connection problems. Based on your heating system, you might also need to be able to turn on your heating system itself (this device just controls a valve). Some features are not usable through Zigbee2mqtt (or at least not easily).</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Aqara Smart Radiator Thermostat E1 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3XDwXF0" rel="nofollow">Aqara Smart Thermostat</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="lights">Lights</h1>
   <p>Lights are my smart home&rsquo;s main &lsquo;output&rsquo; and are controlled by various inputs. Using a <a href="https://amzn.to/3wnMZG2">Zigbee stick</a> in combination with <a href="https://www.zigbee2mqtt.io/">Zigbee2mqtt</a> results in being not limited to a single brand. I have tested multiple brands (Hue, Ikea, OSRAM, Innr), and the lights I use the most are described here.<br>I started out originally with a few Hue lamps combined with cheaper Ikea bulbs. Over the years, I&rsquo;ve slowly moved to using mostly Hue lights. Their light quality, ease of use, availabiltiy of fimrware updates, and connectivity are great quality-of-life upgrades. They are pricier, though, so watching out for sales is recommended.<br>I&rsquo;m still using a few Ikea and Innr lights in places, but as most of the lights are Hue, I&rsquo;ll focus on those here. If you prefer to spend a little bit less on your lights, the Ikea/Innr bulbs are a good alternative.<br><em>Why not smart switches?</em> There is typically a debate between using smart <i>lights</i> and smart <i>switches</i>. Smart switches (e.g., Shelly devices) allow to re-use existing wall switches, are typically cheaper if you control more than one bulb, and, if wifi based, do not need a hub. However, my main selling point for smart lights is the option to change brightness, color, and light temperature per bulb. This is a feature I would not want to live without. Smart switches can dim, but you need smart lights for more features like changing colors. You can, of course, use both, but with smart enough automations, you would not need many switches.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3QZ4CW7" rel="nofollow"><img src="/assets/images/ha/hardware/hue_gu10_color.jpg" loading="lazy" alt="Philips Hue Color GU10"></a>
         <figcaption>Philips Hue Color GU10</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-color-gu10" class="hardware-item-title">Philips Hue Color GU10</h3>

      <div class="hardware-item-content">
         <p>Although a bit expensive, these bulbs&rsquo; overall (light) quality is excellent, and they have a great range of colors (compared to other lights I&rsquo;ve tested). I use them to create color highlights and scenes. Connecting them is typically very easy with a Hue Dimmer Switch, and their meshing capability works well. I went with the cheaper White Ambiance variant for larger areas that do not need colors.</p>

         

         <p><small><em>Pros:</em> Very good color range. Great dimming capabilities. No problems with faulty bulbs (so far). Easy to connect. OTA (updates) through Zigbee2mqtt.</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Color GU10 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3QZ4CW7" rel="nofollow">Hue color bulb (GU10)</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3R12Q7e" rel="nofollow"><img src="/assets/images/ha/hardware/hue_color.png" loading="lazy" alt="Philips Hue Color E27"></a>
         <figcaption>Philips Hue Color E27</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-color-e27" class="hardware-item-title">Philips Hue Color E27</h3>

      <div class="hardware-item-content">
         <p>Similar to the GU10 variants, but in a different form factor. Although a bit expensive, the overall (light) quality of these bulbs is excellent, and they have a great range of colors (in comparison to other lights I&rsquo;ve tested). I use them to create color highlights and scenes. Connecting them is typically very easy with a Hue Dimmer Switch, and their meshing capability works well.</p>

         

         <p><small><em>Pros:</em> Very good color range. Great dimming capabilities. No problems with faulty bulbs (so far). OTA (updates) through Zigbee2mqtt.</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Color E27 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3R12Q7e" rel="nofollow">Hue color bulb (E27)</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3Wt2M0P" rel="nofollow"><img src="/assets/images/ha/hardware/hue_e27.png" loading="lazy" alt="Philips Hue White Ambiance E27"></a>
         <figcaption>Philips Hue White Ambiance E27</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-white-ambiance-e27" class="hardware-item-title">Philips Hue White Ambiance E27</h3>

      <div class="hardware-item-content">
         <p>I use these lights as a cheaper alternative to the colored Hue lights. I use them in places where controlling brightness and color temp is sufficient. Excellent dimming quality and the color warmth has the best range in comparison to the other brands I&rsquo;ve tested.</p>

         

         <p><small><em>Pros:</em> Very good temperature range. Great dimming capabilities. No problems with faulty bulbs (so far). OTA (updates) through Zigbee2mqtt.</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue White Ambiance E27 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3Wt2M0P" rel="nofollow">Hue bulb</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3ZTb4Cc" rel="nofollow"><img src="/assets/images/ha/hardware/hue_gu10.jpeg" loading="lazy" alt="Philips Hue White Ambience GU10"></a>
         <figcaption>Philips Hue White Ambience GU10</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-white-ambience-gu10" class="hardware-item-title">Philips Hue White Ambience GU10</h3>

      <div class="hardware-item-content">
         <p>Highest quality GU10 Zigbee lights I found so far. Excellent dimming capabilities (great for night lights) and a wide color temperature range.</p>

         

         <p><small><em>Pros:</em> Superb dimming capabilities. Nice color temperature range.  OTA (updates) through Zigbee2mqtt.</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue White Ambience GU10 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3ZTb4Cc" rel="nofollow">Hue GU10</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="plugs">Plugs</h1>
   <p>Smart plugs are an easy way to make dumb devices a bit smarter. For example, I use one to control the charger of my wall tablet and a second one to <a href="/posts/auto-turn-on-off-chromecast/">control my (non-smart) TV</a>.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/43dwRVJ" rel="nofollow"><img src="/assets/images/ha/hardware/SP-220.jpg" loading="lazy" alt="Innr smart plug"></a>
         <figcaption>Innr smart plug</figcaption>
      </figure>

      <h3 id="hardware-innr-smart-plug" class="hardware-item-title">Innr smart plug</h3>

      <div class="hardware-item-content">
         <p>I started out with OSRAM plugs. However, one spot in my house needed a smaller form package. The Innr smart plugs offer just that—no fancy features, just a Zigbee-based smart plug that works and doesn&rsquo;t require a lot of space.</p>

         

         <p><small><em>Pros:</em> Good value for your money. Small form factor. Nice design (for a plug).</small></p>
         <p><small><em>Cons:</em> No power measurement in this configuration.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Innr smart plug see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/43dwRVJ" rel="nofollow">Innr smart plug</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/2UBNGXq" rel="nofollow"><img src="/assets/images/ha/hardware/osram_plug.jpg" loading="lazy" alt="OSRAM Smart&#43; Plug (Sylvania in the US)"></a>
         <figcaption>OSRAM Smart&#43; Plug (Sylvania in the US)</figcaption>
      </figure>

      <h3 id="hardware-osram-smart&#43;-plug-sylvania-in-the-us" class="hardware-item-title">OSRAM Smart&#43; Plug (Sylvania in the US)</h3>

      <div class="hardware-item-content">
         <p>Simple plug that I use for adding power on/off control to non-smart devices. I&rsquo;ve only tested the EU OSRAM version, in the US this brand is available under the Sylvania name.
The OSRAM hub is not required as it connects to <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>

         

         <p><small><em>Pros:</em> Good value for your money. Good zigbee meshing capabilities.</small></p>
         <p><small><em>Cons:</em> No power measurement (at least in Zigbee2mqtt/Deconz). A bit bulky in comparison to others (e.g., Innr).</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the OSRAM Smart&#43; Plug (Sylvania in the US) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2UBNGXq" rel="nofollow">OSRAM Plug (EU)</a></li>
            
            <li><a href="https://amzn.to/2G93ZYz" rel="nofollow">Sylvania Plug (US)</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="sensors">Sensors</h1>
   <p>Sensors are what transitioned my home from an <em>app powered</em> home to a (aspiring) <em>smart</em> home. Before I had any sensors, lights were controlled by an app (e.g., the Philips Hue app), and everything was manual. Now, with the introduction of these sensors, 95% of my lights are turned on and off automatically. These sensors are also an excellent input for alarm systems.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/3wkl1ef" rel="nofollow"><img src="/assets/images/ha/hardware/hue_motion.jpg" loading="lazy" alt="Philips Hue Motion Sensor"></a>
         <figcaption>Philips Hue Motion Sensor</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-motion-sensor" class="hardware-item-title">Philips Hue Motion Sensor</h3>

      <div class="hardware-item-content">
         <p>The Hue motion sensor is my go-to choice for a motion sensor. It reacts fast, works well in Zigbee2mqtt, and the light sensor is fairly accurate. The temperature sensor is not great, as it can vary quite a bit between devices, but it gives some indication.</p>

         

         <p><small><em>Pros:</em> Fast response time. Realiable. Very precise light sensor. Can be installed using a magnet.</small></p>
         <p><small><em>Cons:</em> Not the cheapest sensor on the market. Larger than, for example, the Xiaomi motion sensor. Temperature sensor not very accurate.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Motion Sensor see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/3wkl1ef" rel="nofollow">Hue motion sensor</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="https://amzn.to/46zablJ" rel="nofollow"><img src="/assets/images/ha/hardware/hue-outdoor.jpg" loading="lazy" alt="Philips Hue Motion Sensor (Outdoor)"></a>
         <figcaption>Philips Hue Motion Sensor (Outdoor)</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-motion-sensor-outdoor" class="hardware-item-title">Philips Hue Motion Sensor (Outdoor)</h3>

      <div class="hardware-item-content">
         <p>Hue motion sensor in a form factor suitable for outdoor use. One of the few Zigbee sensors available for use in the garden.</p>

         

         <p><small><em>Pros:</em> Easy to install in various configurations. Design looks nice. Feels sturdy.</small></p>
         <p><small><em>Cons:</em> Not cheap. Very sensitive, I have quite some problems with false positives. The sun affects the temperature sensor and will not be accurate for the ambient temperature.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Motion Sensor (Outdoor) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/46zablJ" rel="nofollow">Hue outdoor motion sensor</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/">Local face recognition for Home Assistant using TensorFlow.js, Part 2: Learning to recognize faces</a></h3>
            <p><small>Second post of my series on face recognition for presence detection in Home Assistant. In this post, I create a face recognition system.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Automatically turn on tv when streaming to a Chromecast</title>
      <link>https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/</link>
      <pubDate>Sun, 01 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/</guid>
      <description>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</description>
      <content:encoded><![CDATA[<p>I&rsquo;m all in favor of &ldquo;dumb&rdquo; TVs combined with a <a href="https://amzn.to/3Qav6E0">Chromecast</a> or Apple TV. I used to have a smart button that could toggle a smart switch to turn on my TV, which helped reduce standby mode when we weren&rsquo;t using it. However, pressing the switch was an extra step that I often found inconvenient. That&rsquo;s why I recently implemented a simple automation that turns on the TV automatically. Now, I can&rsquo;t imagine why I didn&rsquo;t set this up sooner - it&rsquo;s so easy and convenient. In general, it&rsquo;s a good lesson that sometimes the most simple automation can have the most practical impact.</p>
<h2 id="hardware-requirements">(Hardware) requirements</h2>
<ul>
<li>HomeAssistant with NodeRED</li>
<li>A media player connected to the TV and configured in HA. I use a <a href="https://amzn.to/3Qav6E0">Chromecast</a>.</li>
<li>A smart switch that controls power to the TV (e.g., a <a href="https://amzn.to/3Ci9rDX">Innr smart plug</a>).</li>
</ul>
<h2 id="flow">Flow</h2>
<p>The flow is effortless. When you connect to a Chromecast from a device (e.g., a smartphone), the state of the media player in HA goes from <code>off</code> to <code>idle</code>. I use that state change to turn on the TV. Usually, when the Chromecast has started the streaming service, the TV also completes its startup routine.</p>
<p>To turn off the TV, I do the reverse. If the state goes to <code>off</code> for 5 seconds, I turn off the smart switch. The five-second delay is used to disable the automation when switching from streaming services. E.g., if you switch from Netflix to Youtube on your Chromecast, it briefly goes to <code>off</code>. Including a 5-second delay fixes that.</p>
<figure>
    <img loading="lazy" src="nodered-flow-chromecast.png"
         alt="Simple flow to automatically turn on the TV"/> <figcaption>
            <p>Simple flow to automatically turn on the TV</p>
        </figcaption>
</figure>

<h2 id="source-code">Source code</h2>
<p>You can import the flow directly in NodeRED using the following snippet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;12a49465495b3126&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;server-state-changed&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;a53c777be3d20af7&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;TV Control state change&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;version&#34;</span><span class="p">:</span><span class="mi">4</span><span class="p">,</span><span class="nt">&#34;exposeToHomeAssistant&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;haConfig&#34;</span><span class="p">:[{</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;name&#34;</span><span class="p">,</span><span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">},{</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;icon&#34;</span><span class="p">,</span><span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">}],</span><span class="nt">&#34;entityidfilter&#34;</span><span class="p">:</span><span class="s2">&#34;media_player.living_room_tv&#34;</span><span class="p">,</span><span class="nt">&#34;entityidfiltertype&#34;</span><span class="p">:</span><span class="s2">&#34;exact&#34;</span><span class="p">,</span><span class="nt">&#34;outputinitially&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;state_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;haltifstate&#34;</span><span class="p">:</span><span class="s2">&#34;off&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_compare&#34;</span><span class="p">:</span><span class="s2">&#34;is&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;output_only_on_state_change&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;for&#34;</span><span class="p">:</span><span class="s2">&#34;5&#34;</span><span class="p">,</span><span class="nt">&#34;forType&#34;</span><span class="p">:</span><span class="s2">&#34;num&#34;</span><span class="p">,</span><span class="nt">&#34;forUnits&#34;</span><span class="p">:</span><span class="s2">&#34;seconds&#34;</span><span class="p">,</span><span class="nt">&#34;ignorePrevStateNull&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;ignorePrevStateUnknown&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;ignorePrevStateUnavailable&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;ignoreCurrentStateUnknown&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;ignoreCurrentStateUnavailable&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;outputProperties&#34;</span><span class="p">:[{</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;propertyType&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;valueType&#34;</span><span class="p">:</span><span class="s2">&#34;entityState&#34;</span><span class="p">},{</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;data&#34;</span><span class="p">,</span><span class="nt">&#34;propertyType&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;valueType&#34;</span><span class="p">:</span><span class="s2">&#34;eventData&#34;</span><span class="p">},{</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;topic&#34;</span><span class="p">,</span><span class="nt">&#34;propertyType&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;value&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;valueType&#34;</span><span class="p">:</span><span class="s2">&#34;triggerId&#34;</span><span class="p">}],</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">150</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">220</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;20e6ab7719a86154&#34;</span><span class="p">],[</span><span class="s2">&#34;095a728742e847df&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;095a728742e847df&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;a53c777be3d20af7&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Turn on TV&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;version&#34;</span><span class="p">:</span><span class="mi">5</span><span class="p">,</span><span class="nt">&#34;debugenabled&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;domain&#34;</span><span class="p">:</span><span class="s2">&#34;switch&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_on&#34;</span><span class="p">,</span><span class="nt">&#34;areaId&#34;</span><span class="p">:[],</span><span class="nt">&#34;deviceId&#34;</span><span class="p">:[</span><span class="s2">&#34;f3bb9df57ab620c668166b9c5eabb1f2&#34;</span><span class="p">],</span><span class="nt">&#34;entityId&#34;</span><span class="p">:[],</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;dataType&#34;</span><span class="p">:</span><span class="s2">&#34;jsonata&#34;</span><span class="p">,</span><span class="nt">&#34;mergeContext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;mustacheAltTags&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;outputProperties&#34;</span><span class="p">:[],</span><span class="nt">&#34;queue&#34;</span><span class="p">:</span><span class="s2">&#34;none&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">430</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">240</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;20e6ab7719a86154&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;a53c777be3d20af7&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Turn off TV&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;version&#34;</span><span class="p">:</span><span class="mi">5</span><span class="p">,</span><span class="nt">&#34;debugenabled&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;domain&#34;</span><span class="p">:</span><span class="s2">&#34;switch&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_off&#34;</span><span class="p">,</span><span class="nt">&#34;areaId&#34;</span><span class="p">:[],</span><span class="nt">&#34;deviceId&#34;</span><span class="p">:[</span><span class="s2">&#34;f3bb9df57ab620c668166b9c5eabb1f2&#34;</span><span class="p">],</span><span class="nt">&#34;entityId&#34;</span><span class="p">:[],</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;dataType&#34;</span><span class="p">:</span><span class="s2">&#34;jsonata&#34;</span><span class="p">,</span><span class="nt">&#34;mergeContext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;mustacheAltTags&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;outputProperties&#34;</span><span class="p">:[],</span><span class="nt">&#34;queue&#34;</span><span class="p">:</span><span class="s2">&#34;none&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">430</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">200</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[]]}]</span>
</span></span></code></pre></div><div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/">Local face recognition for Home Assistant using TensorFlow.js, Part 2: Learning to recognize faces</a></h3>
            <p><small>Second post of my series on face recognition for presence detection in Home Assistant. In this post, I create a face recognition system.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Automatic dark mode for Home Assistant</title>
      <link>https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/</link>
      <pubDate>Thu, 09 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/</guid>
      <description>Quick tutorial on setting up an automatic dark mode for Home Assistant.</description>
      <content:encoded><![CDATA[<p>In this quick tutorial, we will work on how to configure automatic dark mode for Home Assistant. It works with any theme and Lovelace interface as long as you have a dark and light version of the preferred theme. It&rsquo;s easy to set up and a nice feature for wall-mounted tablets or the mobile interface. I use it primarily with my wall-mounted <a href="https://amzn.to/2WFJmIx" rel="nofollow">Fire Tablet</a>.</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/ha/dark-mode-home-assistant.jpg"
         alt="With a single automation, we can setup an easy but effecive auto dark mode for Home Assistant. Left is an example interface during the day, right is the interface in the evening."/> <figcaption>
            <p>With a single automation, we can setup an easy but effecive auto dark mode for Home Assistant. Left is an example interface during the day, right is the interface in the evening.</p>
        </figcaption>
</figure>

<h2 id="getting-a-dark-mode-theme">Getting a dark mode theme</h2>
<p>To set up a dark mode, we will need two themes: one for the day/light theme, and one for the dark mode. In my setup, I make use of the &ldquo;Google theme&rdquo; which conveniently has both a light and dark version. You are of course free to select two different themes.</p>
<ul>
<li><a href="https://github.com/JuanMTech/google_light_theme">Google Light theme</a>: <a href="https://github.com/JuanMTech/google_light_theme/blob/master/themes/google_light_theme.yaml">google_light_theme.yaml</a></li>
<li><a href="https://github.com/JuanMTech/google_dark_theme">Google Dark theme</a>: <a href="https://github.com/JuanMTech/google_dark_theme/blob/master/themes/google_dark_theme.yaml">google_dark_theme.yaml</a></li>
</ul>
<p>You can install the themes using <a href="https://hacs.xyz/">HACS</a> or by adding them to the <code>frontend</code> section of your <code>configuration.yaml</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">frontend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">themes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Google Dark Theme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app-header-background-color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#171717&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app-header-text-color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#BDC1C6&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># .. rest of theme here</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">Google Light Theme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app-header-background-color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#F8F8F8&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">app-header-text-color</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#424242&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># .. rest of theme here</span><span class="w">
</span></span></span></code></pre></div><h2 id="configuring-users">Configuring users</h2>
<p>Next, we need to make sure that we can change the theme for the users. For each user that you have configured for HA, or each user you want to enable dark mode for, go to their profile page and select &ldquo;Backend selected&rdquo; as their theme. &ldquo;Backend selected&rdquo; makes sure that we can set the theme of a user programmatically using an automation.</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/ha/backend-theme.png"
         alt="Make sure that all users have set their theme to backend-selected."/> <figcaption>
            <p>Make sure that all users have set their theme to backend-selected.</p>
        </figcaption>
</figure>

<h2 id="creating-the-automation">Creating the automation</h2>
<p>With the theme and users setup, the only thing left is to create the automation. The automation is quite simple, based upon the state of the <a href="https://www.home-assistant.io/integrations/sun/">sun</a> integration (which is auto-enabled), we set the correct theme. If you would prefer to start dark mode at a fixed time, you could also replace the sun trigger by a time trigger. To make sure the correct theme is always loaded, we also run the automation when HA starts up.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">automation</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Set the correct theme during day and night</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Automatic Theme Change</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">event</span><span class="p">:</span><span class="w"> </span><span class="l">start</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sun.sun</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">service_template</span><span class="p">:</span><span class="w"> </span><span class="l">frontend.set_theme</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="p">&gt;</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            {% if is_state(&#39;sun.sun&#39;, &#39;above_horizon&#39;) %}
</span></span></span><span class="line"><span class="cl"><span class="sd">              Google Light Theme
</span></span></span><span class="line"><span class="cl"><span class="sd">            {% else %}
</span></span></span><span class="line"><span class="cl"><span class="sd">              Google Dark Theme
</span></span></span><span class="line"><span class="cl"><span class="sd">            {% endif %}</span><span class="w">            
</span></span></span></code></pre></div><p>That&rsquo;s it! After adding the automation, reload Home Assistant and you should have automatic dark mode from now on. Overall, this is a very simple but rewarding automation. I mainly installed it for my wall-mounted tablet, which now blends in way more during the evening (see my post on <a href="/posts/home-assistant-smart-home-hardware-setup/">my hardware setup</a> for more information).</p>
<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/">Local face recognition for Home Assistant using TensorFlow.js, Part 2: Learning to recognize faces</a></h3>
            <p><small>Second post of my series on face recognition for presence detection in Home Assistant. In this post, I create a face recognition system.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Local face recognition for Home Assistant using TensorFlow.js, Part 2: Learning to recognize faces</title>
      <link>https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/</link>
      <pubDate>Mon, 01 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/</guid>
      <description>Second post of my series on face recognition for presence detection in Home Assistant. In this post, I create a face recognition system.</description>
      <content:encoded><![CDATA[<p>This is the second post of my series on <strong>face recognition for presence detection in Home Asssistant</strong>. In this series, I am investigating how to set up a face recognition system for my smart home that works locally, without the need for cloud services or internet access, and is fully controllable. In the <a href="https://www.wouterbulten.nl/posts/presence-detection-face-recognition-part-1/">first post</a> I discussed requirements, possible alternatives and the first setup of a face detection system. In this second part, we are going to look at actually learning to detect faces. I will build upon the code written in part 1, so make sure to check that out first if you haven&rsquo;t done so yet.</p>
<p><em>Note:</em> This parts follows up on Part 1 so make sure to check that out first! All code for this second part can be found in the <a href="https://github.com/wouterbulten/ha-facerec-js">GitHub repository</a> under tag <a href="https://github.com/wouterbulten/ha-facerec-js/tree/v0.2.1">v0.2.1</a>.</p>
<h2 id="hardware-requirements">Hardware requirements</h2>
<p>The hardware used in this second part is the same as in part 1. Minimally, the face recognition system should have one camera and something that can run the algorithm. In my case, I use the following:</p>
<ul>
<li><a href="https://amzn.to/3ci2ClP" rel="nofollow">Raspberry Pi 3B+</a> connected to a <a href="https://amzn.to/36RtQ1X" rel="nofollow">Raspberry Pi Camera</a>. This is used as the main camera system. I use <a href="https://github.com/ccrisan/motioneyeos">motionEyeOS</a> as the OS on my Pi.</li>
<li>An <a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel NUC8i5BEK</a> that will run the face recognition system (and also runs Home Assistant). See my post on <a href="/posts/home-assistant-smart-home-hardware-setup/">my hardware setup</a> for more information.</li>
</ul>
<p>In the examples below, I will use a static image with faces, so technically, you can follow along without a working camera. Just point the <code>CAMERA_URL</code> environment variable to an image on your disk.</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/face-detection-setup.svg"
         alt="Overview of the face detection system. In this second part of the series we are focusing on the recognition part of the pipeline."/> <figcaption>
            <p>Overview of the face detection system. In this second part of the series we are focusing on the recognition part of the pipeline.</p>
        </figcaption>
</figure>

<h2 id="collecting-the-training-set">Collecting the training set</h2>
<p>To recognize faces, we will need a training set with faces and associated labels. To store and load those images efficiently, I am using a single directory per person. Each directory can contain multiple images of the same person. In the <a href="https://github.com/justadudewhohacks/face-api.js/tree/master/examples/images">Github repository</a> of the face-api.js library you can find some sample images from the cast of the Big Bang Theory.</p>
<figure class="lazyload">
    <img loading="lazy" src="https://user-images.githubusercontent.com/31125521/57297377-bfcdfd80-70cf-11e9-8afa-2044cb167bff.gif"
         alt="Example image used in this post. The end goal is to recognize all faces of the Big Bang Theory cast. Retrieved from: https://github.com/justadudewhohacks/face-api.js/"/> <figcaption>
            <p>Example image used in this post. The end goal is to recognize all faces of the Big Bang Theory cast. Retrieved from: <a href="https://github.com/justadudewhohacks/face-api.js/">https://github.com/justadudewhohacks/face-api.js/</a></p>
        </figcaption>
</figure>

<h2 id="training-a-face-matcher">Training a face matcher</h2>
<p>To recognize faces, we are using the <code>FaceMatcher</code> object that can match an unknown face to a database of face descriptors. Each descriptor is a list of numbers describing features of that face. By comparing two descriptors, we can check whether the faces are the same. To train this matcher, we need to generate descriptors of all faces in our training set. In this section, we will fill out the <code>train</code> function that creates this matcher object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">faceapi</span> <span class="nx">from</span> <span class="s2">&#34;face-api.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">train</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Train here
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="c1">// 1. Load required models
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// 2. Find all classes (persons)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// 3. Create descriptors for each person
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">FaceMatcher</span><span class="p">(</span><span class="nx">faceDescriptors</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>First, we load all the required models from disk (weights are in the GitHub repository). This makes sure that all models are ready to use.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// 1. Load required models
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">ssdMobilenetv1</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">faceLandmark68Net</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">faceRecognitionNet</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span></code></pre></div><p>When the models are loaded, we traverse the training directory to determine which persons we need to recognize. If you use the Dockerfile associated with this project or run the code directly, example images are in <code>./faces</code>, you can override this directory by setting the <code>FACES_DIR</code> environment variable. If you use the example faces, the snippet below should find eight persons.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// 2. Find all classes (persons)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">let</span> <span class="nx">trainingDir</span> <span class="o">=</span> <span class="s1">&#39;./faces&#39;</span><span class="p">;</span> <span class="c1">// Default directory in the docker image
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FACES_DIR</span> <span class="o">&amp;&amp;</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">existsSync</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FACES_DIR</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Loading training images from </span><span class="si">${</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FACES_DIR</span><span class="si">}</span><span class="sb">`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">trainingDir</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FACES_DIR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Traverse the training dir and get all classes (1 dir = 1 class)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">classes</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readdirSync</span><span class="p">(</span><span class="nx">trainingDir</span><span class="p">,</span> <span class="p">{</span> <span class="nx">withFileTypes</span><span class="o">:</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">i</span> <span class="p">=&gt;</span> <span class="nx">i</span><span class="p">.</span><span class="nx">isDirectory</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="p">=&gt;</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Found </span><span class="si">${</span><span class="nx">classes</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> different persons to learn.`</span><span class="p">);</span>
</span></span></code></pre></div><p>For each person directory, we now discover all images. There is no validation on the images, so make sure that there are no other files in the directories (or add some basic checks).</p>
<p>Each image is loaded, and face descriptors are computed. In this case, we assume each person-image is a cropped version of a single face (without any background). If the images would also contain background, we would have to run a face detection algorithm first to extract the face, increasing the overall training time. The descriptors are labeled with the class name (name of the directory). Later we will work on an automated method to create these training images.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">faceDescriptors</span> <span class="o">=</span> <span class="kr">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">classes</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kr">async</span> <span class="nx">className</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">images</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readdirSync</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">trainingDir</span><span class="p">,</span> <span class="nx">className</span><span class="p">),</span> <span class="p">{</span> <span class="nx">withFileTypes</span><span class="o">:</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">i</span> <span class="p">=&gt;</span> <span class="nx">i</span><span class="p">.</span><span class="nx">isFile</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="p">=&gt;</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">trainingDir</span><span class="p">,</span> <span class="nx">className</span><span class="p">,</span> <span class="nx">i</span><span class="p">.</span><span class="nx">name</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Load all images for this class and retrieve face descriptors
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">descriptors</span> <span class="o">=</span> <span class="kr">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">images</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kr">async</span> <span class="nx">path</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">path</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">computeFaceDescriptor</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}));</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">LabeledFaceDescriptors</span><span class="p">(</span><span class="nx">className</span><span class="p">,</span> <span class="nx">descriptors</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}));</span>
</span></span></code></pre></div><h2 id="recognizing-faces">Recognizing faces</h2>
<p>With our face matcher trained, we can try to recognize faces in new images. To do this, we extend the <code>motion-detected</code> route from <a href="https://www.wouterbulten.nl/posts/presence-detection-face-recognition-part-1/">part 1  of this series</a>. Make sure to read that post for more background on how I use Express and Node.JS to set up this service.</p>
<p>The first step is to load the new image from the camera and compute descriptors for each face in the image. In these steps, we will use the same models we loaded in the <code>train()</code> function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">withFaceLandmarks</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">withFaceDescriptors</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> faces detected`</span><span class="p">);</span>
</span></span></code></pre></div><p>After the faces have been detected, we can compare the descriptors using the <code>FaceMatcher</code>. As in part 1, I save the last detection to disk so that we can view it later.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Create canvas to save to disk
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">results</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span><span class="nx">detection</span><span class="p">,</span> <span class="nx">descriptor</span><span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// See if the descriptor matches a face in our database
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nx">faceMatcher</span><span class="p">.</span><span class="nx">findBestMatch</span><span class="p">(</span><span class="nx">descriptor</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Detected face: </span><span class="si">${</span><span class="nx">label</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">drawBox</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">DrawBox</span><span class="p">(</span><span class="nx">detection</span><span class="p">.</span><span class="nx">box</span><span class="p">,</span> <span class="p">{</span> <span class="nx">label</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="nx">drawBox</span><span class="p">.</span><span class="nx">draw</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Write detections to public folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;public/last-detection.jpg&#39;</span><span class="p">,</span> <span class="nx">out</span><span class="p">.</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Detection saved.&#39;</span><span class="p">);</span>
</span></span></code></pre></div><p>Combining all snippets, the full route is as follows:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;/motion-detected&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;Motion detected&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">withFaceLandmarks</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">withFaceDescriptors</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> faces detected`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Create canvas to save to disk
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">results</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span><span class="nx">detection</span><span class="p">,</span> <span class="nx">descriptor</span><span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nx">faceMatcher</span><span class="p">.</span><span class="nx">findBestMatch</span><span class="p">(</span><span class="nx">descriptor</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Detected face: </span><span class="si">${</span><span class="nx">label</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">drawBox</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">DrawBox</span><span class="p">(</span><span class="nx">detection</span><span class="p">.</span><span class="nx">box</span><span class="p">,</span> <span class="p">{</span> <span class="nx">label</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawBox</span><span class="p">.</span><span class="nx">draw</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Write detections to public folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;public/last-detection.jpg&#39;</span><span class="p">,</span> <span class="nx">out</span><span class="p">.</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Detection saved.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>For testing, it can be helpful to directly show the recognized faces on screen, instead of saving them to disk. For this, we can make a new route under <code>/recognize</code> that directly shows the latest detection. To do this, we have to make use of <code>createJPEGStream()</code> which allows us to stream canvas data to the client&rsquo;s browser. The content of this route is almost equal to the existing route, with the exception in saving the image.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;/recognize&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">withFaceLandmarks</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">withFaceDescriptors</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span><span class="si">}</span><span class="sb"> face(s) detected`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Create canvas to save to disk
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">results</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(({</span><span class="nx">detection</span><span class="p">,</span> <span class="nx">descriptor</span><span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">label</span> <span class="o">=</span> <span class="nx">faceMatcher</span><span class="p">.</span><span class="nx">findBestMatch</span><span class="p">(</span><span class="nx">descriptor</span><span class="p">).</span><span class="nx">toString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Detected face: </span><span class="si">${</span><span class="nx">label</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">drawBox</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">DrawBox</span><span class="p">(</span><span class="nx">detection</span><span class="p">.</span><span class="nx">box</span><span class="p">,</span> <span class="p">{</span> <span class="nx">label</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawBox</span><span class="p">.</span><span class="nx">draw</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">set</span><span class="p">(</span><span class="s1">&#39;Content-Type&#39;</span><span class="p">,</span> <span class="s1">&#39;image/jpeg&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">out</span><span class="p">.</span><span class="nx">createJPEGStream</span><span class="p">().</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">res</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><h2 id="starting-the-server">Starting the server</h2>
<p>With all routes defined, we can start the Express server. Note that the <code>train</code> function we defined earlier is async. It has to perform several actions before we can start recognizing faces, including training the face matcher. If we started up the Express server without the actions completed, the face matcher would not be able to match any faces. Instead, we first run the train function and after it completes start the server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nx">faceMatcher</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">start</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;Start training recognition model.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">faceMatcher</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">train</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;Finished training.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Start express on the defined port
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server running on port </span><span class="si">${</span><span class="nx">PORT</span><span class="si">}</span><span class="sb">`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">start</span><span class="p">();</span>
</span></span></code></pre></div><p>If you start the server and run it on the <a href="https://github.com/justadudewhohacks/face-api.js/blob/master/examples/images/bbt4.jpg">example image</a>, the algorithm should be able to detect all faces:</p>
<figure class="lazyload">
    <img loading="lazy" src="https://user-images.githubusercontent.com/31125521/41526995-1a90e4e6-72e6-11e8-96d4-8b2ccdee5f79.gif"
         alt="Recognized faces from the BBT cast. Source: https://justadudewhohacks.github.io/face-api.js/docs/index.html"/> <figcaption>
            <p>Recognized faces from the BBT cast. Source: <a href="https://justadudewhohacks.github.io/face-api.js/docs/index.html">https://justadudewhohacks.github.io/face-api.js/docs/index.html</a></p>
        </figcaption>
</figure>

<p>To run the algorithm on your own face, you only have to create a new directory in the <code>faces</code> directory and add images of your own face. After restarting the server, the algorithm will pick these images up and should be able to recognize you. The console will output how many persons can be recognized.</p>
<h2 id="adding-training-images-on-the-fly">Adding training images on the fly</h2>
<p>Adding training images manually is quite cumbersome. Luckily, we can also add these semi-automatically! To do this we have to use two functions from the <code>face-api.js</code> library: 1) <code>detectAllFaces</code> to detect faces in a new image; and 2) <code>extractFaces</code> to get the face from the image. Combined, these functions first find all faces in an image and then for each face extract a training image in the correct size.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">faces</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">extractFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="nx">results</span><span class="p">);</span>
</span></span></code></pre></div><p>The <code>faces</code> variable will contain small images of each face in the image. I made this functionality available through a new route <code>/add-face/:name</code>. Apart from the two functions above, this route adds some additional checks to see if a training image can be extracted. For example, if more than one face is detected we are not sure which face to extract so we should trigger an error. The same holds if no face was detected.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Add a new training sample
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;/add-face/:name&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Load an image
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Check if the identifier of this person is a valid directory name (letters and numbers)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">name</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/^[0-9a-zA-Z]+$/</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s2">&#34;Invalid name provided for training sample.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Trying to detect new training sample for &#39;</span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">&#39;.`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">results</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// This route only works when there is one person in the image
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">if</span><span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">422</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s2">&#34;Multiple faces detected in the image, cannot save training data.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">(</span><span class="nx">results</span><span class="p">.</span><span class="nx">length</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">422</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="s2">&#34;No faces detected in the image, cannot save training data.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">faces</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">extractFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="nx">results</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Check if this a new person
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// trainingDir should map to your training folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="kr">const</span> <span class="nx">outputDir</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">trainingDir</span><span class="p">,</span> <span class="nx">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">fs</span><span class="p">.</span><span class="nx">existsSync</span><span class="p">(</span><span class="nx">outputDir</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="sb">`Creating training dir for new person &#39;</span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">&#39;.`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fs</span><span class="p">.</span><span class="nx">mkdirSync</span><span class="p">(</span><span class="nx">outputDir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Write detections to training folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">outputDir</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span><span class="si">}</span><span class="sb">.jpg`</span><span class="p">),</span> <span class="nx">faces</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s1">&#39;New training sample saved.&#39;</span><span class="p">);</span>    
</span></span><span class="line"><span class="cl">    <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">send</span><span class="p">(</span><span class="s1">&#39;OK&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>By calling this route you can add a new training image for a person. To illustrate this I applied the route to the following image of Spock:</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/Spock.jpg"
         alt="Input image to extract a training image from. Adapted from https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG."/> <figcaption>
            <p>Input image to extract a training image from. Adapted from <a href="https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG">https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG</a>.</p>
        </figcaption>
</figure>

<p>Now if you go to <code>/add-face/spock</code>, a new directory should be created with an extracted image of the face of Spock:</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/spock_extracted.jpg"
         alt="Extracted face of Spock. Adapted from https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG."/> <figcaption>
            <p>Extracted face of Spock. Adapted from <a href="https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG">https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_William_Shatner_Star_Trek_1968.JPG</a>.</p>
        </figcaption>
</figure>

<p>After a server restart, the new training images are loaded and will be included in the <code>FaceMatcher</code>. To add yourself, go stand in front of your camera and call <code>/add-face/your-name</code> a few times in different positions. With a few training images the system should already be able to recognize you from now on!</p>
<h2 id="wrap-up--next-steps">Wrap up &amp; next steps</h2>
<p>Looking back at the TODO-list of <a href="https://www.wouterbulten.nl/posts/presence-detection-face-recognition-part-1/">part 1</a>, the first item has now been addressed. We have also built a more efficient way of adding new training samples. The remainder of the items are more focussed on stability and further extending the usability of the system. The core functionality is present. In the future I will work on the Home Assistant integration and adding those extensions.</p>
<ol>
<li><del>The current setup only <strong>detects</strong> faces, we still need to perform the <strong>recognition</strong>.</del></li>
<li><del> We need a way of easily adding new training images, for example, when a user is not recognized.</del></li>
<li>The server only accepts a single camera; it would be nice to support multiple cameras. Of course, you can run an instance for each camera, but maybe it&rsquo;s nice to combine them.</li>
<li>When the face detection is running, the server cannot process other requests. A queue system could help with that.</li>
<li>There is no security: I wouldn&rsquo;t advise running this on an unprotected network! We could add some basic authentication to protect the routes.</li>
</ol>
<p>For now, feel free to let me know what you thought in the <a href="#comment-form">comments</a>. I am happy to hear any questions or remarks! You can find the latest code in the <a href="https://github.com/wouterbulten/ha-facerec-js">Github repository</a>; the code for this post is tagged <a href="https://github.com/wouterbulten/ha-facerec-js/tree/v0.2.1">v0.2.1</a>.</p>
<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Local presence detection using face recognition and TensorFlow.js for Home Assistant, Part 1: Detection</title>
      <link>https://www.wouterbulten.nl/posts/presence-detection-face-recognition-part-1/</link>
      <pubDate>Fri, 29 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/presence-detection-face-recognition-part-1/</guid>
      <description>Face recognition can be a cool addition to a smart home but has potential severe privacy issues. In this post, I start building on a completely local alternative to cloud-based solutions. This first part focusses on face detection.</description>
      <content:encoded><![CDATA[<p>Face recognition can be a nice way of adding presence detection to your smart home. A simple camera at your front door could detect who is home and trigger certain automations in Home Assistant. However, with all camera-based systems, this comes with a risk for user privacy. How are camera feeds processed? Are images stored? Is it easy to disable processing?</p>
<p>Most available out-of-the-box solutions use cloud-based or closed-source approaches. With something as face recognition, I want to have more control before I would consider adding it to my smart home setup. Given this, I was interested in whether I could set up such a system entirely local instead, without any reliance on external cloud services. In this series, I show how to build a face recognition system that can nicely integrate with Home Assistant for presence detection.</p>
<p>Given the large size of this project, this will span multiple posts. This first post lays down the groundwork with, at the end, a functioning face detection system.</p>
<img class="lazyload" src="/assets/images/facerec/detection-star-trek.jpg" caption="Can we build a face recognition system that is free and runs completely local?">
<h2 id="requirements-for-local-face-recognition">Requirements for local face recognition</h2>
<p>Before setting up this system, I made a list of requirements that should be met. There are many possible approaches/solutions, so a list of requirements can guide the design choices.</p>
<dl>
<dt>✔️ No cloud needed.</dt>
<dd>Any solution should not require an internet connection to a cloud-based service.</dd>
<dt>✔️ Local processing.</dt>
<dd>All image processing should be done locally on a machine that I control. I do not want to upload images to a server or service out of my control.</dd>
<dt>✔️ Easy to disable.</dt>
<dd>A recognition system should be easy to disable. For example, I want to automatically disable any processing when the house is in &ldquo;guest mode&rdquo; (i.e., guests are present).</dd>
<dt>✔️ Pre-trained models.</dt>
<dd>I don&rsquo;t want to train a face detection model from scratch. To do that would require a lot of time and a well-curated training set. Of course, for the face recognition part, we will need to have a training phase. But, face detection should be doable with a pre-trained model.</dd>
<dt>✔️ Private training set.</dt>
<dd>Any images that are needed to train the face recognition system (e.g., my face), should also be stored locally. I don&rsquo;t want to upload a training set to a cloud service.</dd>
<dt>✔️ Open source.</dt>
<dd>The source code should be open so that it can be inspected. This also increases the chances that the project will still work in the future.</dd>
<dt>✔️ Free / no-subscription needed.</dt>
<dd>While there is nothing against paying for software, for this experiment, I want something that can be used without a subscription. To make sure that the system will work in the future, I especially don&rsquo;t want to be dependent on an external service. There are many face recognition systems available that are free to use, but companies can decide to end support or introduce new pricing models.</dd>
</dl>
<p>Luckily, meeting all requirements is possible! But, it does require some programming to set everything up.</p>
<h2 id="choosing-a-recognition-system">Choosing a recognition system</h2>
<p>During my search for face recognition systems, I found several systems that could be used. Some of these are cloud-based, others are local. A non-complete overview can be found below. I wasn&rsquo;t able to test all methods myself, so I used information from the docs. Found a error in the overview above? <a href="#comment-form">Let me know!</a> The <code>requirements score</code> is based on how many of the requirements the solution meets.</p>
<div style="max-width: 100%; overflow: auto">
<table>
<thead>
  <tr>
    <th>Solution</th>
    <th>Examples</th>
    <th>Internet needed?</th>
    <th>Pricing / subscription</th>
    <th>Training data</th>
    <th>Processing (inference)</th>
    <th>Requirements score</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td>Cloud solution</td>
    <td><ul><li><a href="https://cloud.google.com/vision/docs/detecting-faces">Google Cloud</a></li>
            <li><a href="https://azure.microsoft.com/en-us/services/cognitive-services/face/">Microsoft Face</a></li> </ul></td>
    <td>Yes</td>
    <td>Pay-per-use with free starter plans</td>
    <td>None<sup>1</sup></td>
    <td>Test images are uploaded</td>
    <td>2/7</td>
  </tr>
  <tr>
    <td>External service, local processing</td>
    <td><ul><li><a href="https://deepstack.cc/">Deepstack</a></li>
            <li><a href="https://machinebox.io/docs/facebox">Facebox</a></li></ul>
    </td>
    <td>No<sup>3</sup></td>
    <td>Free with limits / unclear</td>
    <td>None<sup>1</sup></td>
    <td>Test images are processed locally, but often closed-source</td>
    <td>5/7</td>
  </tr>
  <tr>
    <td>Local, self-trained</td>
    <td><ul><li>Any deep learning framework</li></ul></td>
    <td>No</td>
    <td>Free<sup>3</sup></td>
    <td>Database with faces</td>
    <td>Local, full control</td>
    <td>6/7</td>
  </tr>
  <tr>
    <td>Pre-trained, local processing</td>
    <td><ul><li><a href="https://github.com/ageitgey/face_recognition">face_recognition</a></li>
            <li><a href="https://github.com/justadudewhohacks/face-api.js">face-api.js</a></li></ul></td>
    <td>No</td>
    <td>Free<sup>3</sup></td>
    <td>None<sup>1</sup></td>
    <td>Local, full control</td>
    <td>7/7</td>
  </tr>
</tbody>
</table>
</div>
<small>
1] Some training data is needed to recognize faces (instead of plain detection).<br>
2] Some of these systems have keys which mean they have to phone-home at some point.<br>
3] Free as in "no money." You still have to pay with your own development time of course.<br>
</small>
<p>Based on this overview, I chose to go for one of the libraries that has support for pre-trained models and supports local processing: <a href="https://github.com/ageitgey/face_recognition">face_recognition</a> and <a href="https://github.com/justadudewhohacks/face-api.js">face-api.js</a>. <code>face_recognition</code> is written in Python and uses the <a href="http://dlib.net/">dlib</a> library as a backend. <code>face-api.js</code> runs on Javascript/Node using <a href="https://www.tensorflow.org/js">TensorFlow.js</a> as the backend. Both projects have support for face detection and recognition, with pre-trained models.</p>
<p>In the end, after a lot of testing, I chose <code>face-api.js</code>. I always wanted to experiment more with TensorFlow.js, and, given that I use python during my day-job, JS would be a nice change of scenery. If you are more interested in a pure-python setup, make sure to check out <a href="https://github.com/ageitgey/face_recognition">face_recognition</a>. Note, that even though we will set everything up in JS, the actual processing is done in C++ using the Tensorflow bindings.</p>
<h2 id="hardware-requirements">Hardware requirements</h2>
<p>At the minimum the face recognition system should have one camera and something that can run the algorithm. In my case I use the following:</p>
<ul>
<li><a href="https://amzn.to/3ci2ClP" rel="nofollow">Raspberry Pi 3B+</a> connected to a <a href="https://amzn.to/36RtQ1X" rel="nofollow">Raspberry Pi Camera</a>. This is used as the main camera system. I use <a href="https://github.com/ccrisan/motioneyeos">motionEyeOS</a> as the OS on my Pi.</li>
<li>An <a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel NUC8i5BEK</a> that will run the face recognition system (and also runs Home Assistant).</li>
</ul>
<p>Of course, any other combination can be used. Just make sure that the camera has an URL where you can retrieve an image snapshot. Ideally, the camera also has built-in motion detection so that you can trigger face recognition at the right time. The compute unit that runs the algorithm should be strong enough, or you can expect a delay in processing. I haven&rsquo;t tried this on a Raspberry Pi, <a href="#add-comment">let me know</a> how it performs if you try it out!</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/pi-camera-attached.jpg"
         alt="For this project I use a Raspbbery Pi in combination with a Pi Camera. Image from https://projects.raspberrypi.org/en/projects/getting-started-with-picamera"/> <figcaption>
            <p>For this project I use a Raspbbery Pi in combination with a Pi Camera. Image from <a href="https://projects.raspberrypi.org/en/projects/getting-started-with-picamera">https://projects.raspberrypi.org/en/projects/getting-started-with-picamera</a></p>
        </figcaption>
</figure>

<h2 id="part-1-building-a-face-detection-system">Part 1: Building a face detection system</h2>
<p>In this first part of the series, we set everything up for simple face detection. The overview of the application is shown in the figure below. A camera, in my case a <a href="https://amzn.to/36RtQ1X" rel="nofollow">Raspberry Pi Camera</a>, sends a request to the application when it detects motion. This is done using a simple HTTP GET request. You could also trigger this from Home Assistant using an automation triggered by a motion sensor, for example.</p>
<p>Upon receiving the webhook, the application retrieves a snapshot from the camera, e.g., the last recorded frame. This frame is processed using the detection algorithm. If a face is detected, we can send a request to Home Assistant that something was detected. Additionally, we save the snapshot with a bounding box around the face. This image can be viewed by us (the user), for example, through the Home Assistant dashboard.</p>
<p><figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/face-detection-setup.svg"
         alt="Overview of the face detection system."/> <figcaption>
            <p>Overview of the face detection system.</p>
        </figcaption>
</figure>

<br></p>
<p>The full source code for part 1 can be found on the <a href="https://github.com/wouterbulten/ha-facerec-js/tree/v0.1.0">ha-facerec-js GitHub repository</a> (v0.1.0).</p>
<h3 id="setting-up-the-webhook">Setting up the webhook</h3>
<p>To listen for a request from the camera (the webhook), we set up a simple <a href="">express</a> webserver. The server listens for requests on the <code>/motion-detected</code> endpoint.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">express</span> <span class="nx">from</span> <span class="s1">&#39;express&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Initialize express
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Start express on the defined port
</span></span></span><span class="line"><span class="cl"><span class="c1">// Start express on the defined port
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server running on port </span><span class="si">${</span><span class="nx">PORT</span><span class="si">}</span><span class="sb">`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;/motion-detected&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Send a OK back to the camera
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Do something here
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>To trigger this route, go to your MotionEyeOS admin panel and enable motion detection. Add the URL of your running <code>express</code> instance under &ldquo;webhook URL.&rdquo; Most probably, this is <code>&lt;ip of your machine&gt;:&lt;port&gt;</code>.</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/webhook-camera.png"
         alt="Setup the webhook in your camera&amp;rsquo;s control panel."/> <figcaption>
            <p>Setup the webhook in your camera&rsquo;s control panel.</p>
        </figcaption>
</figure>

<h3 id="running-face-detection">Running face detection</h3>
<p>After motion has been detected, we can start looking for faces. To do this, we request the last frame of the camera. Using <a href="https://github.com/Automattic/node-canvas">node-canvas</a> we can make use of the Canvas functionality within Node.js (normally only possible within a browser). Loading the image becomes as easy as calling the URL of the camera (here defined using the <code>CAMERA_URL</code> env variable):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">canvas</span> <span class="nx">from</span> <span class="s1">&#39;canvas&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// From inside an async function:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span></code></pre></div><p>With the image loaded, we can pass this to the <code>face-api.js</code> library to actually detect faces. For this, I make use of the SSD MobileNetV1 network included with the library. This network has good performance in detecting faces but is a bit slower than other alternatives. Luckily we can speed this up later; see the next section for more info.</p>
<p>The network weights are loaded from disk, and all processing is done locally on the device. The weights of these networks are stored in the Github repository for you to download.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Load network from disk
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">ssdMobilenetv1</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Detect faces
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">detections</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">SsdMobilenetv1Options</span><span class="p">({</span> <span class="nx">minConfidence</span><span class="o">:</span> <span class="mf">0.5</span> <span class="p">}));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Create a new image with a bounding box around each face
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">drawDetections</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span> <span class="nx">detections</span><span class="p">);</span>
</span></span></code></pre></div><p>With faces detected, we can perform our actions. For this example, I save the snapshot with the detected faces to disk:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;public/last-detection.jpg&#39;</span><span class="p">,</span> <span class="nx">out</span><span class="p">.</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span></code></pre></div><p>The exported image can later be retrieved using a new route in Express. You could, for example, show the last detected face in your Home Assistant dashboard using a camera setup.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Load network from disk
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">ssdMobilenetv1</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Detect faces
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">detections</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">SsdMobilenetv1Options</span><span class="p">({</span> <span class="nx">minConfidence</span><span class="o">:</span> <span class="mf">0.5</span> <span class="p">}));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Create a new image with a bounding box around each face
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">drawDetections</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span> <span class="nx">detections</span><span class="p">);</span>
</span></span></code></pre></div><p>With faces detected, we can perform our actions. For this example, I save the snapshot with the detected faces to disk:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;public/last-detection.jpg&#39;</span><span class="p">,</span> <span class="nx">out</span><span class="p">.</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span></code></pre></div><p>The exported image can later be retrieved using a new route in Express. You could, for example, show the last detected face in your Home Assistant dashboard using a camera setup.</p>
<h3 id="speeding-up-recognition">Speeding up recognition</h3>
<p>The MobileNetV1 network is quite slow when we run it in Javascript. Luckily, there is a special package that offers Node bindings for the Tensorflow C++ backend. Using this package drastically speeds up the detection. Using these bindings is as simple as loading them in the script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Load TF bindings to speed up processing
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_BINDINGS</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;Loading tfjs-node bindings.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;@tensorflow/tfjs-node&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;tfjs-node bindings not loaded, speed will be reduced.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Note: The bindings don&rsquo;t always work out of the box in my experience. If you encounter errors, first try to run everything without the bindings loaded.</p>
<h3 id="tying-everything-together">Tying everything together</h3>
<p>Combining all snippets from above results in a simple web server that can detect faces on command. I run this server inside a docker container as part of my <a href="https://www.wouterbulten.nl/posts/home-automation-setup-docker-compose/">home automation docker setup</a>. You can find the <a href="https://github.com/wouterbulten/ha-facerec-js/blob/v0.1.0/Dockerfile">Dockerfile</a> on the <a href="https://github.com/wouterbulten/ha-facerec-js/tree/v0.1.0">GitHub repository</a>. The full source code of the script is as follows:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">express</span> <span class="nx">from</span> <span class="s1">&#39;express&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">faceapi</span> <span class="nx">from</span> <span class="s2">&#34;face-api.js&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">canvas</span> <span class="nx">from</span> <span class="s1">&#39;canvas&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="nx">as</span> <span class="nx">path</span> <span class="nx">from</span> <span class="s1">&#39;path&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">fs</span> <span class="nx">from</span> <span class="s1">&#39;fs&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Load TF bindings to speed up processing
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">TF_BINDINGS</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;Loading tfjs-node bindings.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;@tensorflow/tfjs-node&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">&#34;tfjs-node bindings not loaded, speed will be reduced.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Inject node-canvas to the faceapi lib
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="p">{</span> <span class="nx">Canvas</span><span class="p">,</span> <span class="nx">Image</span><span class="p">,</span> <span class="nx">ImageData</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">faceapi</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">monkeyPatch</span><span class="p">({</span> <span class="nx">Canvas</span><span class="p">,</span> <span class="nx">Image</span><span class="p">,</span> <span class="nx">ImageData</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Initialize express
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PORT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Start express on the defined port
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`Server running on port </span><span class="si">${</span><span class="nx">PORT</span><span class="si">}</span><span class="sb">`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Webhook
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;/motion-detected&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">end</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load network
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">nets</span><span class="p">.</span><span class="nx">ssdMobilenetv1</span><span class="p">.</span><span class="nx">loadFromDisk</span><span class="p">(</span><span class="s1">&#39;weights&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Request image from the camera
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">img</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">loadImage</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">CAMERA_URL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Detect faces
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">detections</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">detectAllFaces</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="k">new</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">SsdMobilenetv1Options</span><span class="p">({</span> <span class="nx">minConfidence</span><span class="o">:</span> <span class="mf">0.5</span> <span class="p">}));</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="nx">faceapi</span><span class="p">.</span><span class="nx">createCanvasFromMedia</span><span class="p">(</span><span class="nx">img</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">faceapi</span><span class="p">.</span><span class="nx">draw</span><span class="p">.</span><span class="nx">drawDetections</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span> <span class="nx">detections</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Write detections to public folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="s1">&#39;public/last-detection.jpg&#39;</span><span class="p">,</span> <span class="nx">out</span><span class="p">.</span><span class="nx">toBuffer</span><span class="p">(</span><span class="s1">&#39;image/jpeg&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Detection saved.&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Static route, give access to everything in the public folder
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="kr">static</span><span class="p">(</span><span class="s1">&#39;public&#39;</span><span class="p">));</span>
</span></span></code></pre></div><p>By running the server, setting <code>CAMERA_URL</code> to the snapshot of the camera, we can now detect faces. As an example, I ran the code on an <a href="https://en.wikipedia.org/wiki/Crowd#/media/File:July_4_crowd_at_Vienna_Metro_station.jpg">image of crowd</a> from Wikipedia. The result is shown below. The algorithm is quite capable of detecting almost all faces in even a single frame.</p>
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/facerec/detection-crowd.jpg"
         alt="Face detection algorithm applied on image of Wikipedia (CC BY-SA)."/> <figcaption>
            <p>Face detection algorithm applied on image of Wikipedia (CC BY-SA).</p>
        </figcaption>
</figure>

<p>Of course, were are not there yet. Detection is nice, but for presence, we also need to recognize faces. There are also some other things left to improve. For example:</p>
<ol>
<li>The current setup only <strong>detects</strong> faces, we still need to perform the <strong>recognition</strong>.</li>
<li>The server only accepts a single camera, it would be nice to support multiple cameras. Of course, you can run an instance for each camera, but maybe it&rsquo;s nice to combine them.</li>
<li>When the face detection is running, the server cannot process other requests. A queue system could help with that.</li>
<li>There is no security: I wouldn&rsquo;t advise to run this on an unprotected network! We could add some basic authentication to protect the routes.</li>
</ol>
<p>All of this will be covered in the next posts. To continue you can read <a href="https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/">part 2</a>. For now, feel free to let me know what you thought in the <a href="#comment-form">comments</a>. You can find the latest code in the <a href="https://github.com/wouterbulten/ha-facerec-js">Github repository</a>, the code for this post is tagged <a href="https://github.com/wouterbulten/ha-facerec-js/tree/v0.1.0">v0.1.0</a>.</p>
<p><strong><a href="https://www.wouterbulten.nl/posts/local-face-recognition-part-2-learning-faces/">Continue to part 2 →</a></strong></p>
<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setting up Home Automation with Docker Compose: Home Assistant, Node-RED, and more</title>
      <link>https://www.wouterbulten.nl/posts/home-automation-setup-docker-compose/</link>
      <pubDate>Thu, 17 Oct 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/home-automation-setup-docker-compose/</guid>
      <description>Docker-compose is a convenient way to setup and configure your home automation system. In this post, I describe my setup and the configuration needed to run the containers.</description>
      <content:encoded><![CDATA[<p>Recently, I migrated my whole home automation setup from a group of Raspberry Pis to an Intel NUC; specifically the <a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel NUC8i5BEK</a>. Due to an increasingly larger set of devices, a single Pi for running Home Assistant and Node-RED did not cut it anymore. See my post on <a href="/posts/home-assistant-smart-home-hardware-setup/">my hardware setup</a> for more information.</p>
<p>With the introduction of the Intel Nuc to my setup, the way I configured my system also changed. Previously, I used Hass.io for managing all services. With the new NUC, I wanted to be more in control. I ended up adopting Docker and Docker Compose for my setup.</p>
<p>During the migration of my setup, it took quite some time to figure out the configurations for all services. In this blog post, I share the configurations I eventually came up with, and which now power my system. I now have running containers for Home Assistant, Node-RED, AppDaemon, MariaDB, VS Code, and Deconz.</p>
<p>Any questions about these setups? Feel free to add a question in the comments. Tips to improve it, those are very welcome too!</p>
<figure style="width: 100%; max-width: 400px; margin: 0 auto;">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 610 145"><defs><clipPath id="a"><path d="M76 2v46H54v23H35.58c-.08.67-.14 1.33-.2 2-1.16 12.53 1.03 24.09 6.06 33.97l1.69 3.03a46.18 46.18 0 003.43 5.19c1.25 1.66 1.69 2.58 2.47 3.69C62.32 133.8 82.13 141 105 141c50.65 0 93.63-22.44 112.66-72.84 13.5 1.38 26.44-2.08 32.34-13.6-9.4-5.42-21.48-3.68-28.44-.19L240 2l-72 46h-23V2z"/></clipPath></defs><path d="M467.82 113.24l-.04-25.56-.13-45.65M493.5 61.7l-25.72 25.98 25.85 25.65M321.3 88.03a26.1 26.1 0 00-4.7-15.62c-4.65-6.53-12.18-11-20.44-11.09-.55 0-1.1 0-1.65.04-3.26.2-6.43.66-9.22 1.82-18.4 7.66-21.66 33.21-6.14 44.77 13.45 10.01 33 5.93 39.78-9.35a28.07 28.07 0 002.37-10.58v-46.2m124.15 22.55c-3.81-2.46-8.26-3.35-12.65-3.34-.48 0-.97.02-1.4.04-12.71.54-24.5 9.53-24.5 26.95 0 20.6 22.09 31.72 38.59 22m74.13-2.24l39.06-33.47s-.95-1.8-1.57-2.62c-5.25-7.06-13.28-10.58-21.75-10.58-20.9 0-36.58 27.04-15.74 46.67a18.71 18.71 0 005.63 3.35c7.65 3.18 18.12 2.92 23.78-1.27m-185-48.67c-.6.02-1.18.08-1.78.09-15.85.29-27.28 16.76-23.82 32.03 3.37 14.88 19.63 23.7 33.68 18.83 16.36-4.29 23.03-24.89 14.05-38.8-4.91-7.61-13.24-12.31-22.13-12.15zm240.73.51c-6.42.02-12.68 2.27-17.31 6.6-7.3 6.03-8.9 13.65-8.8 23.28l.21 21.87" fill="none" stroke="#394d54" stroke-width="10.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M147.49 45.73h22.86v23.38h11.57c5.33 0 10.83-.95 15.88-2.67a40.69 40.69 0 007.73-3.49c-3.23-4.21-4.88-9.53-5.36-14.78-.66-7.13.78-16.42 5.6-22l2.4-2.78 2.87 2.3c7.21 5.8 13.28 13.89 14.35 23.12 8.68-2.56 18.87-1.95 26.53 2.47l3.14 1.8-1.65 3.23c-6.48 12.64-20 16.55-33.24 15.86-19.8 49.3-62.9 72.65-115.16 72.65-27 0-51.77-10.1-65.87-34.05-.83-1.48-1.54-3.04-2.29-4.57-4.77-10.54-6.35-22.1-5.28-33.64l.33-3.45h19.55V45.73h22.87V22.87h45.73V0h27.44v45.73" fill="#394d54"/><g clip-path="url(#a)"><g id="d"><g transform="translate(0 -22.87)" id="c"><path d="M123.86 3.81h19.82v19.82h-19.82z" fill="#00acd3"/><path d="M123.86 26.68h19.82v19.81h-19.82z" fill="#20c2ef"/><path d="M126.3 21.98V5.46m2.96 16.52V5.46m3 16.52V5.46m3 16.52V5.46m3.01 16.52V5.46m2.97 16.52V5.46" id="b" stroke="#394d54" stroke-width="1.56"/><use transform="translate(0 22.87)" xlink:href="#b"/></g><use transform="matrix(1 0 0 -1 22.87 4.57)" xlink:href="#c"/></g><use transform="translate(-91.46 45.73)" xlink:href="#d"/><use transform="translate(-45.73 45.73)" xlink:href="#d"/><use transform="translate(0 45.73)" xlink:href="#d"/></g><path d="M221.57 54.38c1.53-11.92-7.38-21.28-12.91-25.72-6.38 7.37-7.37 26.68 2.63 34.8-5.58 4.96-17.34 9.46-29.37 9.46H34C32.83 85.48 34 146 34 146h217l-.99-91.42c-9.4-5.43-21.48-3.7-28.44-.2" clip-path="url(#a)" fill="#17b5eb"/><path d="M34 89v57h217V89" clip-path="url(#a)" fill-opacity=".17"/><path d="M111.24 140.89c-13.54-6.43-20.97-15.16-25.1-24.7L45 118l21 28 45.24-5.11" clip-path="url(#a)" fill="#d4edf1"/><path d="M222.5 53.94v.03c-20.86 26.89-50.78 50.38-82.9 62.72-28.66 11-53.64 11.06-70.88 2.22-1.86-1.05-3.68-2.22-5.5-3.32-12.64-8.83-19.76-23.44-19.16-42.68H34V146h217V50h-25z" clip-path="url(#a)" fill-opacity=".09"/><path d="M45.63 117.03c14.16.78 29.28.92 42.46-3.22" fill="none" stroke="#394d54" stroke-width="3.4" stroke-linecap="round"/><path d="M102.17 106.96a5.47 5.47 0 11-10.93 0 5.47 5.47 0 0110.93 0z" fill="#d4edf1"/><path d="M98.12 103.3a1.6 1.6 0 102.2 2.16 3.91 3.91 0 11-2.2-2.15zM0 90.16h254.33c-5.54-1.4-17.52-3.3-15.55-10.56-10.07 11.65-34.35 8.18-40.48 2.43-6.82 9.9-46.55 6.14-49.32-1.57-8.56 10.04-35.07 10.04-43.63 0-2.77 7.7-42.5 11.47-49.32 1.57-6.13 5.75-30.41 9.22-40.48-2.43C17.52 86.86 5.54 88.76 0 90.16" fill="#394d54"/></svg>
</figure><br>
<p><strong>Table of contents</strong></p>
<ul>
<li><a href="#env">Use of the .env file</a></li>
<li><a href="#volumes">Note on volumes</a></li>
<li><a href="#home-assistant">Docker compose for <strong>Home Assistant</strong></a></li>
<li><a href="#mariadb">Docker compose for <strong>MariaDB</strong></a></li>
<li><a href="#deconz">Docker compose for <strong>Conbee / Deconz</strong></a></li>
<li><a href="#nodered">Docker compose for <strong>Node-RED</strong></a></li>
<li><a href="#appdaemon">Docker compose for <strong>AppDaemon</strong></a></li>
<li><a href="#vscode">Docker compose for <strong>VS Code</strong></a></li>
<li><a href="#overview">docker-compose.yaml overview</a></li>
</ul>
<p><strong>Updates</strong></p>
<p><em>14-05-2020:</em> Updated VS Code setup.</p>
<p><a name="env"></a></p>
<h2 id="use-of-the-env-file">Use of the .env file</h2>
<p>Al my configurations are pushed to Github as a way of back up. It&rsquo;s not really desirable to commit secrets (passwords, tokens, etc.), so you don&rsquo;t want these in your config file. Luckily, docker-compose can read these from an <code>.env</code> file. In all the examples below, I assume that you have a <code>.env</code> file with the required variables.</p>
<p><a name="volumes"></a></p>
<h2 id="note-on-volumes">Note on volumes</h2>
<p>Most of the docker containers use volumes to store persistent data. Without these volumes, all data and configuration would be lost if you restart one of the containers. Make sure to check the volumes config of each configuration block and update accordingly to your liking.</p>
<p>I store all my configuration (e.g., al volumes) in a central directory like <code>~/homeautomation-config</code>. This directory can then be easily backed up on a remote device.</p>
<p><a name="home-assistant"></a></p>
<h2 id="docker-compose-for-home-assistant">Docker compose for Home Assistant</h2>
<figure style="max-width: 200px; text-align: left;">
<figure>
    <img loading="lazy" src="/assets/images/ha/logos/ha-logo.svg"/> 
</figure>

</figure><br>
<p>The Home Assistant docker is the main container of my smart home setup. Most configuration is done within HA itself, so it&rsquo;s a matter of spinning up the container. I use MariaDB for storing all event data, so that container needs to start first. I also make sure that my Zigbee hub is running before starting HA.</p>
<dl>
  <dt>Website:</dt>
  <dd><a href="https://www.home-assistant.io/">Home Assistant</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>LOCAL_USER</code>: (Optional) Map the docker user to your user id. This ensures that files can be edited without root access. Especially useful in combination with the VSCode server image.</li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># HomeAssistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">homeassistant</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">home-assistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant/home-assistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Local path where your home assistant config will be stored</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;local config path&gt;:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># MariaDB is optional (only if you would like to use a different database for HA).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mariadb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Deconz is optional (only if you use the deconz Zigbee hub).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span></code></pre></div><p><a name="mariadb"></a></p>
<h2 id="docker-compose-for-database-using-mariadb">Docker compose for Database using MariaDB</h2>
<figure style="text-align: left; max-width: 600px">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 414.62 129.41"><path d="M330.93 67.33v31.22H327V93.2c-3.56 4.27-7.64 6.4-12.23 6.4-4.58 0-8.46-1.62-11.64-4.87a16.4 16.4 0 01-4.7-11.7c0-4.55 1.59-8.43 4.76-11.64a15.38 15.38 0 0111.4-4.88c5.1 0 9.24 2.17 12.41 6.52v-5.7h3.94m-3.7 15.76c0-3.57-1.2-6.57-3.59-9a11.8 11.8 0 00-8.82-3.7c-3.45 0-6.37 1.27-8.76 3.82a12.51 12.51 0 00-3.58 8.93 12.6 12.6 0 003.64 9 11.7 11.7 0 008.7 3.76c3.41 0 6.33-1.21 8.76-3.64a12.44 12.44 0 003.65-9.17m12.67 15.46V56.34h8.76c6 0 10.39.45 13.17 1.35 2.82.86 5.25 2.25 7.29 4.17a18.8 18.8 0 014.7 6.94 25.87 25.87 0 011.65 9.64c0 3.65-.86 7.1-2.59 10.35-1.68 3.21-4 5.64-6.94 7.29-2.9 1.64-7.09 2.47-12.58 2.47h-13.46m4.06-4h4.88c5.1 0 8.78-.25 11.05-.76a13.55 13.55 0 006.05-3 15.04 15.04 0 003.94-5.53c.9-2.15 1.36-4.62 1.36-7.4 0-2.79-.53-5.36-1.6-7.7a14.85 14.85 0 00-11.22-8.89c-2.59-.54-6.4-.82-11.46-.82h-3v34.1m-58.49-35.92a3.32 3.32 0 013.3-3.35c.93 0 1.72.33 2.34 1 .67.62 1 1.4 1 2.35 0 .9-.33 1.68-1 2.35-.62.63-1.4.94-2.35.94a3.2 3.2 0 01-2.35-1 3.25 3.25 0 01-.94-2.3m1.3 8.7h4.05v31.23h-4.06V67.33M173.66 98.55l6-42.21h.65l17.16 34.62 17.05-34.62h.65l6.06 42.2h-4.18l-4.11-30.21L198 98.55h-1.05l-15.11-30.46-4.12 30.46h-4.06m86.17-31.22v31.22h-3.94V93.2c-3.56 4.27-7.64 6.4-12.23 6.4-4.58 0-8.46-1.62-11.64-4.87a16.4 16.4 0 01-4.7-11.7c0-4.55 1.59-8.43 4.76-11.64a15.38 15.38 0 0111.4-4.88c5.1 0 9.24 2.17 12.41 6.52v-5.7h3.94m-3.7 15.76c0-3.57-1.2-6.57-3.59-9a11.8 11.8 0 00-8.82-3.7c-3.45 0-6.37 1.27-8.76 3.82a12.51 12.51 0 00-3.58 8.93 12.6 12.6 0 003.64 9 11.7 11.7 0 008.7 3.76c3.41 0 6.33-1.21 8.76-3.64a12.44 12.44 0 003.65-9.17m10.5-15.76h4.12v4.53c1.1-1.85 2.16-3.2 3.17-4.06a5.37 5.37 0 013.47-1.3c1.3 0 2.59.36 3.88 1.06l-2.11 3.41a3.5 3.5 0 00-1.83-.52c-1.21 0-2.37.62-3.46 1.88a12.07 12.07 0 00-2.36 4.88c-.5 1.96-.76 5.54-.76 10.76v10.58h-4.12V67.33m117.65-10.99h8.35c4.43 0 7.78.9 10.05 2.7 2.32 1.76 3.47 4.31 3.47 7.64a9.76 9.76 0 01-5.4 8.82c2.9.94 5.09 2.35 6.58 4.23a10.3 10.3 0 012.3 6.7c0 3.5-1.28 6.4-3.83 8.7-2.55 2.28-5.84 3.42-9.88 3.42h-11.64V56.34m4.18 4.11v13.52h2.4c3.7 0 6.43-.59 8.24-1.76 1.8-1.18 2.7-3.02 2.7-5.53 0-4.15-2.82-6.23-8.46-6.23h-4.88m0 17.76v16.22h5.23c3.02 0 5.23-.3 6.64-.88a8.33 8.33 0 003.53-2.82c.9-1.3 1.35-2.6 1.35-3.94 0-1.33-.25-2.5-.76-3.53a7.85 7.85 0 00-2.3-2.7 9.91 9.91 0 00-3.58-1.77c-1.37-.39-3.96-.58-7.76-.58h-2.35" font-size="58.79" font-weight="400" fill="#a57242" font-family="STHeiti"/><path d="M180.87 5c-2.78.1-1.9.9-7.9 2.37-6.07 1.5-13.47 1.03-20 3.77-19.5 8.18-23.4 36.12-41.13 46.13-13.24 7.48-26.6 8.08-38.62 11.84-7.9 2.48-16.54 7.56-23.69 13.72-5.55 4.79-5.7 9-11.5 15-6.2 6.42-24.67.1-33.03 9.94 2.7 2.72 3.87 3.49 9.19 2.78-1.1 2.08-7.59 3.84-6.32 6.9 1.34 3.23 17 5.42 31.25-3.18 6.64-4 11.92-9.78 22.25-11.16 13.37-1.78 28.78 1.14 44.25 3.38-2.3 6.84-6.9 11.39-10.59 16.84-1.14 1.23 2.3 1.37 6.22.63 7.06-1.75 12.14-3.15 17.47-6.25 6.54-3.81 7.53-13.58 15.56-15.7 4.47 6.88 16.64 8.5 24.19 3-6.63-1.87-8.46-15.97-6.22-22.18 2.12-5.88 4.2-15.29 6.34-23.06 2.3-8.35 3.14-18.87 5.9-23.13 4.18-6.4 8.79-8.6 12.79-12.2 4-3.6 7.66-7.12 7.54-15.38-.04-2.66-1.42-4.14-3.95-4.06z" fill="#002b64" fill-rule="evenodd"/><path d="M10.75 116.87c10.15 1.45 16.32 0 24.46-3.54 6.93-3 13.63-9.2 21.81-11.83 12.02-3.85 25.2 0 38.05.78 3.13.19 6.24.19 9.31-.15 4.79-2.94 4.69-13.94 9.35-14.95-.14 15.44-6.47 24.7-13.09 33.65 13.95-2.47 22.3-10.54 27.94-21.31a78.73 78.73 0 004.47-10.47c2 1.53.86 6.2 1.86 8.72 9.61-5.35 15.12-17.57 18.76-29.92 4.22-14.3 5.94-28.78 8.66-33.02 2.66-4.13 6.79-6.67 10.56-9.32 4.28-3 8.1-6.13 8.76-11.86-4.52-.42-5.56-1.47-6.23-3.74-2.26 1.27-4.34 1.55-6.7 1.62-2.03.06-4.27-.03-7 .25-22.63 2.32-25.5 27.26-40.01 41.4a34.02 34.02 0 01-3.4 2.89c-5.08 3.78-11.31 6.49-17.04 8.68-9.28 3.54-18.1 3.8-26.8 6.85a80.24 80.24 0 00-18.12 9.1c-1.32.9-2.55 1.81-3.69 2.74-3.08 2.52-5.1 5.31-7.05 8.19-2.02 2.96-3.97 6.01-6.94 8.92-4.8 4.72-22.77 1.38-29.1 5.76a5.16 5.16 0 00-1.64 1.77c3.45 1.57 5.76.6 9.73 1.05.52 3.76-8.2 6-6.9 7.74z" fill="#c49a6c"/><path d="M147.84 89.73c.27 4.32 2.78 12.9 4.99 14.98-4.34 1.05-11.81-.69-13.73-3.75.99-4.42 6.11-8.46 8.74-11.23z" clip-rule="evenodd" fill="#c49a6c" fill-rule="evenodd"/><path d="M154.2 21.14c3.2 2.78 9.92.54 8.71-4.99-4.97-.41-7.86 1.28-8.72 4.99z" clip-rule="evenodd" fill="#002b64" fill-rule="evenodd"/><path d="M176.52 14.67c-.85 1.79-2.48 4.09-2.48 8.64 0 .78-.6 1.32-.6.11.04-4.44 1.22-6.37 2.47-8.9.58-1.03.93-.6.61.15z" fill="#002b64"/><path d="M175.66 14c-1 1.7-3.42 4.81-3.82 9.34-.07.78-.7 1.26-.61.06.44-4.42 2.37-7.2 3.84-9.6.66-.98.98-.52.6.2zm-.78-.9c-1.14 1.62-4.87 5.35-5.65 9.84-.14.76-.8 1.2-.61 0 .8-4.37 4.02-7.8 5.68-10.08.75-.92 1.02-.44.58.24zm-.7-1c-1.36 1.45-5.8 6.2-7.2 10.54-.24.74-.97 1.07-.6-.08 1.4-4.22 5.3-8.76 7.26-10.78.87-.8 1.08-.29.54.33z" fill="#002b64"/></svg>
</figure>
<p>I use MariaDB as my main database for Home Assistant. HA gets its own user account to access the db.</p>
<dl>
  <dt>Website:</dt>
  <dd><a href="https://mariadb.org/">MariaDB</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>MYSQL_ROOT_PASSWORD</code>: Root password for the service.</li>
      <li><code>HA_MYSQL_PASSWORD</code>: Password that home assistant will use to connect with the db.</li>
      <li><code>LOCAL_USER</code>: (Optional) Map the docker user to your user id. This ensures that files can be edited without root access.</li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># MariaDb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">mariadb</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mariadb/server:10.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">mariadb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MYSQL_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${MYSQL_ROOT_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MYSQL_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">ha_db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MYSQL_USER</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MYSQL_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${HA_MYSQL_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Local path where the database will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;local db path&gt;:/var/lib/mysql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;3306:3306&#34;</span><span class="w">
</span></span></span></code></pre></div><p><a name="deconz"></a></p>
<h2 id="docker-compose-for-zigbee-hub-using-deconz">Docker compose for Zigbee hub using Deconz</h2>
<figure style="text-align: left; max-width: 300px;"><figure class="lazyload">
    <img loading="lazy" src="/assets/images/ha/hardware/conbee2.jpg"/> 
</figure>
</figure>
<p>To control my devices, I use the <a href="https://amzn.to/2po4t61" rel="nofollow">ConBee</a> USB ZigBee hub from Dresden Elektroniks. It&rsquo;s a great device with a vast list of compatible devices. Luckily, there is a Docker image that can be used to run their software. It even has support for viewing the mesh network through VNC.</p>
<dl>
  <dt>Hardware:</dt>
  <dd><a href="https://amzn.to/2po4t61" rel="nofollow">Conbee 2</a> (I'm using the Conbee 1, but the v2 is slightly improved and should be bought from now on.)</dd>
  <dt>Website:</dt>
  <dd><a href="https://phoscon.de/en/conbee/install#docker">Conbee 1</a> or <a href="https://phoscon.de/en/conbee2/install#docker">Conbee 2</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>VNC_PASSWORD</code>: Password to use when connecting to the VNC server (for viewing the mesh network).</li>
    </ul>
  </dd>
  <dt>Additional notes:</dt>
  <dd>
    <ul>
      <li>You need to map the right USB device to the container. For the Conbee 2 the device mapping is slightly different. You can use `/dev/ttyACM0` instead of `dev/ttyUSB0`.</li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">deconz</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">marthoc/deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># You can access Deconz at this port</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DECONZ_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DECONZ_WS_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8088</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Set VNC_MODE to 0 to disable it completely</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DECONZ_VNC_MODE</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DECONZ_VNC_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">5900</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DECONZ_VNC_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${VNC_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/etc/timezone:/etc/timezone:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Replace &lt;local path&gt; with a path where all deconz config will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;local path&gt;:/root/.local/share/dresden-elektronik/deCONZ</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;/dev/ttyUSB0:/dev/ttyUSB0&#34;</span><span class="w">
</span></span></span></code></pre></div><p><a name="nodered"></a></p>
<h2 id="docker-compose-for-node-red">Docker compose for Node-RED</h2>
<figure style="max-width: 200px; text-align: left;">
<figure class="lazyload">
    <img loading="lazy" src="/assets/images/ha/logos/node-red-logo.svg"/> 
</figure>

</figure><br>
<p>Node-RED powers most of the automations in my smart home. After setting up the container, you can install extensions and configure the connection between Home Assistant and Node-RED.</p>
<dl>
  <dt>Website:</dt>
  <dd><a href="https://nodered.org/docs/getting-started/docker#quick-start">Node-RED</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>LOCAL_USER</code>: (Optional) Map the docker user to your user id. This ensures that files can be edited without root access. Especially useful in combination with the VSCode server image.</li>
    </ul>
  </dd>
  <dt>Additional notes:</dt>
  <dd>
    <ul>
      <li>When the Node-RED container is running, you can install the Home Assistant extension and connect Node-RED with HA. You only have to do this once. After the initial install, all configurations and extension are saved.</li>
      <li>The Home Assistant extension for Node-RED is <a href="https://flows.nodered.org/node/node-red-contrib-home-assistant-websocket">node-red-contrib-home-assistant-websocket</a>. You can install this extension within Node-RED by going to "Manage palette" in the menu.</a></li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Node-RED</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodered</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nodered</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nodered/node-red</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;1880:1880&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Local path where all Node-RED config will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local path&gt;:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TZ</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Europe/Amsterdam&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p><a name="appdaemon"></a></p>
<h2 id="docker-compose-for-appdaemon">Docker compose for AppDaemon</h2>
<p>AppDaemon is a great way to create small Python-based scripts that can interface with Home Assistant. AppDaemon runs in its own container and needs a &ldquo;long lived access token&rdquo; from HA to communicate.</p>
<dl>
  <dt>Website:</dt>
  <dd><a href="https://appdaemon.readthedocs.io/en/latest/">AppDaemon</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>SERVER_IP</code>: URL of Home Assistant.</li>
      <li><code>HA_APPDAEMON_KEY</code>: Long lived access token generated in Home Assistant.</li>
      <li><code>LOCAL_USER</code>: (Optional) Map the docker user to your user id. This ensures that files can be edited without root access. Especially useful in combination with the VSCode server image.</li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">appdaemon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">appdaemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">acockburn/appdaemon:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">HA_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://${SERVER_IP}:8123&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">TOKEN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${HA_APPDAEMON_KEY}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DASH_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://${SERVER_IP}:5050&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;5050:5050&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Set which local directory will contain all your app daemon configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;local config&gt;:/conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span></code></pre></div><p><a name="vscode"></a></p>
<h2 id="docker-compose-for-vs-code-server">Docker compose for VS Code Server</h2>
<figure style="text-align: left; max-width: 150px;">
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 234 235.1"><style>.st0{fill:#0179cb}</style><path class="st0" d="M83.3 143.9l-58 45.2L0 176.5V58.7L25.2 46l57.6 45.3L174 0l60 23.9v186.9l-59.7 24.3-91-91.2zm88.9 15.9V75.3l-54.6 42.3 54.6 42.2zM27.3 144.6L56 118.5 27.3 89.9v54.7z"/></svg>
</figure>
<p>Not technically required for Home Assistant, but Visual Studio Code Server is a great way to add a remote IDE to your setup. When the container is running, you have access to a complete IDE within your browser.</p>
<dl>
  <dt>Website:</dt>
  <dd><a href="https://github.com/cdr/code-server">cdr/code-server</a></dd>
  <dt>ENV variables:</dt>
  <dd>
    <ul>
      <li><code>VSCODE_PASSWORD</code>: Password you want to use with VS Code</li>
    </ul>
  </dd>
</dl>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Visual Studio code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">vscode</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">vscode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">codercom/code-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Set &lt;project dir&gt; to the directory you want to open in VS Code.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;project dir&gt;:/home/coder/project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># &lt;vs code config&gt; should point to a local dir where vs code stores its data.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">&lt;vs code config dir&gt;:/home/coder/.local/share/code-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;8443:8080&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">code-server --auth password --disable-telemetry /home/coder/project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${VSCODE_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><p><a name="overview"></a></p>
<h2 id="complete-docker-compose-file">Complete docker compose file</h2>
<p>For reference, the <code>.env</code> and <code>docker-compose.yaml</code> file:</p>
<p><strong><code>.env</code></strong></p>
<pre tabindex="0"><code>SERVER_IP= 0.0.0.0
HA_APPDAEMON_KEY=some long accces token
VSCODE_PASSWORD=password
LOCAL_USER=1000
VNC_PASSWORD=password
MYSQL_ROOT_PASSWORD=password
HA_MYSQL_PASSWORD=password
</code></pre><p><strong><code>docker-compose.yaml</code></strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># HomeAssistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">homeassistant</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">home-assistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant/home-assistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Local path where your home assistant config will be stored</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local config path&gt;:/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># MariaDB is optional (only if you would like to use a different database for HA).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">mariadb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Deconz is optional (only if you use the deconz Zigbee hub).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># MariaDb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">mariadb</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mariadb/server:10.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">mariadb</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${MYSQL_ROOT_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">ha_db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_USER</span><span class="p">:</span><span class="w"> </span><span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MYSQL_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${HA_MYSQL_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Local path where the database will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local db path&gt;:/var/lib/mysql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;3306:3306&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">deconz</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">marthoc/deconz</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">network_mode</span><span class="p">:</span><span class="w"> </span><span class="l">host</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># You can access Deconz at this port</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DECONZ_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DECONZ_WS_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8088</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Set VNC_MODE to 0 to disable it completely</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DECONZ_VNC_MODE</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DECONZ_VNC_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">5900</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DECONZ_VNC_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${VNC_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/localtime:/etc/localtime:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/etc/timezone:/etc/timezone:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Replace &lt;local path&gt; with a path where all deconz config will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local path&gt;:/root/.local/share/dresden-elektronik/deCONZ</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">devices</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;/dev/ttyUSB0:/dev/ttyUSB0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Node-RED</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">nodered</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nodered</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nodered/node-red</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;1880:1880&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Local path where all Node-RED config will be stored.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local path&gt;:/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TZ</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Europe/Amsterdam&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># AppDaemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">appdaemon</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">appdaemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">acockburn/appdaemon:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">HA_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://${SERVER_IP}:8123&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TOKEN</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${HA_APPDAEMON_KEY}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DASH_URL</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;http://${SERVER_IP}:5050&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;5050:5050&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Set which local directory will contain all your app daemon configuration</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;local config&gt;:/conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">homeassistant</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${LOCAL_USER}:${LOCAL_USER}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Visual Studio code</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">vscode</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">vscode</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">codercom/code-server:v2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># Set &lt;project dir&gt; to the directory you want to open in VS Code.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;project dir&gt;:/home/coder/project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># &lt;vs code config&gt; should point to a local dir where vs code stores its data.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">&lt;vs code config dir&gt;:/home/coder/.local/share/code-server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8443:8443&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">code-server --auth password --port 8443 --disable-telemetry /home/coder/project</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${VSCODE_PASSWORD}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span></code></pre></div><div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Home Assistant Smart Home setup with hardware list</title>
      <link>https://www.wouterbulten.nl/posts/home-assistant-smart-home-hardware-setup/</link>
      <pubDate>Mon, 04 Feb 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/home-assistant-smart-home-hardware-setup/</guid>
      <description>Continuously updated overview overview of the tech I use in my smart home.</description>
      <content:encoded><![CDATA[<p>On this page I keep track of my current Smart Home setup, built around Home Assistant, Deconz and Node-RED. So, if you are interested in my setup please read on. The list of devices I use has grown organically over time, sometimes based on a certain need (like a proper light sensor), and sometimes based on a specific sale or interest for a device. Still, if I had to start over I would still pick many of these devices for a second time.</p>
<p><em>Note:</em> The pros and cons described here are my personal experiences. 🙂</p>
<p><em>Note 2:</em> All trademarks, logos and brand names are the property of their respective owners. All company, product and service names used in this website are for identification purposes only. Otherwise, it would be very hard to explain which hardware devices or software I use or consider for my home automation project! 😉</p>
<p><strong>Quickly jump to:</strong></p>
<ul>
<li><a href="#controllers">Controller</a></li>
<li><a href="#sensors">Sensors</a></li>
<li><a href="#lights">Lights</a></li>
<li><a href="#buttons-and-switches">Buttons and switches</a></li>
<li><a href="#dashboards">Dashboards</a></li>
<li><a href="#plugs">Plugs</a></li>
</ul>

   <h2 id="controllers">Controllers</h1>
   <p>My smart home runs on Home Assistant, App Deamon, Node-RED, and Deconz. I started small with a set of Raspberry Pis. At some point, I required a bit more processing power and switched to an Intel NUC with Docker.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/intelnuc.png" loading="lazy" alt="Intel NUC"></a>
         <figcaption>Intel NUC</figcaption>
      </figure>

      <h3 id="hardware-intel-nuc" class="hardware-item-title">Intel NUC</h3>

      <div class="hardware-item-content">
         <p>Main controller of the system running all services through Docker.</p>

         
         <p>Consisting of:</p>
         <ul>
            
            <li><a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel NUC8i5BEK Bean Canyon</a></li>
            
            <li><a href="https://amzn.to/2nStXIi" rel="nofollow">Samsung SSD 970 EVO Plus 250 GB</a></li>
            
            <li><a href="https://amzn.to/35Dj1zf" rel="nofollow">HyperX Impact 16GB DDR4 2666MHz</a></li>
            
            <li><a href="https://amzn.to/2po4t61" rel="nofollow">ConBee</a></li>
            
         </ul>
         

         <p><small><em>Pros:</em> Powerfull, runs all your services on one device. Home Assistant is more responsive, especially with a large network of devices. Can also run other services. Docker (with Docker compose) gives more flexibility.</small></p>
         <p><small><em>Cons:</em> A lot more expensive than a single Pi. Higher learning curve.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Intel NUC see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2BkXyNK" rel="nofollow">Intel nuc</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://www.home-assistant.io/" rel="nofollow">Home Assistant</a></li>
            
            <li><a href="https://www.home-assistant.io/docs/ecosystem/appdaemon/" rel="nofollow">AppDaemon</a></li>
            
            <li><a href="https://nodered.org/" rel="nofollow">Node-RED</a></li>
            
            <li><a href="https://pi-hole.net/" rel="nofollow">Pi-hole</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/conbee2.jpg" loading="lazy" alt="ConBee"></a>
         <figcaption>ConBee</figcaption>
      </figure>

      <h3 id="hardware-conbee" class="hardware-item-title">ConBee</h3>

      <div class="hardware-item-content">
         <p>My main Zigbee hub using software from Dresden Electronics, connected to my Intel Nuc. I use the v1 version, but a newer v2 version is now available. The ConBee is compatible with most of devices of Hue, Osram, Innr, Ikea and Xiaomi. I chose for the USB version so that I could always switch to another device if needed. As I started with a Raspberry pi, and then moved to an Intel Nuc this turned out to be a good choice.</p>

         

         <p><small><em>Pros:</em> Huge list of compatible devices, see the <a href="https://phoscon.de/en/conbee2/compatible">compatbility list</a>. Nice interface for joining lights, sensors and switches. The GUI can be used to debug/view the Zigbee network (gives great insight in the mesh abilities).</small></p>
         <p><small><em>Cons:</em> More expensive than the flash-your-own Zigbee radios. Higher learning curve than the vendor hubs.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the ConBee see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2po4t61" rel="nofollow">Conbee</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://phoscon.de/en/conbee2/install" rel="nofollow">Official website</a></li>
            
            <li><a href="https://phoscon.de/en/conbee2/install#docker" rel="nofollow">Docker image</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/raspberrypi.jpg" loading="lazy" alt="Raspberry Pi 3B&#43;"></a>
         <figcaption>Raspberry Pi 3B&#43;</figcaption>
      </figure>

      <h3 id="hardware-raspberry-pi-3b&#43;" class="hardware-item-title">Raspberry Pi 3B&#43;</h3>

      <div class="hardware-item-content">
         <p>My first smart home setup consisted of Home Assistant, App Daemon and Node-RED are all running on a Raspberry Pi. Two other Pis, one for Deconz and one for PiHole, completed the set. While I could have ran all these services on a single device (like a NUC), I wanted to start small with a single Pi, and I have extended it since then. Currently, the Raspberry Pis have been replaced by a single Intel NUC.</p>

         

         <p><small><em>Pros:</em> Cheaper / easier to start with.</small></p>
         <p><small><em>Cons:</em> Less room for future upgrades, SD cards will not run indefinately.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Raspberry Pi 3B&#43; see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2sKF2Kq" rel="nofollow">Raspberry Pi 3B&#43;</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://www.home-assistant.io/" rel="nofollow">Home Assistant</a></li>
            
            <li><a href="https://nodered.org/" rel="nofollow">Node-RED</a></li>
            
            <li><a href="https://pi-hole.net/" rel="nofollow">Pi-hole</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="dashboards">Dashboards</h1>
   <p>A tablet-powerd dashboard is a great way of interacting with your house. I use it as the main control panel that gives access to the most important features and settings. It&rsquo;s also a very convenient way of giving guests access to your smart home system without having them to install an app or browsing to a website.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/fire_tablet.jpg" loading="lazy" alt="Amazon Fire Tablet"></a>
         <figcaption>Amazon Fire Tablet</figcaption>
      </figure>

      <h3 id="hardware-amazon-fire-tablet" class="hardware-item-title">Amazon Fire Tablet</h3>

      <div class="hardware-item-content">
         <p>I use a basic Fire 7 tablet from Amazon for my dashboard. In combination with the <a href="https://www.ozerov.de/fully-kiosk-browser/">Fully Kiosk Browser</a> it&rsquo;s a really conventient way of controling my smart home. Fire OS (what runs on these tablets) can be a bit limiting but, for me, is enough for running a dashboard.</p>

         

         <p><small><em>Pros:</em> Good price</small></p>
         <p><small><em>Cons:</em> Not the fastests tablets. Not available in all countries. FireOS can be restrictive.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Amazon Fire Tablet see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2WFJmIx" rel="nofollow">Fire tablet</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://www.ozerov.de/fully-kiosk-browser/" rel="nofollow">Fully Kiosk Browser</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="buttons-and-switches">Buttons and switches</h1>
   <p></p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/hue_dimmer.jpg" loading="lazy" alt="Philips Hue Smart Dimmer Switch"></a>
         <figcaption>Philips Hue Smart Dimmer Switch</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-smart-dimmer-switch" class="hardware-item-title">Philips Hue Smart Dimmer Switch</h3>

      <div class="hardware-item-content">
         <p>A four-button wireles switch that is often present in the starter packs of Philips Hue. Its intended use is to dim lights, however I use it as a remote for my Sonos-powered music system. Using Deconz and Node-RED I mapped the single buttons to music functions: shuffle, volume and skipping songs.</p>

         

         <p><small><em>Pros:</em> Four buttons, wall mount using magnets.</small></p>
         <p><small><em>Cons:</em> Labels on the buttons (can be a pro or con). Bit more expensive than a single Xiaomi button.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Smart Dimmer Switch see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2WEml8Q" rel="nofollow">Hue Dimmer Switch</a></li>
            
         </ul>
         
            
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/WXKG11LM.jpg" loading="lazy" alt="Xiaomi Aqara wireless switch (WXKG11LM)"></a>
         <figcaption>Xiaomi Aqara wireless switch (WXKG11LM)</figcaption>
      </figure>

      <h3 id="hardware-xiaomi-aqara-wireless-switch-wxkg11lm" class="hardware-item-title">Xiaomi Aqara wireless switch (WXKG11LM)</h3>

      <div class="hardware-item-content">
         <p><p>Very useful and cheap (around €7) switch with a single button. I have spread several of these around my house controlling a wide range of functions. They are especially usefull to add a simple light switch to rooms.</p>
<p>The Xiaomi hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Small form factor. Good price.</small></p>
         <p><small><em>Cons:</em> Not available locally</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Xiaomi Aqara wireless switch (WXKG11LM) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought on various websites. Usually cheapest on Gearbest or AliExpress.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/WXKG01LM.jpg" loading="lazy" alt="Xiaomi Aqara wireless switch (WXKG01LM)"></a>
         <figcaption>Xiaomi Aqara wireless switch (WXKG01LM)</figcaption>
      </figure>

      <h3 id="hardware-xiaomi-aqara-wireless-switch-wxkg01lm" class="hardware-item-title">Xiaomi Aqara wireless switch (WXKG01LM)</h3>

      <div class="hardware-item-content">
         <p>Comparable to the other switch (see above), but this one has a bit larger touch area. I use one in my kitchen as I can activate this switch with my elbows which is usefull during cooking. Its a bit more espensive though, usually around €9.
The Xiaomi hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>

         

         <p><small><em>Pros:</em> Larger touch area.</small></p>
         <p><small><em>Cons:</em> Not available locally. Bit more expensive than WXKG11LM.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Xiaomi Aqara wireless switch (WXKG01LM) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought on various websites. Usually cheapest on Gearbest or AliExpress.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="lights">Lights</h1>
   <p>Lights are the main &lsquo;output&rsquo; of my smart home and are controlled by various inputs. As I use Deconz as my main Zigbee hub I&rsquo;m not limited to a single brand. I have tested multiple brands (Hue, Ikea, OSRAM, Innr) and the lights I use the most are described here.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/hue_color.png" loading="lazy" alt="Philips Hue Color Bulbs"></a>
         <figcaption>Philips Hue Color Bulbs</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-color-bulbs" class="hardware-item-title">Philips Hue Color Bulbs</h3>

      <div class="hardware-item-content">
         <p>Although a bit expensive, the quality of these bulbs is very good and they have a great range of colors (in comparison to other lights I&rsquo;ve tested). I use them to create some color highlights. For larger areas that do not need colors I went with the cheaper White Ambiance variant.</p>

         

         <p><small><em>Pros:</em> Very good color range. Great dimming capabilities. No problems with faulty bulbs (so far).</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Color Bulbs see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2TkmUm8" rel="nofollow">Hue color bulb</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/hue_e27.png" loading="lazy" alt="Philips Hue White Ambiance Bulbs"></a>
         <figcaption>Philips Hue White Ambiance Bulbs</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-white-ambiance-bulbs" class="hardware-item-title">Philips Hue White Ambiance Bulbs</h3>

      <div class="hardware-item-content">
         <p>I use these lights as a cheaper alternative to the color Hue lights. I use them in places where controlling brightness and color temp is sufficient. Excellent dimming quality and the color warmth has the best range in comparison to the other brands.</p>

         

         <p><small><em>Pros:</em> Very good temperature range. Great dimming capabilities. No problems with faulty bulbs (so far).</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue White Ambiance Bulbs see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2GdLeST" rel="nofollow">Hue bulb</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/ikea_gu10.jpg" loading="lazy" alt="Ikea Tradfri GU10 dimmable led"></a>
         <figcaption>Ikea Tradfri GU10 dimmable led</figcaption>
      </figure>

      <h3 id="hardware-ikea-tradfri-gu10-dimmable-led" class="hardware-item-title">Ikea Tradfri GU10 dimmable led</h3>

      <div class="hardware-item-content">
         <p><p>What can you do wrong with a €7 smart light? Not much. These lights are very good as a basic lights for rooms that do not need more than dimming. I did have some problems with some of these though, like flickering when they were off or not wanting to connect to my hub.</p>
<p>The Ikea hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Great entry-level light. One of the cheapest Zigbee lights available. Decent light quality. Good value for your money.</small></p>
         <p><small><em>Cons:</em> Dimming capabilities are moderate, 1% brightness of this light is 10% of a comparable Hue light. Had some problems with faulty bulbs.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Ikea Tradfri GU10 dimmable led see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought at any Ikea store.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/hue_gu10.jpeg" loading="lazy" alt="Philips Hue White Ambience GU10"></a>
         <figcaption>Philips Hue White Ambience GU10</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-white-ambience-gu10" class="hardware-item-title">Philips Hue White Ambience GU10</h3>

      <div class="hardware-item-content">
         <p>Highest quality GU10 Zigbee lights I found so far. Excellent dimming capabilities (great for night lights) and a wide color range. If they would have been cheaper I would have bought more of these.</p>

         

         <p><small><em>Pros:</em> Superb dimming capabilities. Nice color temperature range.</small></p>
         <p><small><em>Cons:</em> More expensive than alternatives.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue White Ambience GU10 see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2W0NouR" rel="nofollow">Hue GU10</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/ikea_gu10.jpg" loading="lazy" alt="Ikea Tradfri GU10 dimmable led &#43; color temp"></a>
         <figcaption>Ikea Tradfri GU10 dimmable led &#43; color temp</figcaption>
      </figure>

      <h3 id="hardware-ikea-tradfri-gu10-dimmable-led-&#43;-color-temp" class="hardware-item-title">Ikea Tradfri GU10 dimmable led &#43; color temp</h3>

      <div class="hardware-item-content">
         <p><p>Double the price of the simple Tradfri light but includes adds color temperature. The range of temperatures and the dimming capabilties are still not great, but the lights have a very good value for the price. If you&rsquo;re not to picky for light quality, these lights are a great starter-buy.</p>
<p>The Ikea hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Very good price. Decent light quality. Has color temperature support.</small></p>
         <p><small><em>Cons:</em> Dimming capabilities are moderate, 1% brightness of this light is 10% of a comparable Hue light. Setting temperature and brightness at the same time can be difficult (<a href="https://www.wouterbulten.nl/posts/ikea-tradfri-temp-and-brightness-with-home-assistant/">more info</a>).</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Ikea Tradfri GU10 dimmable led &#43; color temp see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought at any Ikea store.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="plugs">Plugs</h1>
   <p>Smart plugs are an easy way to make dumb devices a bit smarter. For example, I use one to control the charger of my wall tablet and a second one to control my (non-smart) TV.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/osram_plug.jpg" loading="lazy" alt="OSRAM Smart&#43; Plug (Sylvania in the US)"></a>
         <figcaption>OSRAM Smart&#43; Plug (Sylvania in the US)</figcaption>
      </figure>

      <h3 id="hardware-osram-smart&#43;-plug-sylvania-in-the-us" class="hardware-item-title">OSRAM Smart&#43; Plug (Sylvania in the US)</h3>

      <div class="hardware-item-content">
         <p><p>Simple plug that I use for adding on/off control to non-smart devices. I&rsquo;ve only tested the EU OSRAM version, in the US this brand is available under the Sylvania name.</p>
<p>The OSRAM hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Good value for your money. Good zigbee meshing capabilities.</small></p>
         <p><small><em>Cons:</em> No power measurement (at least in Deconz).</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the OSRAM Smart&#43; Plug (Sylvania in the US) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2UBNGXq" rel="nofollow">OSRAM Plug (EU)</a></li>
            
            <li><a href="https://amzn.to/2G93ZYz" rel="nofollow">Sylvania Plug (US)</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://www.ozerov.de/fully-kiosk-browser/" rel="nofollow">Fully Kiosk Browser</a></li>
            
         </ul>
         
      </div>
   </div>
   

   <h2 id="sensors">Sensors</h1>
   <p>Sensors are what transitioned my home from an <em>app powered</em> home to a <em>smart</em> home. Before I had any sensors, lights were controlled by an app (e.g. the Philips Hue app) and everything was manual. Now, with the introduction of these sensors, most lights can be turned on and off automatically.</p>

   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/RTCGQ11LM.jpg" loading="lazy" alt="Xiaomi Aqara motion sensor (RTCGQ11LM)"></a>
         <figcaption>Xiaomi Aqara motion sensor (RTCGQ11LM)</figcaption>
      </figure>

      <h3 id="hardware-xiaomi-aqara-motion-sensor-rtcgq11lm" class="hardware-item-title">Xiaomi Aqara motion sensor (RTCGQ11LM)</h3>

      <div class="hardware-item-content">
         <p><p>In my opinion, the best motion sensor for this price. I use this sensor in all places where I want to automate something based on motion. Usually sells for around €10 (keep an eye on sales!). The device also includes a light sensor but the sensor readings are very inaccurate. The motion detection range is quite good but large rooms will need more than one.</p>
<p>The Xiaomi hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Best for the price. Small form factor.</small></p>
         <p><small><em>Cons:</em> Not available locally. Sends max. 1 update per minute.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Xiaomi Aqara motion sensor (RTCGQ11LM) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought on various websites. Usually cheapest on Gearbest or AliExpress.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/MCCGQ11LM.jpg" loading="lazy" alt="Xiaomi Aqara door sensor (MCCGQ11LM)"></a>
         <figcaption>Xiaomi Aqara door sensor (MCCGQ11LM)</figcaption>
      </figure>

      <h3 id="hardware-xiaomi-aqara-door-sensor-mccgq11lm" class="hardware-item-title">Xiaomi Aqara door sensor (MCCGQ11LM)</h3>

      <div class="hardware-item-content">
         <p><p>Door sensors are one of the most usefull sensors to integrate in to a smart home. They can of course be used as part of an alarm on both windows and doors. Moreover, my main use for them is to turn on lights when a door is opened. This makes sure that lights are on even if the motion sensors did not register the motion yet and results in a nice transition.</p>
<p>These sensors from Aqara are one of the cheapest available, but work great. They are really small and sell for around €7.</p>
<p>The Xiaomi hub is not required as it connects to my <a href="#hardware-conbee">ConBee</a>. An alternative is to run <a href="https://github.com/Koenkk/zigbee2mqtt">Zigbee2mqtt</a>.</p>
</p>

         

         <p><small><em>Pros:</em> Best for the price. Very small.</small></p>
         <p><small><em>Cons:</em> Not available locally</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Xiaomi Aqara door sensor (MCCGQ11LM) see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li>Can be bought on various websites. Usually cheapest on Gearbest or AliExpress.</li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   
   <div class="hardware-item">
      <figure>
         <a href="" rel="nofollow"><img src="/assets/images/ha/hardware/hue_motion.jpg" loading="lazy" alt="Philips Hue Motion Sensor"></a>
         <figcaption>Philips Hue Motion Sensor</figcaption>
      </figure>

      <h3 id="hardware-philips-hue-motion-sensor" class="hardware-item-title">Philips Hue Motion Sensor</h3>

      <div class="hardware-item-content">
         <p>The Hue motion sensor is bit more expensive than the Aqara motion sensors and is also larger in size. I still bought one as the light sensors in the Aqara sensors are not very precise and I wanted to monitor natural light intensity.</p>

         

         <p><small><em>Pros:</em> Very precise light sensor. Can be installed using a magnet.</small></p>
         <p><small><em>Cons:</em> Expensive. Larger than the Xiaomi version.</small></p>
      </div>

      <div class="hardware-item-links">
         <p><small>For more information about the Philips Hue Motion Sensor see any of the following links:</small></p>
         
         <em>Hardware</em>
         <ul>
            
            <li><a href="https://amzn.to/2TmAk19" rel="nofollow">Hue motion sensor</a></li>
            
         </ul>
         
            
         
         <em>Software</em>
         <ul>
            
            <li><a href="https://github.com/dresden-elektronik/deconz-rest-plugin" rel="nofollow">Deconz</a></li>
            
            <li><a href="https://github.com/Koenkk/zigbee2mqtt" rel="nofollow">Zigbee2mqtt</a></li>
            
         </ul>
         
      </div>
   </div>
   

<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Node-RED based custom full-room wake-up light</title>
      <link>https://www.wouterbulten.nl/posts/custom-wake-up-light-with-node-red/</link>
      <pubDate>Sun, 27 Jan 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/custom-wake-up-light-with-node-red/</guid>
      <description>Using Node-RED and Home Assistant and would you like to build a custom full room wake-up light? In this post I show my setup and explain how to build a wake-up light using Node-RED.</description>
      <content:encoded><![CDATA[<p>In a <a href="https://www.wouterbulten.nl/posts/custom-wake-up-light-with-home-assistant/">previous post</a> I showed a wake-up light using Home Assistant and the default <a href="https://www.wouterbulten.nl/posts/custom-wake-up-light-with-home-assistant/">YAML configuration</a>. In this post I&rsquo;ll show my current setup which makes use of Node-RED.</p>
<p>As with my previous system, this wake-up light&rsquo;s function is to light up my complete bedroom just before I need to wake up. The system works with any smart lamp that is compatible with HA, this includes Philips Hue and IKEA Tradfri lamps. I use it with a few GU10 bulbs (like the <a rel="nofollow" href="https://amzn.to/2W0NouR">Hue GU10 lights</a>). If you want you could even use some color-enabled bulbs to include a nice color transition mimicking a sun rise.</p>
<p>In this post I&rsquo;ll show you how to build this system. The automation will be configurable (on/off, time) and has a switch to disable it on weekends.</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/wake-up-light-full-sequence.png" alt="The complete flow.">
    <figcaption>
        <p>The complete flow.</p>
    </figcaption>
</figure>  </p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/ha-wake-up-lights.png" alt="Dashboard widget of the Wake-up light component. All functions are configurable through the Home Assistant dashboard.">
    <figcaption>
        <p>Dashboard widget of the Wake-up light component. All functions are configurable through the Home Assistant dashboard.</p>
    </figcaption>
</figure>  </p>
<h2 id="example-hardware">Example hardware</h2>
<p>I&rsquo;ve used the following components for my Wake-up light:</p>
<ul>
<li>Home Assistant and Node-RED running on a <a rel="nofollow" href="https://amzn.to/2sKF2Kq">Raspberry 3B+</a> using Hass.io</li>
<li>Conbee Zigbee hub; there is a <a rel="nofollow" href="https://amzn.to/2Tov8cQ">USB version</a> and a <a rel="nofollow" href="https://amzn.to/2sPKzzm">PI version</a>. (More info <a href="https://github.com/dresden-elektronik/deconz-rest-plugin">here</a>)</li>
<li>Three GU10 Zigbee smart lights. I use 3 <a rel="nofollow" href="https://amzn.to/2CNPwgy">Innr GU10 lights</a>(not available in the US) but you could also use something like the lights from <a rel="nofollow" href="https://amzn.to/2W0NouR">Philips Hue</a>.</li>
</ul>
<p>These components are just an example. A minimum requirement is a device that runs Home Assistant and Node-RED, and a smart light that you can control.</p>
<p><a class="btn btn-info" href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-hardware-setup/">My Home Automation hardware setup</a></p>
<h2 id="dashboard-widget">Dashboard widget</h2>
<p>For controlling the settings of the wake-up light I use the same widget as with my <a href="https://www.wouterbulten.nl/posts/custom-wake-up-light-with-home-assistant/">previous post</a>. Please see that post if you would like to read more of the details.</p>
<p>The system requires three inputs:</p>
<ol>
<li>An <code>input_boolean</code> to enable or disable the system.</li>
<li>A second <code>input_boolean</code> to enable or disable the system on weekends.</li>
<li>A <code>input_datetime</code> to set the time of the wake-up light.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># configuration.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">input_boolean </span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_enabled</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake-up lights&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="kc">on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:theme-light-dark</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_weekend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Enable Wake-up on weekends&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="kc">off</span><span class="w"> </span><span class="c"># I disable the system on default on weekends</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:calendar-blank</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">input_datetime</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Start lights at&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">has_time</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">has_date</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;07:20&#34;</span><span class="w">
</span></span></span></code></pre></div><p><strong>Note:</strong> If you want to persist the values between restarts of Home Assistant remove the &lsquo;initial&rsquo; value from the configuration.</p>
<p>The controls can be combined in a single view (or you can do this through the Lovelace ui editor):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># groups.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">alarm_clock</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake-up Lights&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entities</span><span class="p">:</span><span class="w"> </span><span class="c"># Add all entities here that should be part of the widget</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_datetime.wakeup_time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_boolean.wakeup_enabled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_boolean.wakeup_weekend</span><span class="w">
</span></span></span></code></pre></div><h2 id="building-the-flow">Building the flow</h2>
<h3 id="scheduling">Scheduling</h3>
<p>The first step in our flow is reading the <code>wakeup_time</code> from Home Assistant. This time is then passed on to a <a href="https://www.npmjs.com/package/node-red-contrib-schedex">schedex node</a>. The <a href="https://www.npmjs.com/package/node-red-contrib-schedex">schedex node</a> is a easy-to-use node to start something at a configurable time. An alternative is BigTimer but for something simple as this I prefer schedex. The schedex node sends a message at a specific time that can be configured. This perfectly matches our requirements as we would like to start the wake-up light at a configurable time (without changing the flow each time the time changes).</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/schedex-node.png" alt="The most important node of the flow: schedex">
    <figcaption>
        <p>The most important node of the flow: schedex</p>
    </figcaption>
</figure>  </p>
<p>To configure the schedex trigger, we have to send a payload with the new time to the schedex node when the wake-up time changes in Home Assistant. Schedex requires a specific payload format so I use simple function node (the <em>parse time</em> node in the image) to map the data from Home Assistant tot he correct format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">new_msg</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">payload</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">ontime</span><span class="o">:</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">offtime</span><span class="o">:</span> <span class="nx">msg</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">substr</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nx">new_msg</span><span class="p">;</span>
</span></span></code></pre></div><p><figure>
    <img loading="lazy" src="/assets/images/ha/wakeup-time-input.png" alt="Triggering the wake-up light is done using a schedex node.">
    <figcaption>
        <p>Triggering the wake-up light is done using a schedex node.</p>
    </figcaption>
</figure>  </p>
<p>These three nodes make sure that: 1) each time we change the wake-up time in HA the schedex node is updated and 2) the schedex node triggers a message at the configured time.</p>
<p>The configuration of the schedex node can be used to set some default values. I let the node send an &lsquo;off&rsquo; message 60 minutes after the wake-up time started; you could use such an off message to run some additional automations.</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/schedex-node-config-onoff.png" alt="Default values for the schedex node">
    <figcaption>
        <p>Default values for the schedex node</p>
    </figcaption>
</figure>  </p>
<h3 id="enabledisable-the-wake-up-light">Enable/disable the wake-up light</h3>
<p>The wake-up light can be disabled or only enabled on week days. To implement these requirements we add a filter after the schedex node that checks if the requirements are met.</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/wakeup-control-nodes.png" alt="When schedex triggers an event we still need to check whether the wakeup light is enabled. This can be done by using a few nodes in series.">
    <figcaption>
        <p>When schedex triggers an event we still need to check whether the wakeup light is enabled. This can be done by using a few nodes in series.</p>
    </figcaption>
</figure>  </p>
<p>To implement this filter I make use of a function node that uses Javascript to get the day of the week. The node returns <code>true</code> as the payload if the current day is a weekend day. In Javascript &lsquo;6&rsquo; is a Saturday and Sunday &lsquo;0&rsquo;.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Contents of the &#34;Day of the week&#34; function node
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">var</span> <span class="nx">day</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getDay</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">isWeekend</span> <span class="o">=</span> <span class="p">(</span><span class="nx">day</span> <span class="o">===</span> <span class="mi">6</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="nx">day</span> <span class="o">===</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">payload</span><span class="o">:</span> <span class="nx">isWeekend</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p><figure>
    <img loading="lazy" src="/assets/images/ha/wakeup-day-of-week.png" alt="Configuration of the function node using the javascript snippet.">
    <figcaption>
        <p>Configuration of the function node using the javascript snippet.</p>
    </figcaption>
</figure>  </p>
<p>The output of the function node can then be routed using a switch node:</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/wakeup-day-of-week-switch.png" alt="Switch node to route the output of the weekend-node.">
    <figcaption>
        <p>Switch node to route the output of the weekend-node.</p>
    </figcaption>
</figure>  </p>
<h3 id="controlling-the-lights">Controlling the lights</h3>
<p>The last step is to connect the output of the &ldquo;Enabled?&rdquo; node to a sequence that controls the lights. You can go wild here and create something based on the light setup in your bedroom. In my case I start the lights at 1% brightness and then slowly increase the brightness for 30 minutes. After 30 minutes, when all the lights are at max brightness, it further sets my home to a &lsquo;morning&rsquo; state and turns on lights in other parts of the house.</p>
<p>In my example I use a template node to slowly increase the brightness of the lights:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">{{<span class="w"> </span><span class="p">[</span><span class="m">250</span><span class="p">,</span><span class="w"> </span><span class="l">state_attr(&#39;light.bedroom&#39;, &#39;brightness&#39;) + 9] | min }}</span><span class="w">
</span></span></span></code></pre></div><h2 id="complete-flow-and-source-code">Complete flow and source code</h2>
<p>Connecting all components and my light control nodes results in the complete flow as shown below:</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/wake-up-light-full-sequence.png" alt="The complete flow controlling my wake-up light using Node-RED.">
    <figcaption>
        <p>The complete flow controlling my wake-up light using Node-RED.</p>
    </figcaption>
</figure>  </p>
<p>To get started with this setup you can use the json source code of my nodes and customize them to fit your needs. At a minimum you need to change the entities of the lights that you would like to control.</p>
<p><strong>Source code of nodes used in this post</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;d228445d.4259f8&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;function&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Get day of week&#34;</span><span class="p">,</span><span class="nt">&#34;func&#34;</span><span class="p">:</span><span class="s2">&#34;var day = new Date().getDay();\n\nvar isWeekend = (day === 6) || (day === 0); \n\nreturn {\n    payload: isWeekend,\n};&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">&#34;noerr&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">260</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1420</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;ba532104.74a48&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;ba532104.74a48&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;switch&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Is it weekend?&#34;</span><span class="p">,</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;propertyType&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;rules&#34;</span><span class="p">:[{</span><span class="nt">&#34;t&#34;</span><span class="p">:</span><span class="s2">&#34;true&#34;</span><span class="p">},{</span><span class="nt">&#34;t&#34;</span><span class="p">:</span><span class="s2">&#34;else&#34;</span><span class="p">}],</span><span class="nt">&#34;checkall&#34;</span><span class="p">:</span><span class="s2">&#34;true&#34;</span><span class="p">,</span><span class="nt">&#34;repair&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">260</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1460</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;e7a99d1f.bdef&#34;</span><span class="p">],[</span><span class="s2">&#34;e25ce15f.614ee&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;e7a99d1f.bdef&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-current-state&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Enabled on weekends?&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if&#34;</span><span class="p">:</span><span class="s2">&#34;off&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_compare&#34;</span><span class="p">:</span><span class="s2">&#34;is&#34;</span><span class="p">,</span><span class="nt">&#34;override_topic&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;override_payload&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;override_data&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;entity_id&#34;</span><span class="p">:</span><span class="s2">&#34;input_boolean.wakeup_weekend&#34;</span><span class="p">,</span><span class="nt">&#34;state_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">530</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1420</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;e25ce15f.614ee&#34;</span><span class="p">],[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;e25ce15f.614ee&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-current-state&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Enabled?&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if&#34;</span><span class="p">:</span><span class="s2">&#34;off&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_compare&#34;</span><span class="p">:</span><span class="s2">&#34;is&#34;</span><span class="p">,</span><span class="nt">&#34;override_topic&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;override_payload&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;override_data&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;entity_id&#34;</span><span class="p">:</span><span class="s2">&#34;input_boolean.wakeup_enabled&#34;</span><span class="p">,</span><span class="nt">&#34;state_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">480</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1460</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;7db42b.e3de6bd4&#34;</span><span class="p">],[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;7db42b.e3de6bd4&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;1%&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;service_domain&#34;</span><span class="p">:</span><span class="s2">&#34;light&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_on&#34;</span><span class="p">,</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;{\&#34;entity_id\&#34;:\&#34;light.bedroom\&#34;,\&#34;brightness\&#34;:1}&#34;</span><span class="p">,</span><span class="nt">&#34;render_data&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;mergecontext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;output_location&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;output_location_type&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">230</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1600</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;5f30d3cb.1aadac&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;5f30d3cb.1aadac&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;looptimer&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;duration&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;units&#34;</span><span class="p">:</span><span class="s2">&#34;Minute&#34;</span><span class="p">,</span><span class="nt">&#34;maxloops&#34;</span><span class="p">:</span><span class="s2">&#34;30&#34;</span><span class="p">,</span><span class="nt">&#34;maxtimeout&#34;</span><span class="p">:</span><span class="s2">&#34;35&#34;</span><span class="p">,</span><span class="nt">&#34;maxtimeoutunits&#34;</span><span class="p">:</span><span class="s2">&#34;Minute&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">300</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1640</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;de710702.9a2d98&#34;</span><span class="p">],[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;de710702.9a2d98&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-render-template&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Get new light setting&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;template&#34;</span><span class="p">:</span><span class="s2">&#34;{{ [250, state_attr(&#39;light.bedroom&#39;, &#39;brightness&#39;) + 9] | min }}&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">360</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1680</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;3df7037a.2f373c&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;3df7037a.2f373c&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Increase light&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;service_domain&#34;</span><span class="p">:</span><span class="s2">&#34;light&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_on&#34;</span><span class="p">,</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;{\&#34;entity_id\&#34;:\&#34;light.bedroom\&#34;,\&#34;brightness\&#34;:\&#34;{{ payload }}\&#34;}&#34;</span><span class="p">,</span><span class="nt">&#34;render_data&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;mergecontext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;output_location&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;output_location_type&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">370</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1720</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;df372079.83747&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;server-state-changed&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Wake up time input&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;entityidfilter&#34;</span><span class="p">:</span><span class="s2">&#34;input_datetime.wakeup_time&#34;</span><span class="p">,</span><span class="nt">&#34;entityidfiltertype&#34;</span><span class="p">:</span><span class="s2">&#34;substring&#34;</span><span class="p">,</span><span class="nt">&#34;outputinitially&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;state_type&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">,</span><span class="nt">&#34;haltifstate&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_type&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;halt_if_compare&#34;</span><span class="p">:</span><span class="s2">&#34;is&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">230</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1200</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;47ca214a.cd1c1&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;47ca214a.cd1c1&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;function&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Parse time&#34;</span><span class="p">,</span><span class="nt">&#34;func&#34;</span><span class="p">:</span><span class="s2">&#34;new_msg = {\n    payload: {\n        ontime: msg.payload.substr(0,5),\n        offtime: msg.payload.substr(0,5)\n    }\n}\nreturn new_msg;&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">&#34;noerr&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">250</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1280</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;cfe7b8ae.660428&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;cfe7b8ae.660428&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;schedex&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Trigger at time&#34;</span><span class="p">,</span><span class="nt">&#34;suspended&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;lat&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;lon&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;ontime&#34;</span><span class="p">:</span><span class="s2">&#34;7:30&#34;</span><span class="p">,</span><span class="nt">&#34;ontopic&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;onpayload&#34;</span><span class="p">:</span><span class="s2">&#34;on&#34;</span><span class="p">,</span><span class="nt">&#34;onoffset&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;onrandomoffset&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;offtime&#34;</span><span class="p">:</span><span class="s2">&#34;12:00&#34;</span><span class="p">,</span><span class="nt">&#34;offtopic&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;offpayload&#34;</span><span class="p">:</span><span class="s2">&#34;off&#34;</span><span class="p">,</span><span class="nt">&#34;offoffset&#34;</span><span class="p">:</span><span class="s2">&#34;60&#34;</span><span class="p">,</span><span class="nt">&#34;offrandomoffset&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;mon&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;tue&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;wed&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;thu&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;fri&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;sat&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;sun&#34;</span><span class="p">:</span><span class="kc">true</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">440</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1280</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;b361dc76.e4a18&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;b361dc76.e4a18&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;switch&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;is on&#34;</span><span class="p">,</span><span class="nt">&#34;property&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;propertyType&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;rules&#34;</span><span class="p">:[{</span><span class="nt">&#34;t&#34;</span><span class="p">:</span><span class="s2">&#34;eq&#34;</span><span class="p">,</span><span class="nt">&#34;v&#34;</span><span class="p">:</span><span class="s2">&#34;on&#34;</span><span class="p">,</span><span class="nt">&#34;vt&#34;</span><span class="p">:</span><span class="s2">&#34;str&#34;</span><span class="p">}],</span><span class="nt">&#34;checkall&#34;</span><span class="p">:</span><span class="s2">&#34;true&#34;</span><span class="p">,</span><span class="nt">&#34;repair&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">590</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1280</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;d228445d.4259f8&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;235e234a.7b2aac&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;comment&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Time setting input&#34;</span><span class="p">,</span><span class="nt">&#34;info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">230</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1160</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;b938cf54.1bdd3&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;comment&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Trigger at the configured time&#34;</span><span class="p">,</span><span class="nt">&#34;info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">480</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1240</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;5a93ca40.9dd574&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;comment&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Check if the wake-up light should be enabled on this day of the week&#34;</span><span class="p">,</span><span class="nt">&#34;info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">390</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1360</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;e34ba2ee.19df8&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;comment&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;556ecca6.827b14&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Run the wake-up sequence&#34;</span><span class="p">,</span><span class="nt">&#34;info&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">260</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">1520</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;server&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Home Assistant&#34;</span><span class="p">}]</span>
</span></span></code></pre></div><p>Questions? Feedback? Feel free to add them to the comments of this post.</p>
<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setting color temperature and brightness of IKEA Tradfri lights simultaneously with Home Assistant</title>
      <link>https://www.wouterbulten.nl/posts/ikea-tradfri-temp-and-brightness-with-home-assistant/</link>
      <pubDate>Thu, 17 Jan 2019 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/ikea-tradfri-temp-and-brightness-with-home-assistant/</guid>
      <description>A small trick to set the color temperature and brightness of IKEA Tradfri lights simultaneously using Home Assistant and/or Node-RED.</description>
      <content:encoded><![CDATA[<p>In my smart home I use multiple IKEA Tradfri lights in combination with a Conbee Zigbee hub and Home Assistant. Recently I encountered a problem when trying to set both the brightness and the color temperature in a Home Assistant service call (<code>light.turn_on</code>). Apparently, the Tradfri bulbs only respond to one of these values at a time. This is unfortunate as changing the temperature and brightness is usually linked. For example, setting the lights to a dimmed warm setting at night or bright white in the morning. Luckily, there is a workaround to be able to set both simultaneously (sort of at least).</p>
<p>The <strong>solution to setting brightness and color temp on Tradfri lights</strong> is to split the single service call in to two and let those blend. In my setup, I first set the brightness of the lights with a small transition and use a second call to change the color temperature with a longer transition. I have tested multiple setups and this method results in the smoothest transition. In essence my workaround performs three steps:</p>
<ol>
<li>Turn on the light with the target brightness.</li>
<li>Delay for a second to make sure that the previous call has finished.</li>
<li>Set the color temperature of the light with a short transition (5 seconds).</li>
</ol>
<p>By putting these steps in to a script it can act as a drop-in replacement of a normal light call. If one of the settings was already set earlier, for example the brightness did not change, the transition is even smoother.</p>
<p>I have created two versions of this workaround, one for <a href="#ha">Home Assistant (YAML)</a> and one for <a href="#nodered">Node-RED</a>, to help others that encounter this problem. It is not the most elegant solution but in practice it works pretty good!</p>
<p><a name="ha"></a></p>
<h2 id="home-assistant-script">Home Assistant script</h2>
<p>To use the workaround in Home Assistant (yaml) add the following snippet to <code>scripts.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">turn_on_ikea_light</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sequence</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Check whether the light is off (optional)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ is_state(entity, &#39;off&#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;00:00:01&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Set the brightness of the lights.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ entity }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">brightness_pct</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ brightness_pct }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">transition</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">delay</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;00:00:01&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Set the color temperature.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ entity }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_temp</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ color_temp }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">transition</span><span class="p">:</span><span class="w"> </span><span class="m">5</span><span class="w">
</span></span></span></code></pre></div><p>This block of code adds a new script <code>turn_on_ikea_light</code> that can be called inside automations. <code>entity</code>, <code>brightness_pct</code> and <code>color_temp</code> are variables that can be set when calling the script. An example is the following automation where a light is turned on when a motion sensor registers movement:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Example automation using the script</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">automation</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">Turn on lights on movement</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">sensor.motion_sensor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">to</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">script.turn_on_ikea_light</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data_template</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c"># Variables that are passed on to the script:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity</span><span class="p">:</span><span class="w"> </span><span class="l">light.some_light_entity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">brightness_pct</span><span class="p">:</span><span class="w"> </span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">color_temp</span><span class="p">:</span><span class="w"> </span><span class="m">350</span><span class="w">
</span></span></span></code></pre></div><p><a name="nodered"></a></p>
<h2 id="node-red-script">Node-RED script</h2>
<p>I&rsquo;ve recently switched most of my automations to Node-RED so also rewrote my workaround. In Node-RED we need two service calls to HA (one for brightness and one for color temp). In the most simple case this can be done with two &ldquo;call service&rdquo; nodes and a delay node. To be able to reuse the flow this can be created as a subflow:</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/ikea-tradfri-subflow.png" alt="Subflow to set brightness and color temperature on Ikea lights">
    <figcaption>
        <p>Subflow to set brightness and color temperature on Ikea lights</p>
    </figcaption>
</figure>  </p>
<p>The subflow shown above expects a JSON message as the <code>msg.payload</code>. The payload is then split in to the two service calls. For example, to set the temperature of a light to 150 and the brightness to 100%, one could send the following payload:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;entity_id&#34;</span><span class="p">:</span> <span class="s2">&#34;light.some_light_entity&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;color_temp&#34;</span><span class="p">:</span> <span class="mi">150</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;brightness_pct&#34;</span><span class="p">:</span><span class="mi">100</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The values can be hard coded in a change-node or can be dynamically set (for example based on the time of day).</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/ikea-tradfri-flow.png" alt="Complete flow in Node-RED. Replace the inject node with something useful, e.g. a motion event.">
    <figcaption>
        <p>Complete flow in Node-RED. Replace the inject node with something useful, e.g. a motion event.</p>
    </figcaption>
</figure>  </p>
<p>The nodes of the subflow can be imported using the following snippet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;13593ff8.a6556&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;2dc416a2.d3b6fa&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Turn on lights&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;service_domain&#34;</span><span class="p">:</span><span class="s2">&#34;light&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_on&#34;</span><span class="p">,</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;{\&#34;entity_id\&#34;:\&#34;light.some_entity\&#34;,\&#34;transition\&#34;:1}&#34;</span><span class="p">,</span><span class="nt">&#34;render_data&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;mergecontext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;output_location&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;output_location_type&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">700</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">480</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;7167f74f.dd6528&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;api-call-service&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;2dc416a2.d3b6fa&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Set color temp&#34;</span><span class="p">,</span><span class="nt">&#34;server&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;service_domain&#34;</span><span class="p">:</span><span class="s2">&#34;light&#34;</span><span class="p">,</span><span class="nt">&#34;service&#34;</span><span class="p">:</span><span class="s2">&#34;turn_on&#34;</span><span class="p">,</span><span class="nt">&#34;data&#34;</span><span class="p">:</span><span class="s2">&#34;{\&#34;transition\&#34;:5}&#34;</span><span class="p">,</span><span class="nt">&#34;render_data&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;mergecontext&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;output_location&#34;</span><span class="p">:</span><span class="s2">&#34;payload&#34;</span><span class="p">,</span><span class="nt">&#34;output_location_type&#34;</span><span class="p">:</span><span class="s2">&#34;msg&#34;</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">840</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">420</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;96a91d65.7a1a4&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;delay&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;2dc416a2.d3b6fa&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;pauseType&#34;</span><span class="p">:</span><span class="s2">&#34;delay&#34;</span><span class="p">,</span><span class="nt">&#34;timeout&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;timeoutUnits&#34;</span><span class="p">:</span><span class="s2">&#34;seconds&#34;</span><span class="p">,</span><span class="nt">&#34;rate&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;nbRateUnits&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;rateUnits&#34;</span><span class="p">:</span><span class="s2">&#34;second&#34;</span><span class="p">,</span><span class="nt">&#34;randomFirst&#34;</span><span class="p">:</span><span class="s2">&#34;1&#34;</span><span class="p">,</span><span class="nt">&#34;randomLast&#34;</span><span class="p">:</span><span class="s2">&#34;5&#34;</span><span class="p">,</span><span class="nt">&#34;randomUnits&#34;</span><span class="p">:</span><span class="s2">&#34;seconds&#34;</span><span class="p">,</span><span class="nt">&#34;drop&#34;</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">680</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">420</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;7167f74f.dd6528&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;b76ccf31.5d0c&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;function&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;2dc416a2.d3b6fa&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Split payload&#34;</span><span class="p">,</span><span class="nt">&#34;func&#34;</span><span class="p">:</span><span class="s2">&#34;br = {\n    payload: {\n        data: {\n            entity_id: msg.payload.entity_id,\n            brightness_pct: msg.payload.brightness_pct\n        }\n    }\n}\n\nwarmth = {\n    payload: {\n        data: {\n            entity_id: msg.payload.entity_id,\n            color_temp: msg.payload.color_temp\n        }\n    }\n}\n\nreturn [warmth, br]\n&#34;</span><span class="p">,</span><span class="nt">&#34;outputs&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span><span class="nt">&#34;noerr&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="nt">&#34;x&#34;</span><span class="p">:</span><span class="mi">490</span><span class="p">,</span><span class="nt">&#34;y&#34;</span><span class="p">:</span><span class="mi">440</span><span class="p">,</span><span class="nt">&#34;wires&#34;</span><span class="p">:[[</span><span class="s2">&#34;96a91d65.7a1a4&#34;</span><span class="p">],[</span><span class="s2">&#34;13593ff8.a6556&#34;</span><span class="p">]]},{</span><span class="nt">&#34;id&#34;</span><span class="p">:</span><span class="s2">&#34;161bb087.35566f&#34;</span><span class="p">,</span><span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;server&#34;</span><span class="p">,</span><span class="nt">&#34;z&#34;</span><span class="p">:</span><span class="s2">&#34;&#34;</span><span class="p">,</span><span class="nt">&#34;name&#34;</span><span class="p">:</span><span class="s2">&#34;Home Assistant&#34;</span><span class="p">}]</span>
</span></span></code></pre></div><div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Talking to my house: Building a chat bot with Home Assistant, AppDaemon and Telegram</title>
      <link>https://www.wouterbulten.nl/posts/talking-to-my-house-telegram-homeassistant-bot/</link>
      <pubDate>Sat, 15 Dec 2018 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/talking-to-my-house-telegram-homeassistant-bot/</guid>
      <description>In this blog post I discuss talking (or chatting) to your home using a Telegram bot. I cover making a new Telegram bot, connecting it to Home Assistant and then building functionality using AppDaemon.</description>
      <content:encoded><![CDATA[<p>Home Assistant (HA) can be easily controlled using the web interface of one of the mobile apps. However, <strong>there are more fun ways to communicate with your home</strong>. In this blog post I discuss talking (or chatting) to your home using a Telegram bot. I show how you can easily make a bot, connect it to Home Assistant and use AppDaemon to build a bot that can give you information on your home.</p>
<p>Best of all, using this setup you can communicate with your home even outside your local network without having to open up Home Assitant. So <strong>no port forwarding required</strong>! The Telegram bot acts as a sort of proxy between you and your HA instance.</p>
<h2 id="setting-up-a-new-telegram-bot">Setting up a new Telegram bot</h2>
<p>To start, we need to make a Telegram bot to link with HA. There is an extensive guide at the <a href="https://core.telegram.org/bots">Telegram website</a>. In essence it boils down to a few simple steps:</p>
<ol>
<li>Start a new conversation with the <a href="https://telegram.me/botfather">BotFather</a>. This is a special bot that can be used to manage and create bots.</li>
<li>Use the command <code>/newbot</code> to start the process of making a bot.</li>
<li>Fill in the name of your bot (this is the display name).</li>
<li>Fill in the username of your bot (this has to be in the format of <code>&lt;your_bot_name&gt;bot</code>).</li>
<li>When all is done you will receive a HTTP API token from the BotFather. This is required to connect the bot to HA.</li>
</ol>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/telegram_bot_new.png" alt="Setting up a new bot with Telegram using the BotFather.">
    <figcaption>
        <p>Setting up a new bot with Telegram using the BotFather.</p>
    </figcaption>
</figure>  </p>
<p>After creating your bot you can start chatting with it. Note that a user must always start a chat with a bot; a bot cannot start a conversation on its own.</p>
<h2 id="connecting-a-telegram-bot-to-home-assistant">Connecting a Telegram bot to Home Assistant</h2>
<p>To start connecting our new Telegram bot to HA we need to enter our credentials for accessing the bot. We do this by adding a few lines to <code>secrets.yaml</code>. If you do not want to use the <code>secrets</code> file you can also skip this step and enter the credentials directly.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">telegram_bot_key</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;your api key from the BotFather&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">telegram_bot_main_chat_id</span><span class="p">:</span><span class="w"> </span><span class="l">&lt;your chat id&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">telegram_bot_chat_ids</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">&lt;your chat id&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">&lt;optional</span><span class="p">:</span><span class="w"> </span><span class="l">other chat ids you want your bot to communicate with&gt;</span><span class="w">
</span></span></span></code></pre></div><p>The chat id is the unique number that identifies your Telegram account. If you do not know your chat id you can send <code>/getid</code> to one of the &lsquo;id&rsquo;-bots. For example the <a href="http://telegram.me/myidbot">IDBot</a>: <a href="http://telegram.me/myidbot">http://telegram.me/myidbot</a>. The list of chat ids is used to prevent other people talking to your bot.</p>
<p>The next step is to load the Telegram bot in HA. Do this by adding the following lines to your <code>configuration.yaml</code> (or to a dedicated package file):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">telegram_bot</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">polling</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">api_key</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret telegram_bot_key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">allowed_chat_ids</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret telegram_bot_chat_ids</span><span class="w">
</span></span></span></code></pre></div><p>I am using the polling method for the Telegram bot. Using this method you do not have to open up your Home Assistant instance to the outside world.</p>
<p>We can also link the bot to the <a href="https://www.home-assistant.io/components/notify/">Notify component</a> of home assistant:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">notify</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">telegram</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">telegram</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">chat_id</span><span class="p">:</span><span class="w"> </span>!<span class="l">secret telegram_bot_main_chat_id</span><span class="w">
</span></span></span></code></pre></div><h2 id="basis-of-an-appdaemon-telegram-app">Basis of an AppDaemon Telegram app</h2>
<p><a href="https://www.home-assistant.io/docs/ecosystem/appdaemon/">AppDaemon</a> is a great tool for building our Telegram bot as we can leverage the flexibility of Python. Alternatively, it is also possible to build your bot totally using yaml files, but that will take a bit more effort.</p>
<p>Start by adding a new instance to your <code>apps.yaml</code> configuration for AppDaemon:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">bot</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">module</span><span class="p">:</span><span class="w"> </span><span class="l">telegram</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">class</span><span class="p">:</span><span class="w"> </span><span class="l">TelegramBot</span><span class="w">
</span></span></span></code></pre></div><p>Then create the associated python file (<code>telegram.py</code>). To get started, copy the following to the file to use as wireframe:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">appdaemon.plugins.hass.hassapi</span> <span class="k">as</span> <span class="nn">hass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">TelegramBot</span><span class="p">(</span><span class="n">hass</span><span class="o">.</span><span class="n">Hass</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Start listening for Telegram updates</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">listen_event</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">receive_telegram_text</span><span class="p">,</span> <span class="s1">&#39;telegram_text&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">receive_telegram_text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_id</span><span class="p">,</span> <span class="n">payload_event</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Do something with the text</span>
</span></span><span class="line"><span class="cl">      <span class="n">user_id</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;user_id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="n">message</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>Save the file and the apps should automatically start. The app starts listening to <code>telegram_text</code> events; these are triggered when the bot receives a message.</p>
<h2 id="saying-hi">Saying Hi!</h2>
<p>The first feature we can build in to the bot is to say a welcome message. However, it would be kind of annoyaing if the bot said &ldquo;Hi&rdquo; every time we send a new message to the bot. To remedy this I only send a greeting message at most once an hour.</p>
<p>Extend the <code>initialize</code> function of the class to keep track of your conversations using a dictionary. We will store the last time a user sent a message in this dictionary.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">time_between_conversations</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span> <span class="o">=</span> <span class="p">{}</span>
</span></span></code></pre></div><p>A new function is added to the class that sends a greeting message. It checks whether the last message was sent longer than 60 minutes and, if so, sends a greeting message.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">greet_user_if_new_conversation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">,</span> <span class="n">user_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Say hi when there is a new conversation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># For new users automatically set the time to zero.</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">user_id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Compute the time difference in seconds since the last message.</span>
</span></span><span class="line"><span class="cl">    <span class="n">time_diff</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">time_diff</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">time_between_conversations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Hi </span><span class="si">{</span><span class="n">user_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Send a message to the user.</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s1">&#39;telegram_bot/send_message&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">target</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">message</span><span class="o">=</span><span class="n">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span></code></pre></div><p>This new function is then called each time we receive a text:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">receive_telegram_text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_id</span><span class="p">,</span> <span class="n">payload_event</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">payload_event</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_id</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;user_id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">greet_user_if_new_conversation</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;from_first&#39;</span><span class="p">])</span>
</span></span></code></pre></div><p>The <code>from_first</code> payload variable should contain your first name (if set). Saying something to your bot should now result in a simple greeting:</p>
<p><br><br></p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/telegram_bot_saying_hi.png" alt="Simple welcome message from the bot.">
    <figcaption>
        <p>Simple welcome message from the bot.</p>
    </figcaption>
</figure>  </p>
<h2 id="asking-for-the-temperature">Asking for the temperature</h2>
<p>Now let&rsquo;s do something usefull. I would like to keep track of the current temperature in my house. To facilitate this I created a small function that reports this. However, I only want my bot to tell me the temperature when I ask for it. So we need to do some (very simple) text parsing. I would like the bot to react to simple questions like:</p>
<p><em>Is it cold outside?</em></p>
<p><em>What is the temperature?</em></p>
<p><em>Is it warm at home?</em></p>
<p>This can be done with some <del>very advanced artificial intelligence</del> simple rules in python. The code below matches simple words in the input. <em>Is it cold outside?</em> matches due to <em>cold</em> and <em>outside</em> being present in the text.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">if</span> <span class="nb">any</span><span class="p">((</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;temperature&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s1">&#39;warm&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;cold&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="s1">&#39;house&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;inside&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;outside&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="s1">&#39;warm&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;cold&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="s1">&#39;house&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;inside&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;outside&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">)):</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Report the temperature</span>
</span></span><span class="line"><span class="cl">  <span class="bp">self</span><span class="o">.</span><span class="n">send_house_temperature</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span></code></pre></div><p>If the text message passed any of our rules we then send the temperature back:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_house_temperature</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Send information about the temperature in the house&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sensors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;The outside temperature is </span><span class="si">{}</span><span class="s1"> °C.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_1&#39;</span><span class="p">,</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;It is </span><span class="si">{}</span><span class="s1"> °C in the livingroom.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_2&#39;</span><span class="p">,</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;The temperature of the livingroom is </span><span class="si">{}</span><span class="s1"> °C.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_2&#39;</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">start_messages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;Let me see.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;These are the temperatures currently.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;I have read the sensors for you.&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">msg</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">start_messages</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Read all sensors.</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">template</span><span class="p">,</span> <span class="n">sensor</span> <span class="ow">in</span> <span class="n">sensors</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">temp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_state</span><span class="p">(</span><span class="n">sensor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">msg</span> <span class="o">+=</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="n">template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">temp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s1">&#39;telegram_bot/send_message&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">target</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">message</span><span class="o">=</span><span class="n">msg</span><span class="p">)</span>
</span></span></code></pre></div><p>In this new function I read out different sensors around my home. In this case measuring the outside temperature, and the temperature inside my livingroom and bedroom. A message is then constructed using a randomly chosen start sentence and the sensor readings.</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/telegram_bot_temperatures.png" alt="A conversation with my Home Assistant bot using Telegram.">
    <figcaption>
        <p>A conversation with my Home Assistant bot using Telegram.</p>
    </figcaption>
</figure>  </p>
<h2 id="next-steps">Next steps?</h2>
<p>This tutorial gave a quick overview of making a Telegram bot, connecting it to Home Assistant and using AppDaemon to send messages. The bot can easily be extended to send other messages or perform certain tasks within Home Assistant. Maybe you want to be able to control your lights using the bot, or activate the robot vacuum cleaner. Any cool ideas? Feel free to add them in the comments!</p>
<h2 id="full-code-for-reference">Full code (for reference)</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">time</span> 
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">random</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">appdaemon.plugins.hass.hassapi</span> <span class="k">as</span> <span class="nn">hass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">TelegramBot</span><span class="p">(</span><span class="n">hass</span><span class="o">.</span><span class="n">Hass</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Start listening for Telegram updates</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">listen_event</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">receive_telegram_text</span><span class="p">,</span> <span class="s1">&#39;telegram_text&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">time_between_conversations</span> <span class="o">=</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">receive_telegram_text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event_id</span><span class="p">,</span> <span class="n">payload_event</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Do something with the text</span>
</span></span><span class="line"><span class="cl">      <span class="n">user_id</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;user_id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="n">message</span> <span class="o">=</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;text&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Send greeting message.</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">greet_user_if_new_conversation</span><span class="p">(</span><span class="n">user_id</span><span class="p">,</span> <span class="n">payload_event</span><span class="p">[</span><span class="s1">&#39;from_first&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="nb">any</span><span class="p">((</span>
</span></span><span class="line"><span class="cl">          <span class="s1">&#39;temperature&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="s1">&#39;warm&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;cold&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="s1">&#39;house&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;inside&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;outside&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="s1">&#39;warm&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;cold&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="s1">&#39;house&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;inside&#39;</span> <span class="ow">in</span> <span class="n">message</span> <span class="ow">or</span> <span class="s1">&#39;outside&#39;</span> <span class="ow">in</span> <span class="n">message</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="p">)):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Report the temperature</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">send_house_temperature</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">greet_user_if_new_conversation</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">,</span> <span class="n">user_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Say hi when there is a new conversation&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># For new users automatically set the time to zero.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">user_id</span> <span class="ow">not</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Compute the time difference in seconds since the last message.</span>
</span></span><span class="line"><span class="cl">        <span class="n">time_diff</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">time_diff</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="o">.</span><span class="n">time_between_conversations</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">msg</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Hi </span><span class="si">{</span><span class="n">user_name</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Send a message to the user.</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s1">&#39;telegram_bot/send_message&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="n">target</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="n">message</span><span class="o">=</span><span class="n">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">last_conversation</span><span class="p">[</span><span class="n">user_id</span><span class="p">]</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">send_house_temperature</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Send information about the temperature in the house&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">sensors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;The outside temperature is </span><span class="si">{}</span><span class="s1"> °C.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_1&#39;</span><span class="p">,</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;It is </span><span class="si">{}</span><span class="s1"> °C in the livingroom.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_2&#39;</span><span class="p">,</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;The temperature of the livingroom is </span><span class="si">{}</span><span class="s1"> °C.&#39;</span><span class="p">:</span> <span class="s1">&#39;sensor.temp_2&#39;</span> <span class="c1"># Replace this with your sensor&#39;s entity id</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">start_messages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;Let me see.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;These are the temperatures currently.&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;I have read the sensors for you.&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="n">start_messages</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Read all sensors.</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">template</span><span class="p">,</span> <span class="n">sensor</span> <span class="ow">in</span> <span class="n">sensors</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="n">temp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_state</span><span class="p">(</span><span class="n">sensor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">msg</span> <span class="o">+=</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="n">template</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">temp</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">call_service</span><span class="p">(</span><span class="s1">&#39;telegram_bot/send_message&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">target</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">message</span><span class="o">=</span><span class="n">msg</span><span class="p">)</span>
</span></span></code></pre></div><div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Room wake-up light: Custom room-wide wake-up light using Home Assistant</title>
      <link>https://www.wouterbulten.nl/posts/custom-wake-up-light-with-home-assistant/</link>
      <pubDate>Sat, 28 Apr 2018 00:00:00 +0000</pubDate>
      
      <guid>https://www.wouterbulten.nl/posts/custom-wake-up-light-with-home-assistant/</guid>
      <description>In this post I will show you how to build a custom wake-up light system. The automation will be configurable (on/off, time) and has a switch to disable it on weekends.</description>
      <content:encoded><![CDATA[<p>Recently I started using <a href="https://www.home-assistant.io/">Home Assistant (HA)</a> as a tool to control the lights in my home. My previous system was based on Homekit and, while working fairly good, lacked tools to further customize the automations. To get more control I switched to HA.</p>
<p>One of the first things I created using HA was a <strong>room wake-up light</strong>. I do own a Philips wake-up light but this doesn&rsquo;t light up my whole room so that left room fore improvement. The automation I built using Home Assistant slowly lights my room every morning. The system works with any smart lamp that is compatible with HA, this includes <a rel="nofollow" href="https://amzn.to/2WmWJNy">Philips Hue]</a> and IKEA Tradfri lamps. I use it with a few GU10 bulbs (like the <a rel="nofollow" href="https://amzn.to/2W0NouR">Hue GU10 lights</a>).</p>
<p>In this post I&rsquo;ll show you how to build this system. The automation will be configurable (on/off, time) and has a switch to disable it on weekends.</p>
<p><strong>More interested in Node-RED?</strong> Please read my post titled &ldquo;<a href="https://www.wouterbulten.nl/posts/custom-wake-up-light-with-node-red/">Node-RED based custom full-room wake-up light</a>&rdquo;.</p>
<p><figure>
    <img loading="lazy" src="/assets/images/ha/ha-wake-up-lights.png" alt="Dashboard widget of the Wake-up light component. All functions are configurable through the Home Assistant dashboard.">
    <figcaption>
        <p>Dashboard widget of the Wake-up light component. All functions are configurable through the Home Assistant dashboard.</p>
    </figcaption>
</figure>  </p>
<h2 id="example-hardware">Example hardware</h2>
<p>I&rsquo;ve used the following components for my Wake-up light:</p>
<ul>
<li>Home Assistant running on a <a rel="nofollow" href="https://amzn.to/2sKF2Kq">Raspberry 3B+</a></li>
<li>Conbee Zigbee hub; there is a <a rel="nofollow" href="https://amzn.to/2Tov8cQ">USB version</a> and a <a rel="nofollow" href="https://amzn.to/2sPKzzm">PI version</a>. (More info <a href="https://github.com/dresden-elektronik/deconz-rest-plugin">here</a>)</li>
<li>Three GU10 Zigbee smart lights. I use 3 <a href="https://amzn.to/2CNPwgy">Innr GU10 lights</a> (not available in the US) but you could also use something like the lights from <a href="https://amzn.to/2W0NouR">Philips Hue</a>.</li>
</ul>
<p>These components are just an example. A minimum requirement is a device that runs Home Assistant and a smart light that you can control.</p>
<p><a class="btn btn-info" href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-hardware-setup/">My Home Automation hardware setup</a></p>
<h2 id="time-sensor">Time sensor</h2>
<p>First step is to add a new sensor that measures time. This sensor will trigger the automation at the correct time. Add the following to <code>sensors.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># sensors.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">time_date</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">display_options</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="s1">&#39;time&#39;</span><span class="w">
</span></span></span></code></pre></div><p>Make sure that this file is included in your <code>configuration.yaml</code> file, if not add the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># configuration.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">sensor</span><span class="p">:</span><span class="w"> </span>!<span class="l">include sensors.yaml</span><span class="w">
</span></span></span></code></pre></div><h2 id="create-dashboard-widget">Create dashboard widget</h2>
<p>Next we build the dashboard widget. The widget consists of three controls: (1) a time input to control when the lights should go on, (2) an on/off switch and (3) a switch to enable the system on weekends.</p>
<p>We start with the two switches which are implemented as an <code>input_boolean</code>. Add the following to <code>configuration.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># configuration.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">input_boolean </span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_enabled</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake-up lights&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="kc">on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:theme-light-dark</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_weekend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Enable Wake-up on weekends&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="kc">off</span><span class="w"> </span><span class="c"># I disable the system on default on weekends</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">icon</span><span class="p">:</span><span class="w"> </span><span class="l">mdi:calendar-blank</span><span class="w">
</span></span></span></code></pre></div><p>The icons can be customized, see <a href="https://materialdesignicons.com/">Material Design Icons</a> for more options.</p>
<p>The third input controls the time of the wakeup light. For this we use <code>input_datetime</code> with the date component disabled as we are only interested in time.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># configuration.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">input_datetime</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">wakeup_time</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Start lights at&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">has_time</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">has_date</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">initial</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;07:20&#34;</span><span class="w">
</span></span></span></code></pre></div><p>To group the controls together in a single card on the dasboard we need to make a new group. Add the following to the <code>groups.yaml</code> file. Again make sure that this file is included in <code>configuration.yaml</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># groups.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">alarm_clock</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake-up Lights&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">entities</span><span class="p">:</span><span class="w"> </span><span class="c"># Add all entities here that should be part of the widget</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_datetime.wakeup_time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_boolean.wakeup_enabled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">input_boolean.wakeup_weekend</span><span class="w">
</span></span></span></code></pre></div><p><strong>Note:</strong> As someone pointed out in the comments, if you want to persist the values between restarts of Home Assistant remove the &lsquo;initial&rsquo; value from the configuration.</p>
<h2 id="create-the-automation">Create the automation</h2>
<p>With all controls defined we can make the automation itself. The automation consists of three components: the trigger, the condition and the action. Add the following to <code>automations.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># automations.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake-me up using bedroom lights&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Something that triggers the automation</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># A list of conditions that need to be met</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># The action we want to perform.</span><span class="w">
</span></span></span></code></pre></div><p>The trigger is based on the time sensor we just created. It gets the state of the sensor and checks whether this value matches the value of our datetime input.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states(&#39;sensor.time&#39;) == (states.input_datetime.wakeup_time.attributes.timestamp | int | timestamp_custom(&#39;%H:%M&#39;, False)) }}&#34;</span><span class="w">
</span></span></span></code></pre></div><p>The trigger we defined above will fire regardless of the on/off switches. To include the switches in the automation we have to create a list of conditions. The first condition checks whether the wake-up light is enabled, if not the automation is not executed at all. Then we add an <code>or</code> condition that checks whether it is a weekday (a condition on time) or that the weekend switch is enabled. Only one of the <code>or</code> conditions has to be met.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.wakeup_enabled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">or</span><span class="w"> </span><span class="c"># One of the conditions below must be true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w"> </span><span class="c"># Will be true when the switch is &#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.wakeup_weekend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w"> </span><span class="c"># Will be true on weekdays</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">weekday</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">mon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">tue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">wed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">thu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- <span class="l">fri</span><span class="w">
</span></span></span></code></pre></div><p>Last we define what to do when all conditions are met. In my case I slowly fade in all the lights in the <code>group.bedroom</code> group. You can add your own lights here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">group.bedroom</span><span class="w"> </span><span class="c"># Put the entity of your light or your group here</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">transition</span><span class="p">:</span><span class="w"> </span><span class="m">600</span><span class="w"> </span><span class="c"># Transition time in seconds</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">brightness</span><span class="p">:</span><span class="w"> </span><span class="m">255</span><span class="w">
</span></span></span></code></pre></div><p>The full automation with everything filled in, this should be placed in <code>automations.yaml</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># automations.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Wake me up with bedroom light transition for weekdays&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">platform</span><span class="p">:</span><span class="w"> </span><span class="l">template</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">value_template</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ states(&#39;sensor.time&#39;) == (states.input_datetime.wakeup_time.attributes.timestamp | int | timestamp_custom(&#39;%H:%M&#39;, False)) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">condition</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.wakeup_enabled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">or</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">conditions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">state</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">input_boolean.wakeup_weekend</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;on&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">condition</span><span class="p">:</span><span class="w"> </span><span class="l">time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">weekday</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">mon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">tue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">wed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">thu</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="l">fri</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">action</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">service</span><span class="p">:</span><span class="w"> </span><span class="l">light.turn_on</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">entity_id</span><span class="p">:</span><span class="w"> </span><span class="l">group.bedroom</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">transition</span><span class="p">:</span><span class="w"> </span><span class="m">600</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">brightness</span><span class="p">:</span><span class="w"> </span><span class="m">255</span><span class="w">
</span></span></span></code></pre></div><h2 id="enable-the-automation">Enable the automation</h2>
<p>With everything filled in restart HA. The widget should be available on your dashboard. From there you can enable the widget and set a custom time. Your lights should now go on at the defined time 💡.</p>
<div class="post-info-frame">
    <figure style="max-width: 240px;">
     <img src="/assets/images/ha/logos/ha-logo.svg" loading="lazy" alt="Home automation"></a>
    </figure>
    
    <h2>Series on Home Automation</h2>
    
    <p>This post is part of a series of posts on Home Automation. These posts usually cover a part of my own smart home or a project I worked on. I make heavy use of Home Assistant, Node-RED and AppDaemon to control my home; these posts are examples of this.</p>
        
    <h3>Interested in my setup?</h3>

    <a class="btn btn-primary" href="/posts/home-assistant-smart-home-hardware-setup/">Home Automation hardware setup</a>

    <h3>Other posts in this series</h3>
    <p>Interested in Home Automation, Home Assistant or Node-RED? I have a few other posts that might be of interest:</p>
    <ul>
    
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-smart-home-setup-two-years-later/">Home Automation / Home Assistant setup with recommended hardware: The four-year update</a></h3>
            <p><small>Overview of my current hardware for my smart home powered by Home Assistant. An update on my previous post of four years ago (!) when I had just started with home automation.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/auto-turn-on-off-chromecast/">Automatically turn on tv when streaming to a Chromecast</a></h3>
            <p><small>Quick Node-RED tutorial on automatically turning on a TV when streaming media.</small></o>
        </li>
     
        <li>
            <h4><a href="https://www.wouterbulten.nl/posts/home-assistant-automatic-dark-mode/">Automatic dark mode for Home Assistant</a></h3>
            <p><small>Quick tutorial on setting up an automatic dark mode for Home Assistant.</small></o>
        </li>
     
    </ul>   
</div>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
