Monday, January 7, 2019

Building a home automation system based on Domoticz, Xiaomi and Broadlink

The home automation system is a combination of software and hardware devices which handles home routine such as lights, temperature/humidity (microclimate) and entertainment systems (TV, audio etc.) automatically based on some scenarios. This post is about building such system based on Domoticz server, Xiaomi and Broadlink devices. I'll try to cover some topics such as system set up and useful scenarios. Agenda:
  1. Hardware
    1. Gateway
    2. Signaling sensors
    3. Data provider sensors
    4. Controlling sensors
    5. Smart lamps
    6. Server and wifi router
  2. Software
    1. Domoticz
    2. Mini DLNA
  3. Adding devices to Domoticz
    1. Gateway
    2. ZigBee devices
    3. Yeelight Smart bulbs
    4. Broadlink RM 3 mini and RM Pro+ remote controls
    5. Naming convention, groups, and scenes
  4. Network
  5. Scenarios
    1. Network devices state
      1. Are you at home?
    2. Notifications
      1. Motion activity detected
      2. Doors/windows activity detected
      3. Fire detected (high temperature)
      4. ZigBee devices batteries level report
    3. Room occupation
      1. Motion trigger
      2. Windows and doors trigger
    4. Room presence detection
      1. TV
      2. Laptop
      3. Room locking
    5. Light
      1. Room occupation trigger
      2. Button trigger
    6. Microclimate
    7. Alarm clock
  6. Remote access to your Domoticz
  7. Monitoring
    1. Service uptime
    2. Containers state
  8. Conclusion

Hardware


Let's overview hardware which will be used in the system. I distinguish 5 types of devices/sensors for building home automation systems: gateways, signaling sensors, data providers, controlling sensors and "smart" light. Most of Xiaomi/Aqara devices are working over ZigBee protocol, but there are some which are working over wifi like lamps. In a nutshell, a gateway is a central place of a smart home which receives all the data from other sensors/devices which are connected to it. Signaling sensors tell about some event. Data sensors provide environment information. Controlling sensors send commands to home devices. Smart lamps can be controlled over wifi in order to switch light.

There are a lot more devices and sensors on the market for different purposes but in this post, I'll cover only that which are used for my basic scenarios.

Gateway

  • Gateway (Xiaomi, WiFi/ZigBee) - a central point of ZigBee network. Receives events from other sensors. A system can run scenarios based on provided information.

Signaling sensors

  • Motions sensor (Xiaomi or Aqara, ZigBee) - emits a signal when motion is detected.
  • Door/window state sensor (Xiaomi or Aqara, ZigBee) - emits a signal when door/window is being opened or closed.

Data provider sensors

  • Temperature and humidity sensor (Xiaomi or Aqara, ZigBee) - provides current temperature and humidity.
  • Lux sensor (Aqara, ZigBee) - provides current luminosity. Usually, this sensor combined with a motion sensor (Aqara).

Controlling sensors

  • Button (Xiaomi, ZigBee) - sends commands (single click, double click and long click).
  • Socket (Xiaomi or Aqara, ZigBee) - power control.
  • Remote control RM Pro + (Broadlink, WiFi) - sends commands to IR and RF controlled devices such as conditioner, TV etc.
  • Remote control RM Mini (Broadlink, WiFi) - sends commands only to IR controlled devices.

Smart lamps


Server and WiFi router


Of course, we will need a server for running Domoticz and wifi router for connecting Xiaomi Gateway and lamps to Domoticz. In my case, it's an old netbook Samsung N210 with the latest Ubuntu Server 18.04 on board and TP-LINK TL-WR840N respectively.

Software


We will use Domoticz as a home automation server and Mini DLNA as a multimedia server.

Domoticz


As I wanted to use Xiaomi devices (because they're cheaper than others on the market) I've tried their MI Home mobile application for scripting scenarios but it can't be really used for something complex - it's oversimplified. Another reason to not to use MI Home app is that fact that it requires an internet connection (there are some types of scenarios that can't work without internet). Moreover, it depends on Chinese servers and nobody knows what will happen to them tomorrow. So I decided to use Domoticz as a home automation server as it allows to script as complex scenarios as you need and all of them will work without dependency on internet and Xiaomi.

I'm using docker for running Domoticz on my server. Follow this article to install it on your ubuntu server. I also use a docker-compose for configuring containers so I wrote a simple compose file:
version: '3'
services:
  domoticz:
    image: linuxserver/domoticz:stable-4.9700
    network_mode: host
    restart: always
    ports:
      - 1443:1443
      - 6144:6144
      - 8080:8080
    volumes:
      - /home/[user]/docker/domoticz/config:/config
    environment:
      - PGID=1001
      - PUID=1001
      - TZ=Europe/Kiev
    logging:
      driver: "json-file"
      options:
        max-size: "10M"
        max-file: "1"
where [user] is the name of your user. Now we can run Domoticz container:
docker-compose -f [path/to/docker-compose-file].yml up -d

After this web UI will be accessible by [server_ip]:8080 url. Installation might take a couple of minutes so be patient at this step.

Mini DLNA


MiniDLNA is server software with the aim of being fully compliant with DLNA/UPnP clients. The MiniDNLA daemon serves media files (music, pictures, and video) to clients on a network. I wanted to expose media files such as movies and photos into my local network in order to be able to watch them on my TV. I ran Mini DLNA server in a docker container as usual:
version: '3'
services:
  ...
  minidlna:
    network_mode: host
    restart: always
    image: bobrik/minidlna
    volumes:
      - /home/[user]/media:/media
    environment:
      - MINIDLNA_MEDIA_DIR=/media
      - MINIDLNA_LISTENING_IP=[server_ip]
      - MINIDLNA_PORT=8200
      - MINIDLNA_FRIENDLY_NAME=home-minidlna
    logging:
      driver: "json-file"
      options:
        max-size: "10M"
        max-file: "1"
  ...
where [user] is the name of your user and [server_ip] the local IP address of the server where the container is running. Run:
docker-compose -f [path/to/docker-compose-file].yml up -d
once again and now we can put media files to the /home/[user]/media directory on your server and they will be available for accessing from DLNA clients (my TV has one out of the box). On my Android device, I use VLC player which has DLNA support as well.

Adding devices to Domoticz


Gateway


Ok, we have Domoticz installed and running. It's time to add Xiaomy Gateway to the server. First, you need to enable "local area network communication protocol" on the gateway (Mi Home app is needed). Second, you need to actually add a device to Domoticz. I won't describe here how to do both these things as there is an official "how-to" article at Domoticz wiki. Follow the steps described there.

ZigBee devices


All ZigBee devices (motion sensors, window/door sensors, temperature/humidity sensors, buttons, sockets etc) will be added to Domoticz automatically if they are assigned to the gateway. Read device's instruction on how to do this but in general, the process is: click 3 times on the gateway's button then press and hold the device's button with a help of a pin for a few seconds. After that gateway will play some Chinese phrase and the device's light will blink 3 times which means that it was added to gateway.

Yeelight Smart Bulbs


For adding lamps into Domoticz we will need Yeelight app. It's only needed for enabling "LAN control" option. First, add your lamp in the app, go to the settings of your lamp and enable "LAN control":



Second, follow this article from Domoticz wiki in order to add lamps to your server.


Seems like there is no integration with Broadlink remote controls in Domoticz out of the box but there is a workaround for this. We will use domoticz virtual switch which will trigger python script for communication with remote controls.

First, we need python-broadlink package for working with broadlink devices. Clone (you need git installed) it somewhere on your host machine:
cd /home/[user]/[project_path]
git clone git@github.com:mjg59/python-broadlink.git
After this step you will have a /home/[user]/[project_path]/python-broadlink directory. Mount it to our container (update existing [path/to/docker-compose-file].yml file):
version: '3'
services:
  domoticz:
    ...
    volumes:
      ...
      - /home/[user]/docker/domoticz/python-broadlink:/python-broadlink
    ...
and run:
docker-compose -f [path/to/docker-compose-file].yml up -d

Second, we need to install python in Domoticz container. Ideally, we must build a new image on top of existing linuxserver/domoticz:stable-4.9700 with python inside but let's skip this for the sake of this post. So, go inside your container:
docker exec -ti [domoticz_container_id] bash
where [domoticz_container_id] is an id of a running container which can be found by:
docker ps
command. Then install python:
apk add python3

Third, install the broadlink package:
cd /python-broadlink
python3 setup.py install

Fourth, we need to come up with a python script for two things:
  1. For reading learned codes (buttons) from the RM device (you can "teach" RM mini and RM Pro+ the codes of your TV, conditioner etc; you need e-Control mobile app for doing this)
  2. Sending codes to tv, conditioner etc.
There are two ways how you can get learned code from your device: RM Bridge way or custom script. I prefer the second option because the first one didn't work with RM Pro+ device at least when I tried it. RM devices work in a way that they keep last learned code in own memory. Therefore, the whole process consists of two parts: teach the code and then read it. Repeat this cycle until you have all the codes for all devices you want to control. With a help of installed python package, we can read this code and use it. A script both for reading and sending codes might look like (save it as /home/[user]/docker/domoticz/python-broadlink/broadlink_remote_control.py):
#!/usr/bin/python

import broadlink
import codecs
import sys
import time

if len(sys.argv) != 3:
  sys.exit();

deviceName = sys.argv[1];
commandType = sys.argv[2];
commandData = "";
device = "";

if deviceName == "rm_pro_plus":
  device = broadlink.rm(host=("[ip_address]",80), mac=bytearray.fromhex("[mac_address]"));

if deviceName == "rm_mini":
  device = broadlink.rm(host=("[ip_address]",80), mac=bytearray.fromhex("[mac_address]"));

if device == "":
  sys.exit();

device.auth();

if commandType == "check_data":
  print(device.check_data());
  sys.exit();

if commandType == "living_room_tv_switch":
  commandData = codecs.decode("2600580000012...", 'hex');

if commandType == "living_room_conditioner_on_for_cooling":
  commandData = codecs.decode("2600e60078341...", 'hex');

if commandType == "living_room_conditioner_on_for_heating":
  commandData = b'&\x00\xe6\x00...';

if commandType == "living_room_conditioner_off":
  commandData = codecs.decode("2600e60078331...", 'hex');

if commandData == "":
  sys.exit();

device.send_data(commandData);
where [ip_address] and [mac_address] are ip and mac addresses of your device(s) respectively. The idea is that you can control any broadlink device you have and send any command defined in the script: python3 broadlink_remote_control.py [device_name] [command]. For example, now inside domoticz container you can read learned code from the RM Pro+ device:
cd /python-broadlink/custom_scripts
python3 broadlink_remote_control.py rm_pro_plus check_data
This will print previously learned code (might look like a sequence of bytes (b'...') or like hex, both variants are shown in the script). To send commands to TV and conditioner run:
cd /python-broadlink/custom_scripts
python3 broadlink_remote_control.py rm_pro_plus living_room_tv_switch
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_on_for_heating
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_on_for_cooling
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_off
Extend the script if you need more/less devices/commands.

The final step, we need to add "Dummy hardware" as a hardware type in Domoticz. This allows users to create a virtual "device", for example, virtual button for enabling/disabling your TV, and assign invocation of phyton script which will actually do "send TV ON/OFF command with a help of my Broadlink remote" actions. For example, I've added one virtual switch for my TV (with "on" and "off" states) and one virtual selector for my conditioner (with "on for heating", "on for cooling" and "off" states). After adding we can use these virtual switches in our domoticz scenarios.

Naming convention, groups and scenes


All my devices in domoticz have names which correspond pattern [room_name]-[vendor]-[device_type]. For example, living_room-xiaomi-gateway or living_room-lg-tv. I know that this is an antipattern to put namespace into variable name but in this case it seems reasonable.


Naming convention

It also makes sense to put some devices into groups or scenes, in this case, name pattern is [room_name]-[group|scene]-[type]. For example, I have 4 lamps in my living room which are used as a base light so I've put them into one group called living_room-group-light. It allows you to turn on/off entire group of devices instead of programming each one. I also have these lamps added into 3 scenes: living_room-scene-light-nightliving_room-scene-light-40 and living_room-scene-light-100. These are for different levels of light (night mode, 40%, and 100% light). We will use these scenes and groups in our scenarios.

Groups and scenes

Network


Each device (gateway, lamp, wifi remote control etc.) in the flat's LAN must have a static IP. If you're using one of TP-LINK routers you can go to the DHCP -> Address Reservation page and assign needed IP address to device's MAC address (preliminarily you need to enable DHCP on your router if it was disabled before). On other routers, this can be set up in a similar way, this shouldn't be an issue.

Each device in a home network has assigned static ip

Next step is setting up Local networks in Domoticz. For example, requests from my laptop and from localhost are allowed to access Domoticz APIs without authentication. This is needed for sending laptop's wifi signal strength and for requests from cron container directly to Domoticz. I'll tell about this later in the next chapter.

Local networks

Scenarios


I'll show examples of scenarios which I personally use. All of them are written in Lua and based on dzEvents - it's an event-based API which allows you to react on triggers such as device's state changes, timers, variables values changes etc. I strongly recommend you to read the article about dzEvents and Domoticz API URLs first because it's what we will use heavily.

I use shared helper functions for writing scripts. It means I have a global_data.lua file where I placed all the needed functions which are accessible through the domoticz.helpers object. I'll show this later.

In Domoticz to start writing dzEvents scripts go to the "Settings -> Advanced -> Events" page:

User-defined scripts

Network devices state


Task: we might want to be able to check network devices (cell phone, laptop, TV etc.) state (on/off) and react on it.

Solution: all devices in our home network have static IPs. It means we can ping a device's IP address, check result and make a conclusion if it's on or off. We need three things: Domoticz variable (we will react on its value changes), cron container and a bash script which will ping a device. Let's consider an example with my cell phone.

First, create an integer Domoticz variable ONEPLUS3_STATE which will contain 0 or 1 values (on/off state). Second, write a simple bash script to ping your device's IP address and update Domoticz variable. Put it somewhere on your server (/home/[user]/docker/bash/device_state/oneplus3.sh):
#!/bin/bash

if ping [PHONE_IP_ADDRESS] -c1 -w1 | grep -q ', 0% packet loss'; then
 curl -s "http://[DOMOTICZ_SERVER_IP]:8080/json.htm?type=command&param=updateuservariable&vname=ONEPLUS3_STATE&vtype=INTEGER&vvalue=1" > /dev/null
else
 curl -s "http://[DOMOTICZ_SERVER_IP]:8080/json.htm?type=command&param=updateuservariable&vname=ONEPLUS3_STATE&vtype=INTEGER&vvalue=0" > /dev/null
fi
So, the idea is to set Domoticz variable value to 1 if ping succeeded (0% packet loss) or to 0 otherwise. The final step is to run this script periodically by cron. Add cron container to your compose file:
version: '3'
services:
  ...
  cron:
    image: fluffydocker/cron-docker-image-curl
    network_mode: host
    restart: always
    command: ["start-cron", "\\* \\* \\* \\* \\* /device_state/oneplus3.sh;"]
    volumes:
      - /home/[user]/docker/bash/device_state:/device_state
    logging:
      driver: "json-file"
      options:
        max-size: "10M"
        max-file: "1"
  ...
and run:
docker-compose -f [path/to/docker-compose-file].yml up -d
Script will be run every minute (`* * * * *` cron rule). Now you are able to react to your network device state changes:
return {
    on = {
        variables = {
            'ONEPLUS3_STATE'
        }
    },
    execute = function(domoticz, variable)
        ...
    end
}
Are you at home?
Task: detect if you (your mobile phone) at home and if you aren't then turn off lights, sockets, TV etc.

Solution: we need to create an integer PRESENCE Domoticz variable which must be set as 1 when the phone at home and as 0 otherwise. This can be done like described above (handle_devices script):
 return {
    on = {
        variables = {
            ...

            'ONEPLUS3_STATE',

            ...
        }
    },
    execute = function(domoticz, variable)
        if (variable.name == 'ONEPLUS3_STATE') then
            if (domoticz.helpers.isOnePlus3Enabled(domoticz)) then
                domoticz.helpers.gotHome(domoticz);
            else
                ...


                domoticz.helpers.leftHome(domoticz);


                ...
            end
        end

        ...
    end
}
Functions gotHome and leftHome only switch PRESENCE value. The only thing we need to do is to react to its value and disable lights, sockets etc when it's 0 (handle_presence script):
return {
    on = {
        variables = {
            'PRESENCE'
        }
    },
    execute = function(domoticz, variable)
        if (domoticz.helpers.isAnyBodyHome(domoticz) == false) then
            domoticz.helpers.switchOffEverything(domoticz);
        end
    end
}
Scripts above a hight-level ones. Helper functions look like:
return {
    helpers = {
        ...

        disableConditionerInLivingRoom = function(domoticz)
            domoticz.log('Disable conditioner in living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-liberton-conditioner').switchSelector(10);
        end,

        ...

        switchOffEverything = function(domoticz)
            domoticz.log('Disable everything.', domoticz.LOG_INFO);

            ...

            domoticz.helpers.disableBedRoomLight(domoticz);
            domoticz.helpers.disableHallRoomLight(domoticz);
            domoticz.helpers.disableLivingRoomLight(domoticz);
            domoticz.helpers.disableBathRoomLight(domoticz);
            domoticz.helpers.disableKitchenRoomLight(domoticz);

            domoticz.helpers.disableTvInLivingRoom(domoticz);
            domoticz.helpers.disableXiaomiGatewayLightInLivingRoom(domoticz);
            domoticz.helpers.disableConditionerInLivingRoom(domoticz);

            domoticz.helpers.switchOffAllSockets(domoticz);

            ...
        end,

        disableXiaomiGatewayLightInLivingRoom = function(domoticz)
            domoticz.log('Disable Xiaomi gateway light in living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-xiaomi-gateway_rgb').switchOff();
        end,

        ...

        disableBedRoomLight = function(domoticz)
            domoticz.log('Disable bed room light.', domoticz.LOG_INFO);

            domoticz.groups('bed_room-group-light').switchOff();
        end,

        disableLivingRoomLight = function(domoticz)
            domoticz.log('Disable living room light.', domoticz.LOG_INFO);

            domoticz.groups('living_room-group-light').switchOff();
        end,

        disableHallRoomLight = function(domoticz)
            domoticz.log('Disable hall room light.', domoticz.LOG_INFO);

            domoticz.groups('hall_room-group-light').switchOff();
        end,

        disableBathRoomLight = function(domoticz)
            domoticz.log('Disable bath room light.', domoticz.LOG_INFO);

            domoticz.groups('bath_room-group-light').switchOff();
        end,

        disableKitchenRoomLight = function(domoticz)
            domoticz.log('Disable kitchen room light.', domoticz.LOG_INFO);

            -- domoticz.devices('kitchen_room-light').switchOff();
        end,

        ...

        isOnePlus3Enabled = function(domoticz)
            return domoticz.variables('ONEPLUS3_STATE').value == 1;
        end,

        ...

        disableTvInLivingRoom = function(domoticz)
            domoticz.log('Disable TV in living room.', domoticz.LOG_INFO);

            if (domoticz.helpers.isTvInLivingRoomEnabled(domoticz)) then
                domoticz.devices('living_room-lg-tv').switchSelector(10);
            end
        end,

        ...

        switchOffAllSockets = function(domoticz)
            domoticz.log('Disable all the sockets.', domoticz.LOG_INFO);

            domoticz.groups('all-group-socket').switchOff();
        end,

        ...

        isAnyBodyHome = function(domoticz)
            return domoticz.variables('PRESENCE').value == 1;
        end,

        leftHome = function(domoticz, force)
            if (domoticz.helpers.isAnyBodyHome(domoticz) or force) then
                domoticz.variables('PRESENCE').set(0);
            end
        end,

        gotHome = function(domoticz, force)
            if (domoticz.helpers.isAnyBodyHome(domoticz) == false or force) then
                domoticz.variables('PRESENCE').set(1);
            end
        end,
    }
}
So, now we have a global presence flag which is controlled by a network device (phone) which can be used in any other scenarios.

Notifications


Domoticz supports a variety of notification systems. I personally use Pushover but you can use any other. Go to the Settings -> Notification and enable needed integration:

Enabled Pushover integration

First, we need to get API credentials from you integration, in my case I've created Pushover account, created there an application and got user key and API key. Just put them and hit "Test" button - test notification will be sent to your authorized mobile device.

Second, we need a function which we can use in our scenarios. So, I have a sendNotification function:
return {
    helpers = {
        ...
        sendNotification = function(domoticz, subsystem, subject, body)
            domoticz.openURL('http://127.0.0.1:8080/json.htm?type=command&param=sendnotification&subsystem=' .. subsystem .. '&subject=' .. subject:gsub('%s', '%%20') .. '&body=' .. body:gsub('%s', '%%20'));
        end,
        ...
    }
}
It uses Domoticz API URLs for interracting with notification subsystems (Pushover, telegram etc.). In Network chapter we set up Domoticz in a way that it doesn't require authorization for requests from localhost - this is why we needed it, to access Domoticz APIs directly.

Now we can use this helper function in other scenarios like:
  domoticz.helpers.sendNotification(domoticz, 'pushover', 'Notification title', 'Notification body');
Motion activity detected
Task: send a mobile notification when motion activity is detected when you aren't at home.

Solution: react to motion sensors and send notification. We can use a isAnyBodyHome function defined in Are you at home? scenario (handle_motion script):
return {
    on = {
        devices = {
            'living_room-xiaomi-motion_sensor',
            'bed_room-xiaomi-motion_sensor',
            'hall_room-xiaomi-motion_sensor',
            'bath_room-xiaomi-motion_sensor',
            'kitchen_room-xiaomi-motion_sensor'
        }
    },
    execute = function(domoticz, switch)
        ...
        
        if (domoticz.helpers.isAnyBodyHome(domoticz) == false) then
            domoticz.helpers.sendNotification(domoticz, 'pushover', 'Motion activity is detected', 'Sensor: ' .. switch.name .. '. State: ' .. switch.state);
        end
    end
}
Doors/windows activity detected
Task: send a mobile notification when door/window activity is detected when you aren't at home.

Solution: react to door/window sensors and send notification (handle_window_and_door script):
 return {
    on = {
        devices = {
            'hall_room-xiaomi-door_sensor',
            'bed_room-xiaomi-window_sensor',
            'living_room-xiaomi-window_sensor',
            'kitchen_room-xiaomi-window_sensor',
            'bed_room-xiaomi-door_sensor',
            'bath_room-xiaomi-door_sensor',
            'kitchen_room-xiaomi-door_sensor'
        }
    },
    execute = function(domoticz, switch)
        ...

        if (domoticz.helpers.isAnyBodyHome(domoticz) == false) then
            domoticz.helpers.sendNotification(domoticz, 'pushover', 'Windows/doors activity is detected', 'Sensor: ' .. switch.name .. '. State: ' .. switch.state);
        end
    end
}
Fire detected (high temperature)
Task: send a mobile notification when it's too hot at home.

Solution: react to temperature sensor and send notification when temperature is higher than threshold defined as Domoticz variable (handle_temperature script):
return {
    on = {
        devices = {
            'living_room-xiaomi-temperature'
        }
    },
    execute = function(domoticz, device)
        ...
        
        local TEMPERATURE_HOT_ALARM = domoticz.variables('TEMPERATURE_HOT_ALARM').value;
        local temperature = domoticz.helpers.getTemperatureInLivingRoom(domoticz);
        
        if (temperature >= TEMPERATURE_HOT_ALARM) then
            domoticz.helpers.sendNotification(domoticz, 'pushover', 'Temperature', 'Too hot! Temperature: ' .. temperature);
        end
    end
}
ZigBee devices batteries level report
Task: send a mobile notification when ZigBee devices have too low batteries level.

Solution: check baterries level on a daily basis and send notification in case some devices have battery level lower than a threshold defined in Domoticz variable (handle_battery_level script):
return {
    on = {
        timer = {
            'every day at 12:00'
        }
    },
    execute = function(domoticz)
        local BATTERY_THRESHOLD = domoticz.variables('BATTERY_THRESHOLD').value;
        
        local message = '';
        local pushMessage = '';
        
        -- First filter on low battery level.
        local lowOnBat = domoticz.devices().filter(function(device)
            local level = device.batteryLevel;
            
            return (
                    level ~= nil and
                    level <= BATTERY_THRESHOLD
                );
        end);
        
        -- Then loop over the results.
        lowOnBat.forEach(function(lowDevice)
            message = message .. 'Device ' .. lowDevice.name .. ' is low on batteries (' .. lowDevice.batteryLevel .. '), ';
            pushMessage = pushMessage .. lowDevice.name .. ' (' .. lowDevice.batteryLevel .. '%)';
        end)
        
        if (message ~= '') then
            domoticz.log('Low battery warning: ' .. message, domoticz.LOG_INFO);
        end
        
        if (pushMessage ~= '') then
            domoticz.helpers.sendNotification(domoticz, 'pushover', 'Low batteries', pushMessage);
        end
    end
}

Room occupation


I tried using only motion sensors for detecting "someone in the room" event for turning light on but then I realised that not only motion sensors mean that room is occupied. For example, an event from door/window sensors can mean the same. So, seems like it's better to have one artificial event like "room occupied" which can be triggered by motion, door or something else you can come up with. Let's consider an example with the hall room.

Task: create an event for room occupation. A room must be occupied for N minutes (we must be able to tune this period for each room). After N minutes room must be released.

Solution: as usual "event" is a Domoticz variable which value can be listened. So, we need to create a HALL_ROOM_IS_OCCUPIED integer variable (which is a flag of room occupation) and HALL_ROOM_LIGHT_TIME (which means how many minutes room must be occupied). We also need a handle_timer script (which will release room occupations when needed) therefore we need a HALL_ROOM_TIMER variable as well (it will contain current ticked minutes).

First of all, let's look at occupyHallRoom and releaseHallRoomOccupation functions which only change occupation variable state and reset timer current tick value:
return {
    helpers = {
        ...

        occupyHallRoom = function(domoticz)
            domoticz.log('Occupy hall room.', domoticz.LOG_INFO);

            ...

            if (domoticz.helpers.isHallRoomOccupied(domoticz) == false) then
                domoticz.variables('HALL_ROOM_IS_OCCUPIED').set(1);
            end

            domoticz.variables('HALL_ROOM_TIMER').set(0);
        end,

        ...

        isHallRoomOccupied = function(domoticz)
            return domoticz.variables('HALL_ROOM_IS_OCCUPIED').value == 1;
        end,

        ...

        releaseHallRoomOccupation = function(domoticz)
            domoticz.log('Release hall room occupation.', domoticz.LOG_INFO);

            domoticz.variables('HALL_ROOM_IS_OCCUPIED').set(0);
            domoticz.variables('HALL_ROOM_TIMER').set(0);
        end,

        ...
    }
}
Ok, now we need to implement timer which will first, increase current tick value and second, check current tick value and release room if occupation time is over (handle_timer script):
return {
 on = {
        timer = {
            'every minute'
        }
    },
    execute = function(domoticz, switch)
        ...

        if (domoticz.helpers.isHallRoomOccupied(domoticz)) then
            domoticz.variables('HALL_ROOM_TIMER').set(domoticz.variables('HALL_ROOM_TIMER').value + 1);
        end

        ...

        if (domoticz.variables('HALL_ROOM_TIMER').value == domoticz.variables('HALL_ROOM_LIGHT_TIME').value) then
            domoticz.helpers.releaseHallRoomOccupation(domoticz);
        end

        ...
    end,
}
Now we have a "room occupation" mechanism which can be used in further scenarios. But first let's fire this event when needed.
Motion trigger
Task: occupy room when there is a motion detected.

Solution: fire occupation event when motion sensor fires motion event (handle_motion script):
return {
    on = {
        devices = {
            'living_room-xiaomi-motion_sensor',
            'bed_room-xiaomi-motion_sensor',
            'hall_room-xiaomi-motion_sensor',
            'bath_room-xiaomi-motion_sensor',
            'kitchen_room-xiaomi-motion_sensor'
        }
    },
    execute = function(domoticz, switch)
        if (switch.state == 'On') then
            ...
            
            if (switch.name == 'hall_room-xiaomi-motion_sensor') then
                domoticz.helpers.occupyHallRoom(domoticz);
            end
            
            ...
        end
        
        ...
    end
}
Windows and doors trigger
Task: occupy room when there is a door being opened.

Solution: fire occupation event when door sensor fires event (handle_window_and_door script):
 return {
    on = {
        devices = {
            'hall_room-xiaomi-door_sensor',
            'bed_room-xiaomi-window_sensor',
            'living_room-xiaomi-window_sensor',
            'kitchen_room-xiaomi-window_sensor',
            'bed_room-xiaomi-door_sensor',
            'bath_room-xiaomi-door_sensor',
            'kitchen_room-xiaomi-door_sensor'
        }
    },
    execute = function(domoticz, switch)
        ...

        if (switch.name == 'hall_room-xiaomi-door_sensor') then
            domoticz.helpers.occupyHallRoom(domoticz);

            ...
        end

        ...
    end
}

Room presence detection


Room occupation is nice but there might be situations when the room is occupied, somebody is in the room and he/she doesn't move/open door/window for N minutes (for instance he/she reads, watching TV etc). In this case, a room will be released and all the actions assigned to this event will be triggered (for example, the light will be turned off). It's not correct because the room technically is still occupied. So, seems like we need something which will stop occupation timer. For example, if TV is enabled in a living room, if a laptop enabled and is in the living room or manual room locking (by button). Let's consider all of these cases in context of living room.
TV
Task: do not release a room occupation when TV is turned on in the room.

Solution: mostly every modern TV has a network interface so it's easy to track its state by pinging it as described in Network devices state chapter. So, the only thing we need to do is to not to "tick" room occupation timer if TV is turned on (handle_timer script):
return {
 on = {
        timer = {
            'every minute'
        }
    },
    execute = function(domoticz, switch)
        ...

        if (domoticz.helpers.isLivingRoomOccupied(domoticz) and domoticz.helpers.isPresenceInLivingRoomDetected(domoticz) == false) then
            domoticz.variables('LIVING_ROOM_TIMER').set(domoticz.variables('LIVING_ROOM_TIMER').value + 1);
        end

        ...
    end,
}
where isPresenceInLivingRoomDetected helper function looks like:
return {
    helpers = {
        ...

        isPresenceInLivingRoomDetected = function(domoticz)
            return (
                domoticz.helpers.isTvInLivingRoomEnabled(domoticz)

                ...
            );
        end,

        ...

        isTvInLivingRoomEnabled = function(domoticz)
            return domoticz.variables('LIVING_ROOM_TV_STATE').value == 1;
        end,

        ...
    }
}
Laptop
Task: do not release a room occupation when a laptop is turned on and is in the room.

Solution: the first part of this task has the same solution as in the previous task, we just need to update isPresenceInLivingRoomDetected function:
return {
    helpers = {

        ...

        isPresenceInLivingRoomDetected = function(domoticz)
            return (
                domoticz.helpers.isTvInLivingRoomEnabled(domoticz) or
                domoticz.helpers.isLaptopInLivingRoom(domoticz)

                ...
            );
        end,

        ...

        isLaptopInLivingRoom = function(domoticz)
            return (domoticz.helpers.isLaptopEnabled(domoticz) and domoticz.variables('LAPTOP_WIFI_STRENGTH').value > 95);
        end,

        ...

        isLaptopEnabled = function(domoticz)
            return domoticz.variables('LAPTOP_STATE').value == 1;
        end,
    }
}
where LAPTOP_WIFI_STRENGTH is a Domoticz variable which contains a percentage of wifi signal strength. If it's > 95 then it means the laptop is near to wifi router and most probably it is in the living room. If this value < 95 then the laptop is in the bedroom. These thresholds will vary in different apartments but the idea should be clear.

But how we get laptop's wifi signal strength? Well, on our laptop we need to add a cron job which will get wifi signal strength and update Domoticz variable value via Domoticz API. It's run every minute on my laptop and looks like (to add cron job type crontab -e):
* * * * * curl -s "http://[SERVER_IP]:[SERVER_PORT]/json.htm?type=command&param=updateuservariable&vname=LAPTOP_WIFI_STRENGTH&vtype=INTEGER&vvalue=$(awk 'NR==3 {print int(($3/70)*100)}' /proc/net/wireless)" > /dev/null
The most interesting part is:
$(awk 'NR==3 {print int(($3/70)*100)}' /proc/net/wireless)
where we get signal strength from /proc/net/wireless system file and map it to a percentage.
Room locking
Sometimes it's useful to lock room manually. For example, when you're going to bed and you don't want the light to be enabled when you accidentally move at night.

Task: do not release room occupation when it's manually locked.

Solution: first, we need to add a living_room-lock switch to Domoticz which can have only two states: on/off. I've added a switch because it's can be controlled from Domoticz UI manually when needed but you can use Domoticz variables if you want.

The second step is to lock room (turn on the switch) by button click (handle_button script):
 return {
    on = {
        devices = {
            'bed_room-xiaomi-switch',
            'hall_room-xiaomi-switch',
            'living_room-xiaomi-switch'
        }
    },
    execute = function(domoticz, switch)
        if (switch.state == 'Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                domoticz.helpers.lockLivingRoom(domoticz);

                ...
            end

            ...
        end
    
        if (switch.state == 'Double Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                domoticz.helpers.lockLivingRoom(domoticz);

                ...
            end

            ...
        end

        if (switch.state == 'Long Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                domoticz.helpers.lockLivingRoom(domoticz);

                ...
            end

            ...
        end
    end
}
We also need helper functions like lockLivingRoom, unlockLivingRoom and isLivingRoomLocked:
return {
    helpers = {
        ...

        lockLivingRoom = function(domoticz)
            domoticz.log('Lock living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-lock').switchOn();
        end,

        unlockLivingRoom = function(domoticz)
            domoticz.log('Unlock living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-lock').switchOff();
        end,

        ...

        isLivingRoomLocked = function(domoticz)
            return domoticz.devices('living_room-lock').state == 'On';
        end,

    }
}
The third step, we need to update isPresenceInLivingRoomDetected function:
return {
    helpers = {

        ...

        isPresenceInLivingRoomDetected = function(domoticz)
            return (
                domoticz.helpers.isTvInLivingRoomEnabled(domoticz) or
                domoticz.helpers.isLaptopInLivingRoom(domoticz) or
                domoticz.helpers.isLivingRoomLocked(domoticz)
            );
        end,

        ...
    }
}
For now, the room will not be released until there are TV enabled or laptop is in the room or room is locked manually.

Finally, we also must unlock room by some event. Of course, we can do it manually from Domoticz UI but in my case room is being unlocked when there is a motion in a hall room (hall room is being occupied) - I went out of the room which means that most probably I want to release this room:
return {
    helpers = {
        ...

        occupyHallRoom = function(domoticz)
            ...

            domoticz.helpers.unlockLivingRoom(domoticz);

            ...
        end,

        ...
    }
}

Light


There must be two ways of how we can enable/disable light: automatically (by room occupation event) and manually (by a button event). Let's consider both.
Room occupation trigger
Task: enable room light when it is being occupied: in the morning enable light only if it's dark in the room; in the afternoon enable daylight mode (100%); at night enable nightlight mode (10%). Also light must be disabled automatically.

Solution: we already have a room occupation event so we can easily build such behavior (handle_occupation script):
 return {
    on = {
        variables = {
            'HALL_ROOM_IS_OCCUPIED',
            'BED_ROOM_IS_OCCUPIED',
            'LIVING_ROOM_IS_OCCUPIED',
            'BATH_ROOM_IS_OCCUPIED',
            'KITCHEN_ROOM_IS_OCCUPIED'
        }
    },
    execute = function(domoticz, variable)
        ...

        if (variable.name == 'LIVING_ROOM_IS_OCCUPIED') then
            if (domoticz.helpers.isLivingRoomOccupied(domoticz)) then
                -- Light is already enabled.
                if (domoticz.helpers.isLivingRoomLightEnabled(domoticz)) then
                    return;
                end;

                if (domoticz.time.hour > 6 and domoticz.time.hour < 15) then
                    if (domoticz.helpers.isItDarkInLivingRoom(domoticz)) then
                        domoticz.helpers.enableLivingRoomDayLight(domoticz);
                    end
                end

                if (domoticz.time.hour >= 15 and domoticz.time.hour <= 24) then
                    domoticz.helpers.enableLivingRoomDayLight(domoticz);
                end

                if (domoticz.time.hour >= 0 and domoticz.time.hour <= 6) then
                    domoticz.helpers.enableLivingRoomNightLight(domoticz);
                end
            else
                domoticz.helpers.disableLivingRoomLight(domoticz);
            end
        end

        ...
    end
}
Helper functions are:
return {
    helpers = {
        ...

        enableLivingRoomDayLight = function(domoticz, force)
            domoticz.log('Enable living room day light.', domoticz.LOG_INFO);

            domoticz.helpers.enableLight(domoticz, 'group', 'living_room-group-light');
        end,

        ...

        enableLivingRoomNightLight = function(domoticz, force)
            domoticz.log('Enable living room night light.', domoticz.LOG_INFO);

            domoticz.helpers.enableLight(domoticz, 'scene', 'living_room-scene-light-night');
        end,

        enableLight = function(domoticz, type, group1)
            if (type == 'scene') then
                domoticz.scenes(group1).switchOn();
            elseif (type == 'group') then
                domoticz.groups(group1).switchOn();
            end
        end,

        ...

        disableLivingRoomLight = function(domoticz)
            domoticz.log('Disable living room light.', domoticz.LOG_INFO);

            domoticz.groups('living_room-group-light').switchOff();
        end,

        ...

        isLivingRoomLightEnabled = function(domoticz)
            return
                domoticz.devices('living_room-xiaomi-lamp_1').level ~= 99 or
                domoticz.devices('living_room-xiaomi-lamp_2').level ~= 99 or
                domoticz.devices('living_room-xiaomi-lamp_3').level ~= 99 or
                domoticz.devices('living_room-xiaomi-lamp_4').level ~= 99;
        end,

        isItDarkInLivingRoom = function(domoticz)
            return tonumber(domoticz.devices('living_room-xiaomi-lux_sensor').state) < 15;
        end,

        ...
    }
}
A few notes: enableLight function trigggers groups (it's handy to enable all the lamps to 100% light mode by group) or scenes (it's handy to enable predefined scene - 10% light mode); isLivingRoomLightEnabled function - I don't know why but when my wifi lamps are disabled they report 99 value, so I use this value as a "disabled" state; isItDarkInLivingRoom function says that it's dark in the room when luxometer reports value < 15, but you can tune it as you want.
Button trigger
Task: control room light bu button: single click - switch light (on(daylight)/off); double click - turn on nightlight mode; long click - turn off light in all rooms (going to bed mode).

Solution: react to libing room button events (handle_button script):
 return {
    on = {
        devices = {
            ...

            'living_room-xiaomi-switch'
        }
    },
    execute = function(domoticz, switch)
        if (switch.state == 'Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                ...

                if (domoticz.helpers.isLivingRoomLightEnabled(domoticz)) then
                    domoticz.helpers.disableLivingRoomLight(domoticz);
                else
                    domoticz.helpers.enableLivingRoomDayLight(domoticz);
                end
            end

            ...
        end
    
        if (switch.state == 'Double Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                ...

                domoticz.helpers.enableLivingRoomNightLight(domoticz);
            end

            ...
        end

        if (switch.state == 'Long Click') then
            ...

            if (switch.name == 'living_room-xiaomi-switch') then
                ...

                domoticz.helpers.disableBedRoomLight(domoticz);
                domoticz.helpers.disableLivingRoomLight(domoticz);
                domoticz.helpers.disableHallRoomLight(domoticz);
                domoticz.helpers.disableKitchenRoomLight(domoticz);
                domoticz.helpers.disableBathRoomLight(domoticz);
            end

            ...
        end
    end
}

Microclimate


Task: maintain temperature and humidity in the apartment at the comfort level (cooling in summer and heating in winter). I don't have a humidifier yet but there are no problems to integrate it in the future (humidifier must be with a remote control function - IR or RF). Comfort temperature and humidity must be set up as Domoticz variables for tunning if needed. Conditioner and humidifier must work only when all the windows are closed. When some of them are being opened - disable climate control.

Solution: this can be built with temperature/humidity sensor, broadlink remote control, and air conditioner. In Broadlink RM 3 mini and RM Pro+ remote controls chapter we found out how to integrate Broadlink devices into Domoticz and talked about "Dummy" Domoticz switches, which can be used for running custom scripts. Let's take a look at a high-level helper adjustWeather function and find out how it works: 
return {
    helpers = {
        adjustWeather = function(domoticz, temperature, humidity)
            domoticz.log('Adjust weather.', domoticz.LOG_INFO);

            domoticz.helpers.adjustTemperatureInLivingRoom(domoticz);
            -- TODO: domoticz.helpers.adjustHumidityInLivingRoom(domoticz);
        end,

        adjustTemperatureInLivingRoom = function(domoticz, temperature, humidity)
            domoticz.log('Adjust temperature in living room.', domoticz.LOG_INFO);

            local TEMPERATU.RE_HOT_THRESHOLD = domoticz.variables('TEMPERATURE_HOT_THRESHOLD').value;
            local TEMPERATURE_COLD_THRESHOLD = domoticz.variables('TEMPERATURE_COLD_THRESHOLD').value;
            local temperature = domoticz.helpers.getTemperatureInLivingRoom(domoticz);

            if (domoticz.helpers.areAllWindowsAndDoorsClosed(domoticz)) then
                if (temperature > TEMPERATURE_HOT_THRESHOLD) then
                    domoticz.helpers.enableConditionerForCoolingInLivingRoom(domoticz);
                end

                if (temperature < TEMPERATURE_COLD_THRESHOLD) then
                    domoticz.helpers.enableConditionerForHeatingInLivingRoom(domoticz);
                end
            else
                domoticz.helpers.disableConditionerInLivingRoom(domoticz);
            end
            
            if (temperature >= (TEMPERATURE_COLD_THRESHOLD + 1) and temperature <= (TEMPERATURE_HOT_THRESHOLD - 1)) then
                domoticz.helpers.disableConditionerInLivingRoom(domoticz);
            end
        end,

        getTemperatureInLivingRoom = function(domoticz)
            return tonumber(domoticz.devices('living_room-xiaomi-temperature').state);
        end,

        enableConditionerForCoolingInLivingRoom = function(domoticz)
            domoticz.log('Enable conditioner for cooling in living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-liberton-conditioner').switchSelector(0);
        end,

        enableConditionerForHeatingInLivingRoom = function(domoticz)
            domoticz.log('Enable conditioner for heating in living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-liberton-conditioner').switchSelector(20);
        end,

        disableConditionerInLivingRoom = function(domoticz)
            domoticz.log('Disable conditioner in living room.', domoticz.LOG_INFO);

            domoticz.devices('living_room-liberton-conditioner').switchSelector(10);
        end,

        areAllWindowsAndDoorsClosed = function(domoticz)
            return (
                domoticz.devices('bed_room-xiaomi-window_sensor').state == 'Closed' and
                domoticz.devices('living_room-xiaomi-window_sensor').state == 'Closed' and
                domoticz.devices('kitchen_room-xiaomi-window_sensor').state == 'Closed' and
                domoticz.devices('hall_room-xiaomi-door_sensor').state == 'Closed'
            );
        end,

        ...
    }
}
This function does one small thing - runs temperature and (TODO) humidity checks and enables devices if needed. The adjustTemperatureInLivingRoom function:
  1. Reads "cold" and "hot" thresholds from Domoticz variables
  2. Checks if all windows are closed
    1. If so - checks current temperature against thresholds
      1. If it's cold - enables heating
      2. If it's hot - enables cooling
    2. If some of the windows are opened - disables conditioner
  3. If the current temperature is in between "cold" and "hot" thresholds then it means we've got a comfort state and we can disable the conditioner.
The most interesting part is how we actually control conditioner. As I said before I use dummy switch which has three states: off, on for heating and on for cooling. Each of states calls python script accordingly:
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_off
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_on_for_heating
python3 broadlink_remote_control.py rm_pro_plus living_room_conditioner_on_for_cooling

Dummy switch for controlling an air conditioner

Now we only need to run this helper function when next events are being happened: open/closed windows and temperature/humidity value changed. Let's consider the first case (handle_window_and_door script)
 return {
    on = {
        devices = {
            'hall_room-xiaomi-door_sensor',
            'bed_room-xiaomi-window_sensor',
            'living_room-xiaomi-window_sensor',
            'kitchen_room-xiaomi-window_sensor',
            'bed_room-xiaomi-door_sensor',
            'bath_room-xiaomi-door_sensor',
            'kitchen_room-xiaomi-door_sensor'
        }
    },
    execute = function(domoticz, switch)
        if (
            switch.name == 'hall_room-xiaomi-door_sensor' or
            switch.name == 'bed_room-xiaomi-window_sensor' or
            switch.name == 'living_room-xiaomi-window_sensor' or
            switch.name == 'kitchen_room-xiaomi-window_sensor'
        ) then
            domoticz.helpers.adjustWeather(domoticz);
        end

        ...
    end
}
and the second case (handle_temperature script):
return {
    on = {
        devices = {
            'living_room-xiaomi-temperature'
        }
    },
    execute = function(domoticz, device)
        domoticz.helpers.adjustWeather(domoticz);
        
        ...
    end
}

Alarm clock


Task: set up an alarm clock which can be disabled only if there is a motion activity in a hall room.

Solution: we can build this using Xiaomy Gateway (which can pa lay predefined melody), a motion sensor in a hall room and a timer based dzEvents script (handle_alarm_clock):
 return {
    on = {
        devices = {
            'hall_room-xiaomi-motion_sensor'
        },
        timer = {
            'every day at 07:00'
        }
    },
    execute = function(domoticz, switch)
        local living_room_xiaomi_gateway_alarm_clock = domoticz.devices('living_room-xiaomi-gateway_alarm_clock');

        if (switch.name ~= 'hall_room-xiaomi-motion_sensor') then
            domoticz.helpers.enableAlarmClock(domoticz);
        elseif (switch.state == 'On') then
            domoticz.helpers.disableAlarmClock(domoticz);
        end
    end
}
Domoticz will execute this script every day at 07:00 (enable alarm clock) or when there is a motion activity is detected in a hall room (disable alarm clock). As you can see here are two helper functions used:
 return {
    helpers = {
        ...

        enableAlarmClock = function(domoticz)
            domoticz.log('Enable alarm clock.', domoticz.LOG_INFO);

            domoticz.devices('living_room-xiaomi-gateway_alarm_clock').switchSelector(70).repeatAfterSec(17, 5);
        end,

        disableAlarmClock = function(domoticz)
            domoticz.log('Disable alarm clock.', domoticz.LOG_INFO);

            domoticz.devices('living_room-xiaomi-gateway_alarm_clock').switchOff();
        end,

        ...
    }
}
One comment on enableAlarmClock: we repeat melody "70" 5 times after 17 seconds of playing (the whole melody is 17 second long). So, alarm sounds 85 seconds which enough for waking up (at least for me).

Remote access to your Domoticz


Of course, we want to have access to our home automation server from the internet. In this case, we need an external IP address which can be bought in our internet provider. Once we've got it we have to set up a virtual server which will forward requests from external_ip:port to internal_ip:port (our Domoticz), where external_ip - bought external IP address, internal_ip - internal IP address of the server where Domoticz is up and running and port - port of Domoticz server (in Software chapter we had a docker compose file where we ran Domoticz container and exposed its 8080 port). In TP-LINK routers it's can be done at Virtual servers page.

Forwarding -> Virtual servers

When it's done we can use Domoticz mobile client for Android or use Domoticz directly in a browser using external_ip:port link (don't forget to set up password for your Domoticz instance).

Monitoring


It's useful to know what is happening with your service: if it's up and running or down right now; if the container is stopped etc.

Service uptime


I've googled and found free monitoring service called uptimerobot but you can use any other if you want, all of them more or less the same. It does one thing - checks HTTP response codes from the given url and reports (via email) if something has changed (url is not accessible etc). It also provides statistics about your service like how many hours it was down etc. Just get an account in some of such services and set up a monitor to check your external_ip:port url.

Domoticz uptime statistics (yep, my internet provider is not so stable as I wanted it to be :)

Please note, that downtime reported by such monitors can mean next:

  • Router lost internet connection
  • Domoticz container is stopped for some reason
  • The power supply is down in your apartment

Containers state


Sometimes it's useful to have a web UI where we can manage running containers and check their state directly. I use portainer for this.

UI for managing docker containers
Run it in a docker container as usual:
version: '3'
services:
  ...
  portainer:
      image: portainer/portainer
      ports:
        - "9000:9000"
      command: -H unix:///var/run/docker.sock
      volumes:
        - /var/run/docker.sock:/var/run/docker.sock
        - portainer_data:/data
      logging:
        driver: "json-file"
        options:
          max-size: "10M"
          max-file: "1"

volumes:
  portainer_data:
Optionally you can allow access to portainer from the internet by forwarding 9000 port to your local server. See Remote access to your Domoticz chapter.

Conclusion


I tried to cover all the steps which I've done to build a home automation system - from the hardware/software setup to the network configuration and scenarios. Complete examples can be found in this github repository. If you have any questions/comments/critics - welcome to comments.

10 comments:

  1. Thanks for compiling such nicest information in your blogs. Articles are very informative and hope again I’ll find more like that.fort worth locksmith

    ReplyDelete
  2. Outsourcing to Eastern Europe has a range of advantages due to its attractive cost-to-quality ratio. I would recommend checking out this article to learn more about the Python development companies in Poland.

    ReplyDelete
  3. On the off chance that you have chosen to search for a vocation, there are explicit methodologies that individuals use to look for. VR images for real estate

    ReplyDelete
  4. Truly, this article is really one of the very best in the history of articles. I am a antique ’Article’ collector and I sometimes read some new articles if I find them interesting. And I found this one pretty fascinating and it should go into my collection. Very good work! Building security camera installation

    ReplyDelete
  5. Took me time to read all the comments, but I really enjoyed the article. It proved to be Very helpful to me and I am sure to all the commenters here! It’s always nice when you can not only be informed, but also entertained! dịch vụ sửa nhà

    ReplyDelete
  6. I havent any word to comprehend this declare.....truly i am inspired from this broadcast....the individual that make this nation it changed into a satisfying human..thank you for shared this associated with us. control4

    ReplyDelete
  7. Super site! I am Loving it!! Will return once more. I'm taking your sustenance in addition. Thanks. fixed cost site remediation

    ReplyDelete
  8. This particular is usually apparently essential and moreover outstanding truth along with for sure fair-minded and moreover admittedly useful My business is looking to find in advance designed for this specific useful stuffs… Limpieza de oficinas

    ReplyDelete
  9. Thanks for compiling such nicest information in your blogs. Articles are very informative and hope again I’ll find more like that. קבלן בונה

    ReplyDelete