UNPKG

node-red-contrib-domotz

Version:

A collection of Node-Red nodes to access the Domotz public API

476 lines (425 loc) 22.7 kB
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> <head> <meta charset="utf-8"/> <meta name="generator" content="pandoc"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"/> <title>tutorial</title> <style> code { white-space: pre-wrap; } span.smallcaps { font-variant: small-caps; } span.underline { text-decoration: underline; } div.column { display: inline-block; vertical-align: top; width: 50%; } div.hanging-indent { margin-left: 1.5em; text-indent: -1.5em; } ul.task-list { list-style: none; } pre > code.sourceCode { white-space: pre; position: relative; } pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } code.sourceCode > span { color: inherit; text-decoration: inherit; } div.sourceCode { margin: 1em 0; } pre.sourceCode { margin: 0; } @media screen { div.sourceCode { overflow: auto; } } @media print { pre > code.sourceCode { white-space: pre-wrap; } pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } } pre.numberSource code { counter-reset: source-line 0; } pre.numberSource code > span { position: relative; left: -4em; counter-increment: source-line; } pre.numberSource code > span > a:first-child::before { content: counter(source-line); position: relative; left: -1em; text-align: right; vertical-align: baseline; border: none; display: inline-block; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; padding: 0 4px; width: 4em; color: #aaaaaa; } pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; } div.sourceCode { } @media screen { pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } } code span.al { color: #ff0000; font-weight: bold; } /* Alert */ code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */ code span.at { color: #7d9029; } /* Attribute */ code span.bn { color: #40a070; } /* BaseN */ code span.bu { } /* BuiltIn */ code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */ code span.ch { color: #4070a0; } /* Char */ code span.cn { color: #880000; } /* Constant */ code span.co { color: #60a0b0; font-style: italic; } /* Comment */ code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */ code span.do { color: #ba2121; font-style: italic; } /* Documentation */ code span.dt { color: #902000; } /* DataType */ code span.dv { color: #40a070; } /* DecVal */ code span.er { color: #ff0000; font-weight: bold; } /* Error */ code span.ex { } /* Extension */ code span.fl { color: #40a070; } /* Float */ code span.fu { color: #06287e; } /* Function */ code span.im { } /* Import */ code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */ code span.kw { color: #007020; font-weight: bold; } /* Keyword */ code span.op { color: #666666; } /* Operator */ code span.ot { color: #007020; } /* Other */ code span.pp { color: #bc7a00; } /* Preprocessor */ code span.sc { color: #4070a0; } /* SpecialChar */ code span.ss { color: #bb6688; } /* SpecialString */ code span.st { color: #4070a0; } /* String */ code span.va { color: #19177c; } /* Variable */ code span.vs { color: #4070a0; } /* VerbatimString */ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ .display.math { display: block; text-align: center; margin: 0.5rem auto; } </style> <!--[if lt IE 9]> <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script> <![endif]--> </head> <body> <h1 id="introduction">Introduction</h1> <p>In the previous tutorial we’ve seen how to install and use <a href="https://flows.nodered.org/node/node-red-contrib-domotz">Node-red-domotz</a> to build a custom dashboard leveraging the Domotz Public API.</p> <p>In this tutorial we’ll learn how to: * setup a Webhook management system using Node-Red * enrich the dashboard displaying live Domotz events * automatically respond to a network event through the Domotz Public API</p> <p>Domotz allows you to subscribe to events happening on agents and devices and to receive Webhooks when they occur. You can find more information about Webhook configuration and data format on the <a href="https://portal.domotz.nl/developers/#domotz-webhook">Domotz Portal</a>. In the rest of the tutorial we’ll assume that you correctly configured a <code>Shared alert</code> and attached it to at least one agent/device. </p> <h1 id="setting-up-the-webhook-listener">Setting-Up the Webhook listener</h1> <p>To be able to receive Webhooks from Domotz you need to expose a web server on a public IP address. The Node-Red <a href="https://nodered.org/docs/getting-started/">installation guide</a> can help you deploying your Node-Red flow on a public server. It is strongly recommended to <a href="https://nodered.org/docs/user-guide/runtime/securing-node-red">secure the endpoint</a>, using HTTPS and to set up an authentication mechanism.</p> <h1 id="create-and-test-the-server">Create and test the server</h1> <p>Once Node-Red is started we can create the web server using the <code>http in</code> node. Just drag it into the workspace and configure it with a URL suffix (<code>webhook-test</code> in this case). Method must be <code>POST</code>. <img src="img1.png" alt="http_in_node"/> This is just a receiver node, any HTTP client expects a response so we need to create also a <code>http response</code> node. Domotz Webhook service expects a response with code <code>201</code> and an empty body, hence we can directly attach the <code>http response</code> node to to the output of the <code>http in</code> node. Let’s also add a debug node to the same output to monitor the received messages. <img src="img2.png" alt="http_node_wiring"/> Once deployed the flow you can test the endpoint using any HTTP client (curl,<a href="https://www.postman.com/">Postman</a>) issuing a POST request like the following:</p> <pre class="$xslt"><code>curl --location --request POST &#39;https://your-node-red-service-ip:1880/webhook-test&#39; \ --header &#39;Content-Type: application/javascript&#39; \ --data-raw &#39;{&quot;test&quot;: true}&#39;</code></pre> <p>Just remember to replace <code>your-node-red-service-ip</code> with the actual IP address of your server and to add the authentication header to the request if needed.</p> <p>If the configuration is correct this is what you should see in the debug panel. <img src="img3.png" alt="http_node_debug"/> # Enriching the dashboard In the previous tutorial we’ve seen how to feed Domotz Public-API data in a Node-Red custom dashboard. In that scenario we used to poll the Domotz Public-API servers using the Domotz Node-Red node. Now we subscribe to Domotz events, receive and display data directly pushed from Domotz into the same dashboard. Keep in mind that the two approaches can happily coexist and they are useful for different purposes.</p> <p>The following setup is simple but powerful as it allows creating a live event log of everything happening on our Domotz agents. To do that, let’s create a new dashboard <code>template</code> node.</p> <figure> <img src="img4.png" alt="template_node"/> <figcaption aria-hidden="true">template_node</figcaption> </figure> <p>You can use the following code in the template to parse and display the incoming Domotz events in a list.</p> <div class="sourceCode" id="cb2"><pre class="sourceCode html"><code class="sourceCode html"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="kw">&lt;style&gt;</span></span> <span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a> li {</span> <span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a> <span class="kw">margin</span>: <span class="dv">10</span><span class="dt">px</span> <span class="dv">0</span><span class="op">;</span></span> <span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a> }</span> <span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a></span> <span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a> time {</span> <span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a> <span class="kw">font-size</span>: <span class="dv">10</span><span class="dt">px</span><span class="op">;</span></span> <span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a> <span class="kw">display</span>: <span class="dv">block</span><span class="op">;</span></span> <span id="cb2-9"><a href="#cb2-9" aria-hidden="true"></a> <span class="kw">font-style</span>: <span class="dv">italic</span><span class="op">;</span></span> <span id="cb2-10"><a href="#cb2-10" aria-hidden="true"></a> }</span> <span id="cb2-11"><a href="#cb2-11" aria-hidden="true"></a></span> <span id="cb2-12"><a href="#cb2-12" aria-hidden="true"></a> details {</span> <span id="cb2-13"><a href="#cb2-13" aria-hidden="true"></a> <span class="kw">display</span>: <span class="dv">block</span><span class="op">;</span></span> <span id="cb2-14"><a href="#cb2-14" aria-hidden="true"></a> <span class="kw">font-size</span>: <span class="dv">10</span><span class="dt">px</span><span class="op">;</span></span> <span id="cb2-15"><a href="#cb2-15" aria-hidden="true"></a> }</span> <span id="cb2-16"><a href="#cb2-16" aria-hidden="true"></a><span class="kw">&lt;/style&gt;</span></span> <span id="cb2-17"><a href="#cb2-17" aria-hidden="true"></a></span> <span id="cb2-18"><a href="#cb2-18" aria-hidden="true"></a><span class="kw">&lt;script&gt;</span></span> <span id="cb2-19"><a href="#cb2-19" aria-hidden="true"></a></span> <span id="cb2-20"><a href="#cb2-20" aria-hidden="true"></a> (<span class="kw">function</span> (scope) {</span> <span id="cb2-21"><a href="#cb2-21" aria-hidden="true"></a> scope<span class="op">.</span><span class="fu">$watch</span>(<span class="st">&#39;msg&#39;</span><span class="op">,</span> <span class="kw">function</span> (msg) {</span> <span id="cb2-22"><a href="#cb2-22" aria-hidden="true"></a> <span class="cf">if</span> (msg) {</span> <span id="cb2-23"><a href="#cb2-23" aria-hidden="true"></a> <span class="kw">var</span> list <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">getElementById</span>(<span class="st">&#39;myList&#39;</span>)<span class="op">;</span></span> <span id="cb2-24"><a href="#cb2-24" aria-hidden="true"></a> <span class="kw">var</span> entry <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">createElement</span>(<span class="st">&#39;li&#39;</span>)<span class="op">;</span></span> <span id="cb2-25"><a href="#cb2-25" aria-hidden="true"></a> <span class="kw">var</span> time <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">createElement</span>(<span class="st">&#39;time&#39;</span>)<span class="op">;</span></span> <span id="cb2-26"><a href="#cb2-26" aria-hidden="true"></a> <span class="kw">var</span> details <span class="op">=</span> <span class="bu">document</span><span class="op">.</span><span class="fu">createElement</span>(<span class="st">&#39;details&#39;</span>)<span class="op">;</span></span> <span id="cb2-27"><a href="#cb2-27" aria-hidden="true"></a> </span> <span id="cb2-28"><a href="#cb2-28" aria-hidden="true"></a> entry<span class="op">.</span><span class="fu">appendChild</span>(<span class="bu">document</span><span class="op">.</span><span class="fu">createTextNode</span>(msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">name</span>))<span class="op">;</span></span> <span id="cb2-29"><a href="#cb2-29" aria-hidden="true"></a> time<span class="op">.</span><span class="fu">appendChild</span>(<span class="bu">document</span><span class="op">.</span><span class="fu">createTextNode</span>(msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">timestamp</span>))<span class="op">;</span></span> <span id="cb2-30"><a href="#cb2-30" aria-hidden="true"></a> entry<span class="op">.</span><span class="fu">prepend</span>(time)<span class="op">;</span></span> <span id="cb2-31"><a href="#cb2-31" aria-hidden="true"></a> list<span class="op">.</span><span class="fu">prepend</span>(entry)<span class="op">;</span></span> <span id="cb2-32"><a href="#cb2-32" aria-hidden="true"></a> details<span class="op">.</span><span class="fu">appendChild</span>(<span class="bu">document</span><span class="op">.</span><span class="fu">createTextNode</span>(<span class="bu">JSON</span><span class="op">.</span><span class="fu">stringify</span>(msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">data</span><span class="op">,</span> <span class="kw">null</span><span class="op">,</span> <span class="dv">2</span>)))<span class="op">;</span></span> <span id="cb2-33"><a href="#cb2-33" aria-hidden="true"></a> entry<span class="op">.</span><span class="fu">append</span>(details)<span class="op">;</span></span> <span id="cb2-34"><a href="#cb2-34" aria-hidden="true"></a> }</span> <span id="cb2-35"><a href="#cb2-35" aria-hidden="true"></a> })<span class="op">;</span></span> <span id="cb2-36"><a href="#cb2-36" aria-hidden="true"></a> })(scope)<span class="op">;</span></span> <span id="cb2-37"><a href="#cb2-37" aria-hidden="true"></a></span> <span id="cb2-38"><a href="#cb2-38" aria-hidden="true"></a><span class="kw">&lt;/script&gt;</span></span> <span id="cb2-39"><a href="#cb2-39" aria-hidden="true"></a></span> <span id="cb2-40"><a href="#cb2-40" aria-hidden="true"></a><span class="kw">&lt;div&gt;</span></span> <span id="cb2-41"><a href="#cb2-41" aria-hidden="true"></a> <span class="kw">&lt;ul</span><span class="ot"> id=</span><span class="st">&quot;myList&quot;</span><span class="kw">&gt;&lt;/ul&gt;</span></span> <span id="cb2-42"><a href="#cb2-42" aria-hidden="true"></a><span class="kw">&lt;/div&gt;</span></span></code></pre> </div> <p>Each time a new message is received the $watch callback is invoked with the newly-received message. What we do in the callback is to unpack the content of the message into three parts (name, timestamp, and details), which are common to all Domotz Webhooks, and insert them at the top of a simple list. You can see the result in the following image. <img src="img5.png" alt="event_list_ui"/></p> <h1 id="advanced-event-parsing-and-automatic-reaction">Advanced event parsing and automatic reaction</h1> <p>In the previous example we treated each message in the same way, that is, take its content and display it in a dynamic list. If we want to respond to a specific event then we need to define a custom parsing function.</p> <p>In the next example we chain a webhook event handler (Round-Trip-Delay issue on a device) with a Public API call (restarting the device itself). The RTD issue event is described <a href="https://portal.domotz.nl/developers/#tocSdevicertdissueevent">here</a> and is generated when Domotz detects that the RTD values exceeds the defined thresholds. We then use the <a href="https://portal.domotz.nl/developers/#poweractionondevice">powerActionOnDevice</a> API call to perform a power cycle on the device. The following images display the wiring and configuration of the nodes.</p> <figure> <img src="img6.png" alt="reboot_conf"/> <figcaption aria-hidden="true">reboot_conf</figcaption> </figure> <figure> <img src="img7.png" alt="reboot_wiring"/> <figcaption aria-hidden="true">reboot_wiring</figcaption> </figure> <p>A function node is needed to: * filter the unrelated webhook events (we are only interested in events named <code>device_rtd</code>) * extract the information required to perform a reboot (<code>agent_id</code> and <code>device_id</code>) from the event body</p> <p>The information we extracted are returned as output of the function node and become the input of the API node (remember to check the <code>Use node input params</code> option in the Domotz node in this case. You can use the following code in the function node:</p> <div class="sourceCode" id="cb3"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="cf">if</span> (msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">name</span> <span class="op">===</span> <span class="st">&#39;device_rtd&#39;</span> <span class="op">&amp;&amp;</span> </span> <span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a> msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">data</span><span class="op">.</span><span class="at">status</span> <span class="op">===</span> <span class="st">&#39;RTD_ISSUE_DETECTED&#39;</span>) {</span> <span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a> <span class="cf">return</span> {</span> <span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a> <span class="st">&quot;payload&quot;</span><span class="op">:</span> {</span> <span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a> <span class="st">&quot;params&quot;</span><span class="op">:</span> {</span> <span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a> <span class="st">&quot;agent_id&quot;</span><span class="op">:</span> msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">data</span><span class="op">.</span><span class="at">agent_id</span><span class="op">,</span></span> <span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a> <span class="st">&quot;device_id&quot;</span><span class="op">:</span> msg<span class="op">.</span><span class="at">payload</span><span class="op">.</span><span class="at">data</span><span class="op">.</span><span class="at">device_id</span><span class="op">,</span></span> <span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a> <span class="st">&quot;field&quot;</span><span class="op">:</span> <span class="st">&quot;cycle&quot;</span></span> <span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a> }</span> <span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a> }</span> <span id="cb3-11"><a href="#cb3-11" aria-hidden="true"></a> }<span class="op">;</span></span> <span id="cb3-12"><a href="#cb3-12" aria-hidden="true"></a>}</span></code></pre> </div> <p>That’s it, the next time a RDT event will be detected by Domotz our flow will automatically reboot the device.</p> <h1 id="testing-tips">Testing Tips</h1> <p>Waiting for a webhook from Domotz can be tiresome, after all they are only triggered when something <em>really</em> happens on your networks. If you are interested in developing a Webhook handler a quick way to test your integration is to generate fake events using an HTTP client (as described before). You can forge a body simply copying the <em>Events</em> examples provided in the <a href="https://portal.domotz.nl/developers/#schemas">schemas</a> section of the documentation</p> <h1 id="conclusion">Conclusion</h1> <p>Node-Red can be a powerful tool to customise your Domotz experience. In conjunction with the <a href="https://flows.nodered.org/node/node-red-contrib-domotz">Node-red-domotz</a> plugin and the Domotz Webhook service it allows you to create custom application logic and beautiful dashboards.</p> </body> </html>