/domoja

NodeJS home automation framework

Primary LanguageTypeScript

NPM

NPM version Node.js CI CodeQL Coverage Status

domoja

A Typescript framework for home automation

Introduction

This framework allows to create home automation applications.

The server part is written in Typescript, while the GUI uses Angular and Ionic.

Here are some screenshots of the application:

Installation

# install domoja and domoja-core
$ yarn add domoja domoja-core 

# install tempo and proxiti modules to run the demo
$ yarn add domoja-tempo domoja-proxiti

# run the demo on http://localhost:8700
$ yarn domoja -p 8700 --dev

Concepts

Domoja collects information from and interacts with devices through sources. You can think of sources as sources of information.

Sources

A source provides information about, and allows controlling a certain set of devices.

To use a source, it is necessary to load its type in Domoja. Some source types are predefined in Domoja, some others can be add by extension modules:

$ yarn add domoja-<source-module>

Once loaded in Domoja, the module providing the source type needs to be imported in the configuration before a source of this type can be declared and then referenced by a device.

The example below shows how to create the source myAstronomy of type astronomy from the module proxity, to get the sunrise time:

$ yarn add domoja-proxiti
imports:
  - module: proxiti
    source: astronomy

sources:
  - myAstronomy: {
      type: astronomy,
      location: "06030"
  }

devices:
  - sunrise: { type: device, widget: text, tags: 'astronomyTag', source: myAstronomy, id: sunriseTime, name: "Lever du soleil" }

Source type Module Description
astronomy proxiti This source provides astronomy information from http://www.proxiti.info/horaires_soleil.php?o=06030
This includes sunset, sunrise, dawn, dusk, zenith times, and day duration, at a specific location.
Parameters:Example:
sources:
- astronomy: {
type: astronomy,
location: "06030"
}

devices:
- sunset: { type: device, widget: text, tags: 'astronomy', source: astronomy, id: sunsetTime, name: "Coucher du soleil" }
command core/sources/command Source implemented with shell commands:
  • parameters define the shell commands to execute when a device takes a given value
  • Example with parameters ON and OFF :
    - sources:
    - robonect-command: {
    type: command,
    ON: "bash -c \"curl 'http://192.168.0.16/xml?cmd=start' -s -K- <<< \\\"--user $(grep robonectBasicAuth config/secrets.yml | sed -e 's!^ [^:][^:]: *!!' -e 's/[\r\n]//g')\\\"\"",
    OFF: "bash -c \"curl 'http://192.168.0.16/xml?cmd=stop' -s -K- <<< \\\"--user $(grep robonectBasicAuth config/secrets.yml | sed -e 's!^ [^:][^:]: *!!' -e 's/[\r\n]//g')\\\"\""
    }
  • the optional parameter pushupdates is a shell command executed once as a daemon at the creation of the source
    • it allows to emit changes of device state values
    • it shoud produce stdout output in the form {"id": "<device_id>", "attribute": "<attribute>", "value": "<value>"}, e.g. {"id": "temp", "attribute": "state", "value": "10 °C"}
    • the daemon will be killed when the source is released, but to avoid zombie processes to be created, it is good to guard a loop by checking the parent process, for example:
    • while [ $(ps -o ppid= $$) != 1 ]; do ; sleep 60; done
    • available variables are:
      • ID: id of the device using the source
      • SOURCE: the path of the source
      • DEBUG: debug mode of the source ('0'|'1')

Example:
sources:
- disk-usage: {
type: command,
push-updates: "
while [ $(ps -o ppid= $$) != 1 ]
do
df -k | awk '{
mount=$6
percent=$5
str=\"{ \\\"id\\\": \\\"\"mount\"\\\", \\\"attribute\\\": \\\"state\\\", \\\"value\\\": \\\"\"percent\"\\\"}\"
if ('$DEBUG') print str > \"/dev/stderr\" # debug
print str
}'
sleep 60
done
"
}
Freebox freebox
IPX800 ipx800 This source connects to IPX800 devices from GCE Electronics.
Example:
sources:
- myIPX800: {
type: IPX800,
ip: 192.168.0.17,
macaddress: 00:04:A3:2D:68:E6,
update_url: /ipx800/update,
timeout: 60
}
Mqtt mqtt
Openzwave openzwave Domoja source to connect to ZWave devices
Example:
sources:
- ZStick: {
type: Openzwave,
debug: false,
driverLogLevel: "silly",
port: /dev/ttyACM0
}
devices:
- zwave:
- controller : { type: device, widget: "multistate:INCLUSION,INCLUSION_NON_SECURE,EXCLUSION,NO_INCLUSION_EXCLUSION:secondary,secondary,danger,primary:Inclure,Inclure non séc.,Exclure,Stop", tags: 'zwave', source: ZStick, id: "1", attribute: "inclusion_mode", name: "Controleur"}
- config : { type: device, widget: "zwave-config", tags: 'zwave', source: ZStick, id: "1", attribute: "zwave_config", name: "ZWave config"}
- grand: { type: device, widget: text, tags: 'portails', source: ZStick, id: "16-37-2-currentValue", name: "Petit Portail ouvert en grand", camera: camera_exterieure }
Sample sample A source derives from the Source class and implements the following methods:
  • createInstance: create an instance of the source, taking into account the requested configuration
  • getParameters: describes the parameters supported by the source
  • doSetAttribute: implements a requested change of value of an attribute of a device managed by the source
  • release: releases a source to free any used resource
  • registerDeviceTypes: a static method to declare which device types are supported by the source
tempo tempo Cette source récupère les informations de couleur de période auprès de l'EDF, pour le jour courant et le lendemain.
Exemple:
sources:
- tempo: { type: tempo }

devices:
- tempo:
- couleur_du_jour : { type: device, widget: tempo-color, tags: 'tempo', source: tempo, id: couleurDuJour, name: "Couleur du jour" }
- couleur_de_demain : { type: device, widget: tempo-color, tags: 'tempo', source: tempo, id: couleurDeDemain, name: "Couleur de demain" }
VoiceByGoogle voice-google
Zibase zibase This source connects to a Zibase device.
Not used anymore as Zodianet company is now dead for years...

Devices

The framework supports a range of devices:

  • device: a generic device with attributes which can be set or get.
  • relay: a particular switch, for which delays can be configured.
  • variable: a special device that contains a value which can be read or written.

Modules

Domoja can be extended through modules, to add new sources, devices, etc. They are essentially npm modules following particular specifications:

  • their name must start with domoja-
  • they must derive from domoModule

Available modules

The following modules are currently available:

Adding a new module

Before importing a module in the config file, you need to make it available. In the domoja directory, use yarn add <themodule>.

If you are developing the module, you might want to add it linked:

$ cd <themodule_dir>
$ yarn link
$ cd <domoja_dir>
$ yarn link <themodule>

How to develop a new module

Developers can develop new Domoja modules. For this, proceed this way:

  • Copy the domoja/modules/sample repository.
  • Update package.json. Note that the module name must start with domoja-
  • You can find in sources/sample.ts a sample source,
  • Link your module using cd <yourmodule_dir>; yarn link and cd <domoja_dir>; yarn link "domoja-<yourmodule>"
  • You can now import your module from the config file.

It can be convenient to setup a small test file and run it with nodemon. This will make it possible to automatically restart the test execution when the module source is modified, and also to debug with Chrome for instance.

Example: File test_module.ts:

import { MyModule } from 'domoja-samplemodule';

let freebox = new MyClass('path', 'some', 'parameters');

and run it with: nodemon --ext ts --watch test-module.ts --watch <module_dir> --exec node --inspect=0.0.0.0 --require ts-node/register test-module.ts

API

Domoja provides a REST/JSON api, which is available through Swagger at /api-docs.

  • GET /app: Retrieve the app data
  • POST /app/demo-mode: Set the app demo mode
  • GET /devices: Retrieves the list of devices
  • GET /devices/{id}: Retrieves a device
  • POST /devices/{id}: Sends a command to a device
  • GET /devices/{id}/snapshot: Get a snapshot from a camera device
  • GET /devices/{id}/stream: Get a stream from a camera device
  • GET /devices/{id}/history: Get the history of a device
  • GET /pages: Retrieves the list of pages

Persistence

Device states can be persisted using MongoDB. By default, all states that are numbers are persisted. Persistence can be specified through the persistence attribute:

persistence: "<persistence-module>:<id>:0:<aggregation mode>:<keep>"

  • <persistence-module> is mongo by default.

  • <id> specifies the id of the device to be persisted. If not specified, then the path of the device is used. Specifying the id is useful if you want to be sure to keep the persisted states even if you change the path of the device.

  • <aggregation mode>: one of raw or aggregate.

  • <keep>: One or two comma-separated durations indicating how long to persist the states. In case aggregate mode is specified, the field contains 2 durations, the first one applies to raw data, while the second one applies to the aggregated data. Durations can be a number (or calculation) of minutes, or a specification of years, months, weeks, days, hours, minutes. A duration of 0 means that data is kept indefinitely.

    Example of durations:

    • 5 years
    • 1 month 2 weeks
    • (2 * 5 + 3) hours + 10 minutes
    • 30

    By default, raw data is kept 1 year and aggregated data 5 years.

User Interface

Domoja comes with a generic user interface based on Ionic. The files are located in the www directory, and generated by domoja-ui.

HomeKit and Siri integration

Domoja can be easily integrated with HomeKit through the great homebridge. Once Homebridge is installed, the best is to create an API key. Then you can add accessories in homebridge's config.json file.

Example for a switch:

{
      "accessory": "HTTP-SWITCH",
      "name": "Lampe préau",
      "switchType": "stateful",
      "pullInterval": 5000,
      "onUrl": {
        "url": "https://XXX/devices/lampes.lampe_preau",
        "method": "POST",
        "body": "command=ON",
        "auth": {
          "username": "XX",
          "password": "XX"
        },
        "headers": {
          "Content-Type": "application/x-www-form-urlencoded"
        }
      },
      "offUrl": {
        "url": "https://XXX/devices/lampes.lampe_preau",
        "method": "POST",
        "body": "command=OFF",
        "auth": {
          "username": "XX",
          "password": "XX"
        },
        "headers": {
          "Content-Type": "application/x-www-form-urlencoded"
        }
      },
      "statusUrl": {
        "url": "https://XXX/devices/lampes.lampe_preau",
        "method": "GET",
        "auth": {
          "username": "XX",
          "password": "XX"
        }
      },
      "statusPattern": "\"ON\""
    }

Example for a sensor:

    {
      "accessory": "HTTP-TEMPERATURE",
      "name": "Température piscine",
      "debug": 1,
      "getUrl": {
        "url": "https://XXX/devices/piscine.temperature",
        "method": "GET",
        "auth": {
          "username": "XX",
          "password": "XX"
        }
      },
      "statusPattern": ".*\"state\":\"\\+?(-?[0-9]+\\.[0-9]*)\"",
      "patternGroupToExtract": 1
    },

Siri is then available on iOS through the Home application.

Development

To test a new domoja package, create it through yarn pack and install it:

$ cd domoja
$ yarn pack --filename /tmp/domoja.$RANDOM.tar.gz


$ cd ../domoja-run
yarn add $(ls -t /tmp/domoja.*.tar.gz | head -1)

To do

  • Ajouter un status general couplé avec Siri: "donne moi le statut de la maison/piscine/tondeuse..."
  • support other attribute than 'state' in devices // normalement ca marche (ex tem avec temp piscine). L'utiliser pour le stick etc
  • support for dev: reload module when changed
  • authentication cookies seem not persisted: when restarting the server, we need to relogin

Issues

  • on config reload it seems that we get mongodb drain
  • sur iOS revenir sur l'app ne fonctionne pas toujours
  • lumieres clignotantes a fixer

Fixed