Skip to content

parasaurolophus/home-automation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

home-automation

Node-RED based home automation system

Note: these flows use features introduced in Node-RED version 3.0. They will not load correctly into earlier versions of Node-RED.

graph TB

    browser["Web Browser"]

    subgraph "Home LAN"

        proxy["Reverse Proxy"]
        vue["Vue / Vuetify\nWeb App"]

        subgraph nodered["Node-RED"]

            flows["Flows\nhttps://github.com/parasaurolophus/home-automation"]
            dnssd["@parasaurolophus/nodered-dnssd"]
            eventsource["@parasaurolophus/nodered-eventsource"]

        end

        pvhub["PowerView Hub"]
        pvdevice["Hunter-Douglas Window Coverings"]
        huebridge["Philips Hue Bridge(s)"]
        huedevice["Philips Hue Lights\nand Accessories"]

    end

    browser <-- "HTTPS /\nWebSocket" --> proxy
    proxy <-- "HTTPS /\nWebSocket" --> vue
    vue <-- "WebSocket" --> flows

    flows <--> dnssd
    flows <--> eventsource

    flows <-- HTTP --> pvhub
    pvhub <-- Bluetooth --> pvdevice

    flows <-- "HTTPS" --> huebridge
    eventsource <-- "SSE" --> huebridge
    dnssd <-- "mDNS" --> huebridge
    huebridge <-- "Zigbee" --> huedevice

    click flows "https://github.com/parasaurolophus/home-automation" _blank
    click dnssd "https://flows.nodered.org/node/@parasaurolophus/node-red-dnssd" _blank
    click eventsource "https://flows.nodered.org/node/@parasaurolophus/node-red-eventsource" _blank
    click vue "https://github.com/parasaurolophus/home-automation/tree/main/dashboard" _blank
Loading

*If remote access is not a priority, just leave out the HTTPS related configuration from ~/.node-red/settings.js. Detailed instructions for obtaining and installing certificates and configuring IP reservations and port forwarding on routers are beyond the scope of this README.

**An earlier version of this project implemented the dashboard using pure HTML 5 / JavaScript for its dashboard which did not have additional build steps. I admit it. I was led astray by the pretty toggle switches.


About

These flows implement more sophisticated time-based automation than is supported directly by popular home automation platforms like Apple HomeKit, Amazon Alexa, Google Home, Samsung SmartThings, etc., or the native apps supplied by Philips and Hunter-Douglas. For example, they activate different seasonal lighting themes automatically based on the date. They also drive window shade automation based on the sun's position over the course of a day based on the geographic location of the home and the day of the year.

Configuration

These flows require some specific configuration in settings.js. To facilitate documenting and sharing these options, the following assumes that these secctions of settings.js are defined using require rather than by directly embedded JavaScript objects:

  • contextStorage
  • projects
  • An entry in the httpStatic array
  • A set of environment variable definitions

This is accomplished by creating a number of separate JavaScript files and referencing them from settings.js by placing the following just above (not inside) module.exports:

var environment = require("./environment");
var dashboardPath = require("./dashboardPath");
var contextStorage = require("./contextStorage");
var projects = require("./projects");

Each of these JavaScript files consists of its own module.exports statement, described below.

environment.js

Placing the following line above module.exports in settings.js loads the contents of environment.js as a module:

var environment = require("./environment");

The environment.js file contains definitions of environment variables assumed by various function nodes and subflows. Here is an example of what it should look contain:

module.exports = {
  // change these to the latitude and longitude
  // of your home
  latitude: (process.env.LATITUDE = "42.1967"),
  longitude: (process.env.LONGITUDE = "-90.0382"),

  // change these to the IP address and access token
  // of your Hue Bridge
  ground_floor_hue_address: (process.env.GROUND_FLOOR_HUE_ADDRESS =
    "192.168.1.40"),
  ground_floor_hue_key: (process.env.GROUND_FLOOR_HUE_KEY =
    "FiLAHkz6wMwa-3bz06K9a8MLfCaSSzR5WxSK8a-y"),

  // you can define multiple Hue Bridge configurations
  basement_hue_address: (process.env.BASEMENT_HUE_ADDRESS = "192.168.1.41"),
  basement_hue_key: (process.env.BASEMENT_HUE_KEY =
    "-7bz1KHa9Lde2QV00yBYQ75bo6hlVBN2mopvXnrNa"),

  // change this to the IP address of your PowerView hub
  powerview_address: (process.env.POWERVIEW_ADDRESS = "192.168.1.42"),

  // don't use "localhost" inside Node-RED due to a bug
  // introduced by Node.js >= 18
  //
  // also, if you have TLS / SSL enabled elsewhere
  // in settings.js, use wss:// here and you will
  // then probably have to use the DNS name in your
  // certificate instead of an IP address
  broker_url: (process.env.BROKER_URL = "ws://127.0.0.1:1880/broker"),
};
Environment Variable Description
LATITUDE Coordinate for use with suncalc
LONGITUDE Coordinate for use with suncalc
GROUND_FLOOR_HUE_ADDRESS IP address of the Hue Bridge controlling devices on the ground floor
GROUND_FLOOR_HUE_KEY API access token for the ground floor Hue Bridge
BASEMENT_HUE_ADDRESS IP address of the Hue Bridge controlling devices in the basement
BASEMENT_HUE_KEY API access token for the basement Hue Bridge
POWERVIEW_ADDRESS IP address of the PowerView hub
BROKER_URL The URL to use to when connecting as a client to the WebSocket served by these flows

contextStorage.js

Placing the following above module.exports in settings.js set the value of a variable named contextStorage to the JavaScript object defined by a module in contextStorage.js:

var contextStorage = require("./contextStorage");

That file contains:

module.exports = {
  default: { module: "memory" },
  file: { module: "localfilesystem" },
};

To be effective, the corresponding contextStorage: section in settings.js must reference the global variable:

/** Context Storage
 * The following property can be used to enable context storage. The configuration
 * provided here will enable file-based context that flushes to disk every 30 seconds.
 * Refer to the documentation for further options: https://nodered.org/docs/api/context/
 */
contextStorage: contextStorage,

The preceding adds a file-system backed context store named file to the default in-memory context store.

projects.js

Placing the following above module.exports in settings.js set the value of a variable named projects to the JavaScript object defined by a module in projects.js:

var projects = require("./projects");

That file contains:

module.exports = {
  /** To enable the Projects feature, set this value to true */
  enabled: true,
  workflow: {
    /** Set the default projects workflow mode.
     *  - manual - you must manually commit changes
     *  - auto - changes are automatically committed
     * This can be overridden per-user from the 'Git config'
     * section of 'User Settings' within the editor
     */
    mode: "manual",
  },
};

To be effective, the corresponding projects: subsection inside the editorTheme section in settings.js must reference the global variable:

projects: projects,

The preceding enables the git based projects features in the Node-RED editor and sets the workflow option to "manual" (the default).

dashboardPath.js

Placing the following above module.exports in settings.js set the value of a variable named dashboardPath to the JavaScript object defined by a module in dashboardPath.js:

var dashboardPath = require("./dashboardPath");

That file contains:

module.exports = {
  // change "nodereduser" to the name of the user
  // as which the Node-RED service runs
  path: "/home/nodereduser/.node-red/projects/home-automation/dashboard/dist/",
  root: "/dashboard/",
};

This module definitions assumes that the value of the httpStatic: section of settings.js will be an array, and that the value of the global dashboardPath variable will be one of its values:

httpStatic: [
    dashboardPath,
    // additional static paths, if desired...
],

Installation

  1. (Optional) Install your desired version of Node, e.g. using the nodesource repository for your host platform (make sure to choose a version that is compatible with your target version of Node-RED 3.0 or later)

  2. Install Node-RED 3.0 or later as per https://nodered.org/docs/getting-started/ (if you skipped step 0 then the standard install script for Debian based hosts will attempt to install or upgrade Node if no compatible version is found but then you may be left with a version of Node that is behind the current LTS)

  3. Install the dependencies described below

  4. Create the settings module files and modify settings.js as described above; modify the contents of the settings module files as appropriate for your local environment

  5. Restart the Node-RED process

  6. Create a copy of https://github.com/parasaurolophus/home-automation so that you can easily customize your configuration and use Git to safely manage it (you can, of course, create a fork but pull requests that include changes to the home automation configuration will be rejected)

  7. Use Node-RED's "open project" feature to clone your copied repository into your local project folder

  8. In a terminal,

    cd ~/.node-red/projects/home-automation/dashboard
    npm install
    npm run build

    to create the ~/.node-red/projects/home-automation/dashboard/dist directory

If all goes well, opening the Node-RED editor will allow you to examine and modify these flows. Browsing to your Node-RED instance's URL with /dashboard added to the end will open the Vuetify based dashboard. Note that you will have to edit the contents of the Automation and Hue flows to match your hardware setup and scene configuration in order for the Hue controls to be generated on the dashboard and the time and date based lighting and window shade automations to have the intended effect.

Dependencies

The following node packages must be installed before loading these flows into your environment:

In addition, some function nodes in these flows load the suncalc package dynamically, which must be enabled in settings.js (true by default).

Features

  • Local control of Philips Hue lighting and Hunter-Douglas (PowerView) window coverings using the API's provided by their respective hubs

  • Dynamically created dashboard controls for individual device groups and scenes created by querying the Hue and PowerView hubs

  • Home automation driven by date, time and the position of the sun over the course of each day of the year

  • Support multiple Hue hubs concurrently

The implementation of these features provides practical demonstrations of a number of basic software-engineering concepts such as event-driven programming and data-driven user interfaces using the "Model - View - Controller" (MVC) design pattern. It also serves as a repository of examples of a number of techniques specific to Node-RED as a home automation platform and how it interoperates with underlying technologies such as WebSockets, JavaScript embedded in HTML and so on.

Requirements

The goals for these flows include:

  1. More sophisticated automation rules than supported directly by the native apps and off-the-shelf "smart home" platforms supplied by companies like Philips, Hunter-Douglas, Apple, Google, Amazon, Samsung etc.

  2. Support a consolidated user interface easily accessible by anyone in the home, including guests, without having to own a specific make of mobile device, install specific apps or create accounts with any so-called "cloud services"

  3. Eliminate the use of third-party "clouds" to the greatest degree possible due to performance, reliability, security and privacy concerns

The home in question has a number of "smart" devices from multiple manufacturers, none of which come with any ability to interoperate directly with one another. Home automation platforms from companies like Apple, Google and Amazon are woefully inadequate in many respects, and each requires anyone attempting to do things as simple as turning on and off lights to have a specific app, with a properly configured account, with that account given pretty much carte blanche authority to do anything it likes to every aspect of the "smart" home, mediated by "cloud services" owned and operated by third-parties with their own agendas that trump any consideration of their customers' convenience, security or privacy.

Using Node-RED allows for a fairly intuitive user interface that can be accessed with no more specialized an app than a web browser. Further, Node-RED can be programmed to do anything that can be accomplished in a general-purpose programming language, JavaScript, rather than being constrained by the features made available at the whim of companies more interested in extending their surveillance and control over their "walled gardens" (which they regard as including their customers' homes and personal property) than in providing useful products and services.

User interface

Warning! There is a build step required after cloning the repository for these flows in order for the dashboard to be available at https://<nodered-host>:1880/dashboard

cd ~/.node-red/projects/home-automation/dashboard
npm install
npm run build

This invokes the Vue / Vuetify "tooling" to create the contents of the _dist directory referenced by the httpStatic setting described above._

These Node-RED flows do not directly implement any user interface. Instead, they asynchronously send event messages and receive command messages using a WebSocket node configured to "listen to" the URI /broker. In addition to and separate from flows.json, the GitHub repository for these flows includes a subdirectory, 'ui, which implements a "single page web application" that connects to the /broker WebSocket listener in Node-RED and presents a user inteface composed of Vuetify 3 components. They make extensive use of Vue 3 features to implement a highly reactive user interface that automatically adapts to configuration changes made in the Hue and PowerView native apps.

This allows for a nearly complete separation between the view implemented using Vuetify, the model transmitted as JSON message payloads using WebSockets, and Node-RED flows as the controller in the so-called MVC (Model, View, Controller) architectural pattern. The result is that these flows display a consolidated user interface for controlling diverse devices from multiple vendors without having to be edited whenever those devices' configuration is changed in their respective native apps. They do this using features directly supported by modern web browsers without requiring the intermingling of front-end and back-end logic directly within the Node-RED flows. This is why these flows have no dependency on node-red-dashboard nor any of the community supplied packages intended to replace it. All of the functionality that would be supplied by any such package is entirely encapsulated within the ui/dist directory that must be built after downloading this repository from GitHub. With the appropriate configuration of settings.js as described above, you can access 'ui/dist/index.html using Node-RED's built-in web server and the web page will automatically deduce the correct URL with which to connect to the /broker WebSocket server implemented by these flows.

The reason for this strict separation between view and controller is not a dogmatic adherence to theoretical purity in the domain of software architecture. The sad truth is that while Node-RED greatly benefits from the culture of an open source community, it also suffers from the inevitable shortcomings of all such products. (This is not unique to Node-RED nor even to open source software: the many challenges of relying on any open source product is a specific example of the general principle known as the tragedy of the commons about which political and economic theorists have written for centuries.) The node-red-dashboard component, supplied by Node-RED's core development team, suffers from the kind of "bit rot" that always -- no, really, always -- infects open source projects' repositories while community-supplied components vary widely in their quality. Support by their authors is at best intermittent and very often ephemeral. That is why these flows are designed to rely on as few add-on components as possible. They use core nodes such as function and http request to utilize the various device API's directly rather than using node packages that wrap them because this reduces exposure to defects and deficiencies in third-party components. The critical bits of functionality that are implemented as community supplied node packages were created by the same author for the specific needs of accessing the Philips Hue Bridge SSE API from within these flows.

The author feels confident in sufficiently prompt and diligent responses to bug reports and feature requests he makes to himself, while having no illusions nor unreasonable expectations regarding the priority such issues would be given by someone else who created some similar package for their own purposes, who knows how long ago, and then moved on to who knows what other projects and interests.

Note that once built, the 'ui/dist directory contains only HTML, JavaScript, CSS and similar standard web content files. It does not require any special code on the web server (Node-RED, in this case). It uses only the native WebSocket support built into modern web browsers to communicate with the Node-RED back end rather than using a package like socket.io. All dynamic rendering is done on the client side using JavaScript and the DOM API also built into web browsers. Vue does use certain browser features that require it to be loaded with a URL beginning with http:// or https://, but it does not actually rely on any "server side rendering" code.

To emphasize this point, an earlier version of this repository had a UI implemented without the use of any client-side tooling but, rather, hand-crafted HTML, CSS and JavaScript. That version worked fine when opened using a file:// URL, i.e. without being "served" via HTTP at all. Frameworks like Vue / Vuetify provide only "nice to have" features that enhance maintainability and readability of the HTML, not essential features required for core functionality. To be clear, the latter is a feature of such frameworks, not a deficiency. By locating front-end concerns mostly (or, ideally, entirely) on the client, a distributed system makes optimal use of all the computing and memory resources available to it.

Theory of Operation

The automation features that are the core of these flows are implemented as a series of "trigger" events that are emitted up to six times per day when particular time and date conditions are met. Each "trigger" event has msg.topic set to auomtation/trigger and msg.payload containing a JavaScript object with the following properties:

msg.payload[] Description
timer/theme One of the theme values described below
timer/time One of the time values described below
settings/lighting A boolean value indicating whether or not lighting automation has been enabled by a user
settings/shades A boolean value indicating whether or not window covering automation has been enabled by a user

The dashboard includes controls for selecting the values for msg.payload['settings/lighting'] and msg.payload'settings/shades'].

Theme

The payload of each automation/trigger event has msg.payload['timer/theme'] set to one of following values:

msg.payload['timer/theme'] Description
tribal July 1 - 4 (US Independence Day)
spooky Any day in October
jolly Any day in December
standard Any other day

Time

These flows use suncalc and require that the ${LATITUDE} and ${LONGITUDE} environment variables be set as described above. In particular, each time these flows are started, and again every morning at 1AM (local time), these flows invoke suncalc.getTimes() and the current value of settings/bedtime to calculate the times at which to send automation/trigger events for the coming day. The timer function node on the Automation flow tab will arrange to send automation/trigger events at each of the times indicated by the result of calling suncalc.getTimes() after using suncalc.addTime() to include midday and afternoon time slots based on the sun's position in the sky. It also adds a bedtime time slot by adding or subtracting an offset of up to 1/2 hour that is randomly generated each time the timer function node is invoked to the current value of settings/bedtime.

Models, Events and Commands

As already noted, these flows and their UI are designed according to the MVC paradigm. These models are built up and maintained over time based on the payloads of various event messages sent by the flows. There are three general categories of such events:

  1. Timer events are used to trigger particular automations at specific times of days, taking into account specific times of year

  2. Settings events reflect user selectable options such as whether or not enable particular types of automation and when to trigger bedtime automations

  3. Device state messages are emitted when particular "smart" devices' states change

These flows also support command messages to control particular devices, activate "scenes" and so on that correspond generally to state change event messages but not always in an exactly 1:1 manner due to idiosyncracies of particular third-party systems. For example, The state of PowerView shades can be queried and controlled synchronously but do not support asynchronous state-change messaging. More subtly, the API exposed by Hue bridges emits a far more rich (and arguably over-engineered) repertoire of state change events than it accepts as commands.

The general pattern is that the flows emit fairly fine-grained state and settings messages asynchronously while the UI consumes such messages in real time to build up and maintain the state of in-memory data models which it uses to drive the user experience. The state-change messages are generated by the flows subscribing to and querying device-specific API's exposed by their respective hubs and then broadcasting equivalent messages using the WebSocket based "message broker" flow. For example, here is how this pattern is implemented for Hue "resources" corresponding to individual lights, rooms and zones:

sequenceDiagram

  actor user as User

  box rgba(255, 255, 255, 0.25) Web Browser
    participant ui as UI
  end

  box rgba(255, 0, 0, 0.25) Node-RED Flows
    participant broker as Broker Flow
    participant subflow as Bridge Subflow
  end

  box rgba(0, 0, 255, 0.25) Zigbee Network
    participant hub as Hue Bridge
    participant device as Hue Resource(s)
  end

  user ->> ui: launch
  ui ->> broker: connect
  broker ->> subflow: refresh controls
  subflow ->> hub: query device state

  loop forever
    par device to user
      device ->> hub: current state event
      hub ->> subflow: current state event
      subflow ->> broker: state message
      broker ->> ui: state message
      ui ->> ui: update model
      ui ->> user: display controls
    and user to device
      user ->> ui: change control
      ui ->> broker: command message
      broker ->> subflow: command message
      subflow ->> hub: command
      hub ->> device: command
      device ->> device: change state
    end
  end
Loading

The same pattern could be easily extended to other makes and models of "smart" devices, including stand-alone Zigbee or Z-Wave based devices where Node-RED, itself, is acting as the "hub." This represents what the Node-RED maintainers refer to as the "closed-loop feedback" pattern and some UI toolkits refer to as "controlled components." The state of the controls (failry) reliably reflect the current state of the actual devices at the cost of some latency in updating the state of the UI to reflect the outcome of user actions. This is relevant to components like Vuetify switches whose visual state are intended to reflect the actual state of some corresponding device or subsystem. PowerView hubs, by contrast, do not emit any asynchronous state change events and (officially, at least) support only commands at the "scene" level of their internal data model. Such scene activations are represented as stateless button controls for both Hue and PowerView control panels in the UI.

Either way, using features of HTML5 as wrapped by the Vue / Vuetify web component framework and libraries, the front end not only maintains the in-memory model of the state of the controls, it dynamically generates the controls themselves based on the current state of the model. This allows the UI to adapt automatically to the current configuration of the back end systems. For example, there is no need in this implementation to change any UI code if Hue bridges are added or removed in the back end, lights are added or removed in a bridge, scenes are created or deleted, and so on.

Colors

Tribal Colors

Bureau of Educational and Cultural Affairs style guidelines

Pantone Identifier R G B Hex C M Y K
PMS 282C ("Old Glory Red") 0.04 0.19 0.38 #0A3161 1.00 0.68 0.00 0.54
White 1.00 1.00 1.00 #FFFFFF 0.00 0.00 0.00 0.00
PMS 193C ("Old Glory Blue") 0.72 0.10 0.26 #B31942 0.00 1.00 0.66 0.13