MAX! HOME AUTOMATION

version 3.18

by Dmitry A. Kazakov

(mailbox@dmitry-kazakov.de)



This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.



This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.



You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

MAX! home automation is a GTK+ application to manage ELV/eQ-3 MAX! cubes. A cube is a gateway to a network of radiator thermostats, shutter contacts etc. The application provides:

Access and control over HTTP rest API;

Access and control over an integrated MQTT broker;

Python scripting support;

Data logger backed by a database, most commonly databases are supported over ODBC;

Set and is temperatures graphs;

E-mail notifications of the low battery status.

ARM Intel Download MAX! home automation Platform: v7 64- 32bit Fedora packages precompiled and packaged using RPM CentOS packages precompiled and packaged using RPM Debian packages precompiled and packaged for dpkg Ubuntu packages precompiled and packaged for dpkg Windows installer max_home_automation_setup_3_18.exe (dual installer for 32- and 64-bit Windows versions) Source distribution (any platform) max_home_automation_3_18.tgz (tar + gzip, Windows users may use WinZip)

See also the changes log.

The application is based on the ELV/eQ-3 cube client protocol stack implementation. The stack is distributed under the GM GPL license and is free to use independently on this application.

1. Use

When the application starts is scans the local area network (LAN) for connected MAX! cubes. Once a cube is found the radio network topology is shown on the overview pane.

1.1. Overview pane

Depending on the LAN configuration it could be impossible to find the cube. In that case the address of a MAX! cube can be entered manually after pressing the button . Note the bar near the cube address. It indicates the amount of 868 MHz radio traffic used by the cube. The traffic is limited. When the limit is exhausted the cube stops transmitting commands controlling the thermostats. The limit is reset each hour. Other participants, e.g. the thermostats, will continue sending their data back to the cube.

The second column contains the measured temperature. The third column contains the set temperature. The measured temperature is not always known. The rightmost column contains the time stamp of the latest known temperature as reported by the device. For forcing measured temperature reports from the thermostat using see scanning temperatures below.

The device state pictograms indicated right of the battery charge status have the following meaning:

The device is operating properly The device has a link error. The devices in the same room are linked to each other. For example, when a shutter contact is open it turns all radiator thermostats linked to it into the airing mode. When a device has a link error it cannot properly respond to or control other devices. Usually this is a result of some configuration problem. Fixing it may require deleting the device and pairing it again The device is in an error state The device is in an error state and has a link error

When a thermostat is selected its operating mode can be changed in the combo box:

Additional parameters like vacation end time and set temperature appear. The mode is changed by pressing the mode set button . Note that the cube performs mode change asynchronously. It could take a considerable time before the thermostat reacts to a mode change. While doing that the cube continues to report the previous operation mode. When the thermostat ultimately changes the mode and the cube becomes aware of that, the actual mode gets reflected on the overview panel.

For the wall mounted thermostats you can additionally set its display to indicate either the measured or set (current scheduled) temperature. Like mode settings the operation is performed asynchronously. There is no indication of the current mode. In order to control the effect you must inspect the corresponding wall thermostat and compare the indicated value with the values on the overview pane.

1.2. Thermostat settings pane

The thermostat settings and time schedule is shown when the configure device button is clicked:

For each week day you can define thermostat temperatures, maximum up to 13 points per day. Each point defines the temperature and the time until the thermostat must keep that temperature. The last point is held to the end of the day. The time resolution is 5 minutes. The temperature resolution is 5 Centigrade.

The day schedule points are automatically sorted according to the time column. If you have to insert a new point enter it into any unused row. Multiple points can be copied. The whole week schedule can be taken from another thermostat when the copy from button is pressed:

A week thermostat schedule can be written into and read from a file. The file format is portable across supported operating systems.

1.3. Trace pane

The trace pane indicated the exchange with the cube and other network parties (e.g. SMTP server):

Tracing can be additionally directed to a file. Buttons clear/stop/start control only tracing into the pane. Tracing into the file, when enabled, continues uninterrupted. When the checkbox append is checked the trace file is appended upon application start. Otherwise, the file is overwritten.

1.4. Graphs and monitor panes

The graphs page contains one pane per room. A room's pane has stacked oscilloscopes one per thermostat indicating the measured room temperature (red), the valve position (yellow) if the thermostat is a radiator thermostat. The gray curve is the set temperature plus thermostat offset, the value used by control the heater. Adding the offset can be disabled on the settings pane.

The temperature scaling is automatic by default. It can be set to fixed on the settings pane.

The monitor pane shows the overview of rooms heating. It has a graph per each room stacked upon each other. If the room has a wall-mounted thermostat then the temperature reported by the thermostat is shown (red and other colors). If there is no wall-mounted thermostats in the room then temperatures of radiator thermostats are shown instead. The averaged position of radiator thermostat valves is shown yellow.

1.5. Scanning temperatures measured by radiator thermostat

The application deploys a technique forcing radiator thermostats to report measured temperatures. A radiator thermostat installed in the room managed by a wall-mounted thermostat reports the temperature measured by the wall-mounted thermostat instead of its own temperature. When installed in an unmanaged room with no wall-mounted thermostats a radiator thermostat reports its own measured temperature but only when either its valve position is changed or its operating mode is. For such "unmanaged" thermostats the application temporarily alters the thermostat mode from automatic to manual or conversely, when there no recent measured temperature known. As soon as the thermostat refreshes the measured temperature it is switched back to the original mode.

The following parameters on the settings page control this scanning behavior:

The check box turning scanning altogether on or off;

Temperature timeout is the time after which the measured temperature is considered obsolete. Nothing is done until that;

is the time after which the measured temperature is considered obsolete. Nothing is done until that; Scan period specifies when the application should try to force a thermostat to report measured temperature after the last attempt has failed for whatever reason;

specifies when the application should try to force a thermostat to report measured temperature after the last attempt has failed for whatever reason; Scan timeout is the time the application waits for a measured temperature reported when a thermostat is in an alternate mode. The thermostat is returned back to the original mode when either the measured temperature is obtained or else the scan timeout expires. This parameter value should not be greater than a half of scan period. The thermostat is returned back to the original mode when half of scan period is expired regardless of scan timeout.

1.6. Cube discovery

The application's setting pane controls the method cubes are discovered and connected.

If LAN discovery is enabled, the application ties to find cubes to connect. When the host address is empty, the application scans all available network interfaces (adapters) for cubes connected. Note that discovery is limited by the segment. Cubes behind routers cannot be found. The host address (or name) can be specified to limit discovery to a concrete LAN segment. This address or name must one of the host where the application is running, i.e. the address of a network interface (adapter).

is enabled, the application ties to find cubes to connect. When the is empty, the application scans all available network interfaces (adapters) for cubes connected. Note that discovery is limited by the segment. Cubes behind routers cannot be found. The (or name) can be specified to limit discovery to a concrete LAN segment. This address or name must one of the host where the application is running, i.e. the address of a network interface (adapter). If LAN discovery is disabled the application does not scan the network. When the cube address is empty, nothing else is done. The cube can be connected later manually. When the cube address (or name) is specified the application tries to connect to the cube upon start. This method can be used when the cube is not connected to the local segment. Such cubes cannot be discovered but still can be connected to.

1.7. Saving and restoring thermostat configurations

When a cube is selected rooms, devices and configurations of all its radiator thermostats can be stored into and restored from a file. Use the button shown on the figure below in order to backup the actual configuration:

The items saved are:

Topology (rooms and devices)

Schedule (temperature program)

Thermostat parameters (temperatures)

Thermostat's valve parameters

In order to restore a stored configuration, select a cube and press the button shown below:

You will be prompted to select the configuration file. Upon successful file selection the restoring configuration pane is shown:

It has two tabs on the right. The tab shown on the figure above is used to restore the cube topology. The topology describes rooms and devices. The configuration read from the file is matched against the actual cube topology. The topology from the file is shown on the pane. There you see the rooms and devices on the left and the actions required to restore it on the right. The check boxes allow masking undesired actions. Press the button in order to start restoring the topology. When a device is missing a pairing dialog will appear in order to add the device. The dialog will indicate the address, name and the room of the missing device.

The lower tab on the right is used to restore the configuration of the thermostats:

Upon reading the file application tries to match addresses or names of the saved thermostats with the addresses and names of existing thermostats. The match is shown in the column get from. This column contains combo-boxes where another source thermostat or no thermostat can be selected. When no thermostat address is selected in the combo box, the existing thermostat's configuration is preserved. Additionally the items to restore can be selected in the check boxes below.

Notices

If you have problem with a cube losing its configuration, note that this does not influence the devices. They will keep their configurations. Thus once you have restored the topology there is no need to restore device configurations. On the other hand, if you have a defect thermostat you want to replace. Then you can use the saved configuration of the old thermostat to configure the new one. Use the combo box in the column get from as described above. Restoring configurations of many thermostats requires much of radio traffic. When the traffic limit is exhausted the cube stops accepting further commands. The bar at the cube address will show 100% and an exclamation sign will appear. After that an hour is required to wait until restoring can be continued. Successfully restored thermostat configurations are skipped. The retry button starts failed configuration over again.

1.8. Pairing devices and creating rooms

In order to use devices with a cube they must be paired with it. To start pairing select a cube in the list and press the button :

This will bring the pairing dialog up:

The dialog shows detected devices and the progress bar indicating when the cube will leave the pairing mode. To be found a device must be also in the pairing mode. Usually pairing of a device is activated by pressing a certain button for a few seconds. Please, refer to the device documentation. Multiple devices can be paired at once and brought in the same room. When the cube leaves pairing mode, discovered devices can be either added or discarded. You can also repeat pairing to find other devices in the room. The cube will leave the pairing mode when the progress bar runs full. You can also stop it prematurely by pressing the Stop button.

The names of discovered devices as set in the Name edit field. The room for the devices is selected from the list box at the bottom of the dialog. When it must be a new room, the room name is entered into the Room edit field. Note that an eco switch button is placed outside any rooms.

When found devices are rejected they are deleted from the cube. It is can be a lengthy process that could potentially take several minutes. Eventually the cube will drop these devices and you will be able to pair them again.

1.9. Deleting devices and rooms

In order to delete a device or a room, select it and then press the delete button:

When a room is deleted all devices in the room are deleted as well. Deleting a device does not influence its settings. E.g. the schedule and valve parameters of a thermostat are kept intact.

Deleting a device could take up to several minutes. During that the deleted devices become orphaned. The cube considers them paired. Until they are finally disconnected they cannot be paired again. If there are orphaned devices the symbol and button is shown. When pressed it brings up a dialog with the list of orphaned and faulty devices. Faulty devices from the list can be deleted. The time required to delete a device can be reduced using the following method. Start pairing pressing button. Then bring the device being deleted into the pairing mode:

Wait a few seconds and cancel pairing. Now the device must disappear from the list of orphaned devices.

1.10. Resetting the cube

In order to delete all rooms and all devices of a cube select the cube and then press the button reset:

After this you will have to pair all devices and create all rooms new. Note that the device settings are retained as they are kept outside the cube.

1.11. Interaction with the ELV MAX! software

The MAX! home automation understands changes to the cube made by the ELV MAX! software. If you add, configure, remove devices using ELV MAX! software the changes will be automatically accommodated by MAX! home automation. The reverse is not true. Changes made in MAX! home automation may disrupt functioning ELV MAX! software. If you plan to continue using ELV MAX! software you can make it aware of the changes by pressing the button . This will back up the current ELV MAX! settings and write the ones corresponding to the actual state.

2. HTTP automation (REST API)

The application has an integrated HTTP server than can be used to control the connected cubes. The server is disabled by default. It can be enabled through the settings pane. The HTTP server port can be set to a value different from the standard 80, when it is already used. The server supports the HTTP GET requests described below:

2.1. Querying a device status

http:// <max-home-automation> /get-status?cube= <address>[ &device= <address>]

Here <max-home-automation> is the name or IP-address of the server. <address> is the RF address (hexadecimal) of the cube and the device correspondingly. For example, assuming accessing the server at the localhost:

http://127.0.0.1/get-status?cube=0BB8F0&device=10A40C

The server response describes the current device status in textual format. Its components depend on the device type. When device address is absent the response lists statuses of all devices controlled by the cube. Individual parts of the status can be queried by the following queries:

http:// <max-home-automation> /get-status-json?cube= <address>[ &device= <address>]

This query is analogous to get-status except that it reports the status of a device or of all available devices in the JSON parser format.

http:// <max-home-automation> /get-status-csv?cube= <address>[ &device= <address>]

This query is analogous to get-status except that it reports the status of a device or of all available devices in the CSV format. The output contains one line for each device. The line has the following fields separated by semicolon, all fields except the last one are numeric:

No. Value Values 1 The device type Device Value cube 0 radiator thermostat 1 radiator thermostat plus 2 wall thermostat 3 shutter contact 4 eco button 5 unknown 6 2 The device RF address, hexadecimal 000000..FFFFFF 3 The device error 0..1 4 Device initialized 0..1 5 Battery low 0..1 6 Error 0..1 7 Panel locked 0..1 8 Gateway known 0..1 9 Day saving time 0..1 10 Mode Mode Value automatic 0 manual 1 boost 2 vacation 3 11 Set temperature 12 New temperature 13 Valve position 0..100 14 Actual temperature 0 if not known 15 Open (shutter contact) 0..1 16 Offset (thermostat) 17 Serial number KEQ0828854 18 Device name Thermostat 1 http:// <max-home-automation> /get-battery?cube= <address> &device= <address>

This query is responded with the current battery level of a device. The returned value is high or low.

http:// <max-home-automation> /get-cubes-list

This query is responded with the list of RF addresses of the known cubes separated by spaces.

http:// <max-home-automation> /get-cubes-json-list

This query is responded with the list of RF addresses of the known cubes in JSON format.

http:// <max-home-automation> /get-duty?cube= <address>

This query is responded with the current value of the cube's duty cycle in percent. When the value reaches 100% the cube stops using its radio frequency for an hour.

http:// <max-home-automation> /get-link?cube= <address> &device= <address>

This query is responded with the radio link of a device. The returned value is error or ok.

http:// <max-home-automation> /get-mode?cube= <address> &device= <address>

This query is responded with the current mode of a thermostat. The returned values are automatic, manual, boots, vacation.

http:// <max-home-automation> /get-rooms-list?cube= <address>

This query is responded with the list of rooms per line. For each room the corresponding line of the response contains the list of space separated RF-addresses of the devices in the room, followed by the room ID and name. The room ID is introduced by dash (#), the name is by minus (-). For example:

0B76D4 0CB6E2 #1 - Small room on the left 0B7E19 0C9809 0CB7FF #2 - Bedroom 0B76DA 0C9E44 0CB003 17EE29 #3 - Recreation room 0C8EA2 0CA489 0CB6D6 10A40C #4 - Big room on the right 050F6D 12F4E0 #5 - Bathroom

http:// <max-home-automation> /get-rooms-json-list?cube= <address>

This query is responded with the list of rooms in JSON format. For each room the response contains the room name, the room ID and the list of RF-addresses of the devices in the room.

http:// <max-home-automation> /get-temperature?cube= <address> &device= <address>

This query is responded with the current temperature measured by a wall-mounted thermostat in Centigrade.

http:// <max-home-automation> /get-set-temperature?cube= <address> &device= <address>

This query is responded with the current set temperature of a thermostat in Centigrade.

http:// <max-home-automation> /get-summer-time?cube= <address> &device= <address>

This query is responded with the device summer time settings. The returned value is yes or no.

http:// <max-home-automation> /get-valve?cube= <address> &device= <address>

This query is responded with the current valve position of a radiator thermostat 0-100%. The value 100% corresponds to the fully opened valve.

http:// <max-home-automation> /get-valve-average?cube= <address>

This query is responded with the average valve position of radiator thermostats handled by the cube.

http:// <max-home-automation> /get-valve-min?cube= <address>

This query is responded with the minimum valve position of all radiator thermostats handled by the cube.

http:// <max-home-automation> /get-valve-max?cube= <address>

This query is responded with the maximum valve position of all radiator thermostats handled by the cube.

2.2. Setting thermostats into automatic mode

http:// <max-home-automation> /set-automatic?cube= <address>[ &device= <address>]

or

http:// <max-home-automation> /set-automatic?cube= <address>[ &device= <address>] & { temperature [ + | - ] = <value>| airing | comfort | eco }

For example:

http://127.0.0.1/set-automatic?cube=0BB8F0&device=10A40C

The temperature is specified by the parameter <value> . It can be a relative or absolute value. For example an absolute temperature specification:

http://127.0.0.1/set-automatic?cube=0BB8F0&device=10A40C&temperature=18.5

Here the thermostat 10A40C is set to keep 18.5 Centigrade until the next time interval, when it returns to the schedule. The following is a example of setting the temperature relative to the current one:

http://127.0.0.1/set-automatic?cube=0BB8F0&device=10A40C&temperature+=0.5

In this example the temperature is incremented by 0.5 Centigrade.

Additionally one of the following keywords can be used for the temperature:

airing is the temperature from the thermostat's settings used in the airing mode, i.e. when one of the windows associated with the thermostat is open;

is the temperature from the thermostat's settings used in the airing mode, i.e. when one of the windows associated with the thermostat is open; comfort is the comfort temperature from from the thermostat settings;

is the comfort temperature from from the thermostat settings; eco is the eco temperature from the thermostat's settings.

The following request sets airing temperature:

http://127.0.0.1/set-automatic?cube=0BB8F0&device=10A40C&airing

The field device= <address> can be omitted. In this case the command applies to all thermostats, e.g.

http://127.0.0.1/set-automatic?cube=0BB8F0&eco

Here all thermostats set to the corresponding eco temperature (until the next time slice in the schedule).

2.3. Setting thermostats into boost mode

http:// <max-home-automation> /set-boost?cube= <address>[ &device= <address>]

For example:

http://127.0.0.1/set-boost?cube=0BB8F0&device=10A40C

2.4. Setting thermostats into manual mode

http:// <max-home-automation> /set-manual?cube= <address>[ &device= <address>] & { temperature [ + | - ] = <value>| airing | comfort | eco }

The temperature is specified by the parameter <value> . It can be a relative or absolute value. For example an absolute temperature specification:

http://127.0.0.1/set-manual?cube=0BB8F0&device=10A40C&temperature=18.5

Here the thermostat 10A40C is set to keep 18.5 Centigrade. The following is a example of setting the temperature relative to the current one:

http://127.0.0.1/set-manual?cube=0BB8F0&device=10A40C&temperature+=0.5

In this example the temperature is incremented by 0.5 Centigrade.

2.5. Setting thermostats into vacation mode

http:// <max-home-automation> /set-vacation?cube= <address>[ &device= <address>] & { temperature [ + | - ] = <value>| airing | comfort | eco } & { weeks | days | hours | minutes } = <count>

For the vacation mode again the temperature can be specified relative or absolute. The vacation end time is given as a number <count> of weeks, days, hours or minutes. For example:

http://127.0.0.1/set-vacation?cube=0BB8F0&device=10A40C&temperature=16&days=2

Here the thermostat 10A40C is set to keep 16 Centigrade for two days. After that it will switch back into its previous mode.

2.6. Setting thermostat's schedule

http:// <max-home-automation> /set-schedule?cube= <address> &device= <address> &schedule= <week-schedule>

The thermostat schedule in the following format (using Bacus-Naur forms):

<week-schedule> ::= <sub-schedule>[ , <week-schedule>] <sub-schedule> ::= <days-range> ( <day-schedule> ) <days-range> ::= <week-day>|<week-day> .. <week-day> <week-day> ::= Mo | Tu | We | Th | Fr | Sa | Su <day-schedule> ::= <temperature-point>[ , day-schedule>] <temperature-point> ::= <time> = <temperature> <time> ::= <hour>[ : <minute>]

Blank characters including LF and CR may appear between the elements. The value of <time> specifies the end time until the given temperature is to be held by the thermostat. The times must be specified in an ascending order. No more than 13 points are allowed per <day-schedule> . Week days and their ranges can appear in any order. They are case-insensitive. All days of the week must be covered. Here is an example of a schedule:

http://127.0.0.1/set-schedule?cube=0BB8F0&device=10A40C&schedule=Mo(17:00=20,18:30=21.5),Tu..Su(09:00=19.0,10:10=21.0,18:30=19.5)

2.7. Controlling cube connection

http:// <max-home-automation> /disconnect?cube= <address>

http:// <max-home-automation> /reconnect?cube= <address>

A cube can be disconnected and reconnected again using these two queries.

http:// <max-home-automation> /get-connection?cube= <address>

This query returns connected or disconnected depending of the cube connection status.

2.8. Setting thermostat parameters

http:// <max-home-automation> /set-eco-temperature?cube= <address> &device= <address> &temperature= <value>

A cube can be disconnected and reconnected again using these two queries.

http://127.0.0.1/set-eco-temperature?cube=0BB8F0&device=10A40C&temperature=16.5

In this example the eco temperature is set to 16.5 Centigrade.

2.9. Custom web page

Apart from the REST API described above, the integrated HTTP server supports custom web pages. A custom web page can be specified on the settings pane as UI HTTP page:

This feature can be used to design a custom user interface, e.g. a dashboard. The path to the file must refer a file that exists during run-time. The file may refer to other files (e.g. jpeg pictures etc) which also must exist in order to show the page properly. These files are not cached and always opened new at each HTTP client request.

There exist a great variety of tools and libraries for designing web-based dashboards. Any of them can be used, provided it would not require a special backend, e.g. a special HTTP server, database etc. Also it might be important to choose a self-contained tool that allows holding locally all files necessary for the page to function. Otherwise the dashboard will not work without internet access. For example, the sample dashboard represented below refers to an external site where the trial version of the widget library is hosted.

The following sample code represents a simple dashboard designed using FusionCharts library:

Here is the source code of the corresponding HTTP page (it deploys JavaScript):

<html>

<head>

<title> Dashboard sample </title>

<script type =" text/javascript " src =" https://cdn.fusioncharts.com/fusioncharts/latest/fusioncharts.js " ></script>

<script type =" text/javascript " src =" https://cdn.fusioncharts.com/fusioncharts/latest/themes/fusioncharts.theme.fusion.js " ></script>

<script type =" text/javascript " >



var HttpClient = function ()

{

this.get = function (URL, Callback)

{

var Request = new XMLHttpRequest ();

Request.onreadystatechange = function ()

{

if (Request.readyState == 4 && Request.status == 200 )

{

Callback (Request.responseText);

} }

Request.open (" GET ", URL, true );

Request.send ( null );

}

}



var Client = new HttpClient ();

var Topology = {}; // The rooms map id->room data

var Actions = []; // The queue of actions to perform

var Location = 0 ; // Where to place a new widget



function Do_Room (Cube, Room)

{

if (Topology [Cube] [Room].temperature.source == 0 ) return ;

Topology [Cube] [Room].temperature.widget =

new FusionCharts

( { type: ' thermometer ',

renderAt: Topology [Cube] [Room].location,

width: 240 ,

height: 310 ,

dataFormat: ' json ',

dataSource:

{ " chart ":

{ " caption ": Topology [Cube] [Room].name,

// "subcaption": "",

" lowerLimit ": 0 ,

" upperLimit ": 30 ,

" decimals ": 1 ,

" numberSuffix ": " °C ",

" showhovereffect ": 1 ,

" thmFillColor ": " #008ee4 ",

" showGaugeBorder ": 1 ,

" gaugeBorderColor ": " #008ee4 ",

" gaugeBorderThickness ": 2 ,

" gaugeBorderAlpha ": 30 ,

" gaugeFillColor ": " #008ee4 ",

" gaugeFillAlpha ": 100 ,

" thmOriginX ": 100 ,

" chartBottomMargin ": 20 ,

" valueFontColor ": " #000000 ",

" showValue ": 1 ,

" adjustTM ": 1 ,

" ticksOnRight ": 0 ,

" tickMarkDistance ": 5 ,

" tickValueDistance ": 2 ,

" majorTMNumber ": 9 ,

" majorTMHeight ": 12 ,

" minorTMNumber ": 4 ,

" minorTMHeight ": 7 ,

" tickValueStep ": 2 ,

" valueFontSize ": 46 ,

" valueFontBold ": 1 ,

" ValuePadding ": 35 ,

" valueFontColor ": " #808080 ",

" showhovereffect ": 1 ,

" theme ": " fusion "

},

" value ": 0 ,

} }

);

}



function Refresh ()

{

for ( var Cube in Topology)

{

Client.get

( window.location.origin + ' /get-status-json?cube= ' + Cube,

function (Response)

{

State = JSON.parse (Response);



for ( var Device in State.devices)

{

var Address = State.devices [Device].address;



for ( var Room in Topology [Cube])

{

if (Topology [Cube] [Room].temperature.source == Address)

{

Topology [Cube] [Room].temperature.widget.feedData

( " &value= " + State.devices [Device].temperature

);

} } } }

);

}

}



function Pump ()

{

if (Actions.length > 0 )

{

var This = Actions.pop ();



This.Function (This.Cube, This.Room, This.Device);

return ;

}

for ( var Cube in Topology)

{

for ( var Room in Topology [Cube])

{

Do_Room (Cube, Room);

} }

FusionCharts.ready

( function ()

{

for ( var Cube in Topology)

{

for (var Room in Topology [Cube])

{

if (Topology [Cube] [Room].temperature.widget != null )

{

Topology [Cube] [Room].temperature.widget.render ();

} } }

setInterval (Refresh, 1000 );

}

);

}



function On_Device_Status (Response, Cube, Room, Device)

{

var Status = JSON.parse (Response);



if (Status.type == " wall thermostat ")

{

Topology [Cube] [Room].temperature.source = Device;

Topology [Cube] [Room].is_wall = true ;

}

else if (Status.type == " radiator thermostat ")

{

if (Topology [Cube] [Room].temperature.source == 0 )

{

Topology [Cube] [Room].temperature.source = Device;

} }

Pump ();

}



function On_Rooms_List (Response, Cube)

{

var Rooms_List = JSON.parse (Response);



for ( var Room_No = 0 ; Room_No < Rooms_List.length; Room_No++)

{

var Room_ID = Rooms_List [Room_No].id;

var At = Location;



Location++;

Topology [Cube] [Room_ID] =

{ " name ": Rooms_List [Room_No].name,

" id ": Room_ID,

" location ": At.toString ( 10 ),

" temperature ": { " source ": 0 ,

" is_wall ": false ,

" widget ": null

} };

for ( var Device_No = 0 ;

Device_No < Rooms_List [Room_No].devices.length;

Device_No++

)

{

var Device = Rooms_List [Room_No].devices [Device_No];



Actions.push

( { " Function ":

function (Cube, Room, Device)

{

Client.get

( window.location.origin +

' /get-status-json?cube= ' +

Cube +

' &device= ' +

Device.toString ( 16 ),

function (Response)

{

On_Device_Status (Response, Cube, Room, Device);

}

);

},

" Cube ": Cube,

" Room ": Room_ID,

" Device ": Device

}

);

} }

Pump ();

}



function On_Cubes_List (Response)

{

var Cubes_List = JSON.parse (Response);



for ( var Cube_No = 0 ; Cube_No < Cubes_List.length; Cube_No++)

{ // Query list of rooms

var Cube = Cubes_List [Cube_No].toString ( 16 );



Topology [Cube] = {};

Actions.push

( { " Function " :

function (Cube, Room, Device)

{

Client.get

( window.location.origin +

' /get-rooms-json-list?cube= ' +

Cube.toString ( 16 ),

function (Response) { On_Rooms_List (Response, Cube); }

);

},

" Cube " : Cube

}

);

}

Pump ();

}



Client.get (window.location.origin + ' /get-cubes-json-list ', On_Cubes_List);



</script>

</head>

<body>

<table border =" 0 " width =" 100% ">

<tr>

<td><div class =' chartCont ' id =' 0 ' ></div></td>

<td><div class =' chartCont ' id =' 1 ' ></div></td>

<td><div class =' chartCont ' id =' 2 ' ></div></td>

</tr>

<tr>

<td><div class =' chartCont ' id =' 3 ' ></div></td>

<td><div class =' chartCont ' id =' 4 ' ></div></td>

<td><div class =' chartCont ' id =' 5 ' ></div></td>

</tr>

</table>

</body>

</html>

3. MQTT automation

The integrated MQTT server allows access and control of the cubes through the Message Queueing Telemetry Transport (MQTT). MQTT is an ISO standard (ISO/IEC PRF 20922) messaging protocol. By default the MQTT server is disabled. It is enabled through the settings pane.

3.1. Published topics

The server publishes data received from the cube as retained topics:

Topic Contents <cube-address> /connection on, off <cube-address> /duty 0.1 <cube-address> /radiator thermostat/ <device-address> /battery low, high <cube-address> /radiator thermostat/ <device-address> /error ok, error <cube-address> /radiator thermostat/ <device-address> /link ok, error <cube-address> /radiator thermostat/ <device-address> /mode automatic, manual, boost, vacation <cube-address> /radiator thermostat/ <device-address> /set temperature 19.0 <cube-address> /radiator thermostat/ <device-address> /temperature 19.5 <cube-address> /radiator thermostat/ <device-address> /valve 30 <cube-address> /shutter contact/ <device-address> /battery low, high <cube-address> /shutter contact/ <device-address> /error ok, error <cube-address> /shutter contact/ <device-address> /link ok, error <cube-address> /shutter contact/ <device-address> /status open, close <cube-address> /thermostat valves/average 50 <cube-address> /thermostat valves/max 70 <cube-address> /thermostat valves/min 30 <cube-address> /wall thermostat/ <device-address> /battery low, high <cube-address> /wall thermostat/ <device-address> /error ok, error <cube-address> /wall thermostat/ <device-address> /link ok, error <cube-address> /wall thermostat/ <device-address> /mode automatic, manual, boost, vacation <cube-address> /wall thermostat/ <device-address> /set temperature 19.0 <cube-address> /wall thermostat/ <device-address> /temperature 19.5

Here <cube-address> is the RF address (hexadecimal) of the cube. <device-address> is the RF address of the device. All topics are retained, that means a client may subscribe to them at any time and get the latest values of. The topic .../thermostat valves/average contains the average valve position of all cube's radiator thermostats. The topics .../thermostat valves/min and .../thermostat valves/max are correspondingly the minimum and maximum valve positions among cube's radiator thermostats.

3.2. Controlling thermostats

A thermostat can be controlled by publishing special topics on the server. The server does not propagate or retain these topics.

Topic Contents <cube-address> /set [ / <device-address>] /automatic [[ + | - ]<temperature>]| airing | eco | comfort <cube-address> /set [ / <device-address>] /boost - <cube-address> /set/ <device-address> /eco <temperature> <cube-address> /set [ / <device-address>] /manual [ + | - ]<temperature>| airing | eco | comfort <cube-address> /set [ / <device-address>] /mode { automatic [[ + | - ]<temperature>]| airing | eco | comfort | boost | manual [ + | - ]<temperature>| airing | eco | comfort | vacation {[ + | - ]<temperature>| airing | eco | comfort } <count>{ weeks | days | hours | minutes } } <cube-address> /set/ <device-address> /schedule The thermostat schedule in the format described above <cube-address> /set [ / <device-address>] /vacation {[ + | - ]<temperature>| airing | eco | comfort } <count>{ weeks | days | hours | minutes }

Here <cube-address> is the RF address (hexadecimal) of the cube. <device-address> is the RF address of the device, which must be a thermostat. When <device-address> is absent the command applies to all radiator thermostats. <temperature> is the temperature to set. When a sign is used the temperature is relative to the actual set value. <count> is the number of weeks, days, hours, minutes as specified. Additionally one of the following keywords can be used for the temperature:

airing is the temperature from the thermostat's settings used in the airing mode, i.e. when one of the windows associated with the thermostat is open;

is the temperature from the thermostat's settings used in the airing mode, i.e. when one of the windows associated with the thermostat is open; comfort is the comfort temperature from from the thermostat settings;

is the comfort temperature from from the thermostat settings; eco is the eco temperature from the thermostat's settings.

3.3. Controlling cube connection

A thermostat can be disconnected and reconnecting by publishing special topics on the server. The server does not propagate or retain these topics.

Topic Contents <cube-address> /disconnect - <cube-address> /reconnect -

Here <cube-address> is the RF address (hexadecimal) of the cube.

3.4. Externally published topics

By default the MAX! home automation MQTT broker rejects client's attempts to publish any topics beyond ones to control the thermostats. This behavior can be changed on the settings pane by allowing MQTT publishing. Further the list of allowed topics can be specified. When the list is empty any topic can be published. Otherwise it contains a comma-separated list of MQTT topic patterns. A topic is published if it matches at least one item from the list.

MQTT publishing can be used together with Python scripting to connect external sensors connected to a MQTT client. The client can publish sensor readings as retained MQTT topics on the MAX! home automation MQTT broker. The Python script can read the published messages (see get_mqtt_message) and use the data for control.

4. Logging into a database

The device data can be stored into a database, for example in order to accumulate long-term statistics. New data are logged only if changed. Repeating values are ignored.

4.1. Database configuration

The settings pane has database section allowing database configuration:

ODBC and SQLite are supported interfaces.

4.1.1 ODBC

ODBC stands for Open Database Connectivity. The database configuration consists of:

Data source name . The DSN indentifies the database to use;

User is the user name to access to database;

Password is the password.

The database may be located on a different computer and run under a different OS.

Under MS-Windows ODBC is configured by typing set up ODBC data sources . This will bring Windows ODBC administrator tool, where the driver and DSN can be set up.

Under Linux ODBC is provided by unixODBC package. See http://www.unixodbc.org/ for further information.

ODBC drivers are provided by the database vendors. Most commonly used databases support ODBC, e.g. Microsoft SQL Server, Microsoft Access, Oracle, Sybase, MySQL, PostgreSQL to name few.

4.1.2 SQLite

SQLite is a single-file database. All database is stored in the file which is usually located on the local file system. The database file name is all that has to be configured. When the file does not exist, it is automatically created.

4.2. The database structure

The data are stored into the table named datalog. The table has the following columns:

Name Type Contents time_stamp String

or

Timestamp For SQLite the timestamp is a text in the format: 2017-02-18 14:00:30.12

For ODBC a database native timestamp type is used address Integer The RF-address of the device device_type Integer Device Value cube 0 radiator thermostat 1 radiator thermostat plus 2 wall thermostat 3 shutter contact 4 eco button 5 unknown 6 set_temperature Real The temperature kept by the thermostat new_temperature Real The new temperature for the thermostat. It differs from the above if the thermostat is not yet aware of the change is_temperature Real The actual room temperature offset Real The temperature offset to add to the set-temperature. The result is used as the target by the thermostat valve_position Integer 0..100. The value 100 corresponds to a fully open valve contact_open Integer 0..1. The value 1 means open contact. This column is filled by window shutter contacts error Integer 0..1. The value 1 means error duty Integer 0..100. This column is filled by cube slots Integer The number of free slots. This column is filled by cube battery_low Integer 0..1 panel_locked Integer 0..1 current_mode Mode Value automatic 0 manual 1 boost 2 vacation 3

The table is created automatically if does not exist. For ODBC data types of the columns are selected according to the capacities of the database management system (DBMS). If a column or columns are not defined or do not apply to the value is set to NULL. For example, when data of a shutter contact are stored, the set_temperature is written as NULL.

5. Scripting

5.1. Python scripting

Simple control tasks can be performed by providing a Python script to be called periodically. The script can access the application through the module elv_max_home. The module is loaded as shown in the following example:

# Minimal controller script

import elv_max_cube

def controller (*args):

elv_max_cube.trace ( "Hello there!" )

The settings pane has Python script settings:

Here the fields are:

Python library is the dynamic library of the Python interpreter. If the library name is relative the path to it must be in the system library path;

is the dynamic library of the Python interpreter. If the library name is relative the path to it must be in the system library path; Script file name is the name of the script to execute. It must have the name controller.py . Python usually can load only scripts located in the same directory as the application or in the Python directory. If the script is located elsewhere, it must be specified by full path;

is the name of the script to execute. It must have the name . Python usually can load only scripts located in the same directory as the application or in the Python directory. If the script is located elsewhere, it must be specified by full path; Invoke period specifies how frequently the script must be executed. This is the time to wait before next call of the script;

specifies how frequently the script must be executed. This is the time to wait before next call of the script; Start delay specifies the time before the first invocation of the script. You want to make sure that the cube completes handshake procedure before starting the script.

5.2. Python module elv_max_home

The following methods are provided by the module elv_max_home:

5.2.1. disconnect

elv_max_cube.disconnect (cube)

This method disconnects from the cube specified by the parameter cube which is the RF address of the cube. Note that the operation is asynchronous, the call only initiates disconnection. TypeError is raised when no such cube exists.

5.2.2. get_battery

elv_max_cube.get_battery (cube, device)

This method returns the battery state by the RF address of the cube and the RF address of the device. The result is 'high' or 'low'. TypeError is when no device was found or it has no battery. For example the following code lists battery state of a device:

elv_max_cube.trace ( "Battery state: " + elv_max_cube.get_battery ( 0x0BB8F0 , 0x0B76DA ))

5.2.3. get_connection

elv_max_cube.get_connection (cube)

This method returns cube connection status. The parameter cube is the RF address of the cube. The result is one of 'connected', 'disconnected', 'unknown'. The string 'unknown' is returned when cube does not specify a known cube.

5.2.4. get_cubes_list

This method returns the list of RF addresses, one address for each detected cube. For example the following code lists all cubes:

cubes_list = elv_max_cube.get_cubes_list ()

elv_max_cube.trace ( "MAX! cubes detected: " + ", " .join ( '{:06X}' .format (cube) for cube in cubes_list))

5.2.5. get_devices_list

elv_max_cube.get_devices_list (cube, device_types)

This method returns the list of RF addresses of the devices managed by the cube and filtered by the type. The parameter cube is the RF address of the cube. When 0 the devices are taken from any cube. device_types is the list of device types to accept. It can contain any of: 'cube', 'radiator thermostat', 'radiator thermostat plus', 'wall thermostat', 'shutter contact', 'eco button'. The following example lists all radiator thermostats managed by the the cube with the address 0BB8F0:

thermostats_list = elv_max_cube.get_devices_list ( 0x0BB8F0 , ['radiator thermostat', 'radiator thermostat plus'])

elv_max_cube.trace ( "Thermostats: " + ", " .join ( '{:06X}' .format (thermostat) for thermostat in thermostats_list))

5.2.6. get_duty

elv_max_cube.get_duty (cube)

This method returns the duty of the cube 0.0..1.0. When the value reaches its maximum the cube stops sending anything and waits for an hour.

5.2.7. get_link

elv_max_cube.get_link (cube, device)

This method returns the link state by the RF address of the cube and the RF address of the device. The result is 'error' or 'ok'. TypeError is when no device was found or it has no link state. For example the following code shows link state of a device:

elv_max_cube.trace ( "Link state: " + elv_max_cube.get_link ( 0x0BB8F0 , 0x0B76DA ))

5.2.8. get_mode

elv_max_cube.get_mode (cube, device)

This method returns the link state by the RF address of the cube and the RF address of the device. The result is 'automatic', 'manual', 'boost', 'vacation'. TypeError is raised when no device was found or it has no mode. For example the following code lists all cubes:

elv_max_cube.trace ( "Mode: " + elv_max_cube.get_mode ( 0x0BB8F0 , 0x0B76DA ))

5.2.9. get_mqtt_message

elv_max_cube.get_mqtt_message (topic)

This method returns retained message from the MAX! home automation broker. The parameter topic specifies the message topic. When the topic is invalid or when no message exists TypeError is raised. The following example illustrates getting topic Greeting:

elv_max_cube.trace ( "Message: " + elv_max_cube.get_mqtt_message ( "Greeting" ))

5.2.10. get_parameters

elv_max_cube.get_parameters (cube, device)

This method returns a dictionary describing parameters of device handled by cube. The dictionary contains the following key some of which can be absent depending on the device type:

Key Type Contents address long The RF-address of the device airing temperature float The thermostat room airing temperature (Centigrade) airing mode delay string The duration in the format HH:MM boost duration string The duration of the thermostat boost mode in the format HH:MM boost valve position float The valve position in the boost mode in the range 0..1. 1 corresponds to a fully open valve comfort temperature float The thermostat comfort temperature (Centigrade) decalcification time dictionary The week day and time to perform the procedure: Key Type Contents day string The week day, e.g. 'Monday' time string The time in the format HH:MM eco temperature float The thermostat eco temperature, when in the 'eco' mode (Centigrade) maximum valve position float The thermostat maximum valve position in the range 0..1. 1 corresponds to a fully open valve maximum temperature float The thermostat maximal temperature (Centigrade) minimum temperature float The thermostat minimum temperature (Centigrade) room string The device's room name room id long The ID number of the device's room name string The device name schedule dictionary The thermostat schedule in the automatic mode: Key Type Contents Monday list The thermostat scheduling points on Monday. The points in the list are ordered by the time. Each point is the dictionary: Key Type Contents until string The time in the format HH:MM until the specified temperature must be held temperature float The set temperature in Centigrade Tuesday list The thermostat scheduling points on Tuesday Wednesday list The thermostat scheduling points on Wednesday Thursday list The thermostat scheduling points on Thursday Friday list The thermostat scheduling points on Friday Saturday list The thermostat scheduling points on Saturday Sunday list The thermostat scheduling points on Sunday serial no string The device serial number temperature offset float The thermostat temperature offset (Centigrade) type string Device cube radiator thermostat radiator thermostat plus wall thermostat shutter contact eco button unknown valve float The value 1.0 corresponds to a fully open valve

TypeError occurs when no device was found. The following example illustrates tracing state of the thermostat 0B76DA managed by the the cube with the address 0BB8F0:

elv_max_cube.trace ( "Parameters " + str (elv_max_cube.get_parameters ( 0x0BB8F0 , 0x0B76DA )))

Possible output could be: Status {'type': 'radiator thermostat', 'address': 751322, 'mode': 'manual', 'error': True, 'initialized': True, 'valve': 0.1298828125, 'set temperature': 17.0, 'new temperature': 17.0, 'panel locked': False, 'battery low': False, 'link error': False, 'summer time': True}

5.2.11. get_set_temperature

elv_max_cube.get_set_temperature (cube, device)

This method returns the current set temperature by the RF address of the cube and the RF address of the device. The result is the temperature in Centigrade. The set temperature is used to control the radiator. TypeError is when no device was found or it has no temperature. For example the following code lists the set temperature:

elv_max_cube.trace ( "Set temperature: " + str (elv_max_cube.get_set_temperature ( 0x0BB8F0 , 0x0B76DA )))

5.2.12. get_status

elv_max_cube.get_status (cube, device)

This method returns a dictionary describing status of device handled by cube. The dictionary contains the following key some of which can be absent depending on the device type:

Key Type Contents address long The RF-address of the device battery low True/False True if battery is low error True/False True if error initialized True/False True if initialized link error True/False True if link error mode string Mode automatic manual boost vacation new temperature float The new temperature for the thermostat. It differs from the above if the thermostat is not yet aware of the change open True/False True if shutter contact is open panel locked True/False True if locked set temperature float The temperature kept by the thermostat summer time True/False True if summer time temperature float The actual room temperature, if known type string Device cube radiator thermostat radiator thermostat plus wall thermostat shutter contact eco button unknown valve float The value 1.0 corresponds to a fully open valve

TypeError is when no device was found. The following example illustrates tracing state of the thermostat 0B76DA managed by the the cube with the address 0BB8F0:

elv_max_cube.trace ( "Status " + str (elv_max_cube.get_status ( 0x0BB8F0 , 0x0B76DA )))

Possible output could be: Status {'type': 'radiator thermostat', 'address': 751322, 'mode': 'manual', 'error': True, 'initialized': True, 'valve': 0.1298828125, 'set temperature': 17.0, 'new temperature': 17.0, 'panel locked': False, 'battery low': False, 'link error': False, 'summer time': True}

5.2.13. get_summer_time

elv_max_cube.get_summer_time (cube, device)

This method returns the daytime saving mode of device specified by the RF address of the cube and the RF address of the device. True is he result when the summer time is active. TypeError is when no device was found or it has no time mode settings. For example the following code shows the active daytime saving mode:

elv_max_cube.trace ( "Summer time: " + str (elv_max_cube.get_summer_time ( 0x0BB8F0 , 0x0B76DA )))

5.2.14. get_temperature

elv_max_cube.get_temperature (cube, device)

This method returns the current measured temperature by the RF address of the cube and the RF address of the device. The result is the measured temperature in Centigrade. TypeError is when no device was found, it has no temperature or when the temperature is yet unknown. For example the following code lists the measured temperature:

elv_max_cube.trace ( "Temperature: " + str (elv_max_cube.get_temperature ( 0x0BB8F0 , 0x0B76DA )))

5.2.15. get_valve

elv_max_cube.get_valve (cube, device)

This method returns the valve position of a thermostat specified by the RF address of the cube and the RF address of the device. The result is in the range 0..1. The value 1 corresponds to a fully open valve. TypeError is when no device was found or it is not a thermostat. For example the following code shows the valve position of a thermostat:

elv_max_cube.trace ( "Valve: " + str (elv_max_cube.get_valve ( 0x0BB8F0 , 0x0B76DA )))

5.2.16. get_valve_statistics

elv_max_cube.get_valve_statistics (cube)

This method returns the statistics of the current thermostat valve positions. The parameter cube is the RF address of the cube which thermostats are queried. The result is the dictionary:

Key Type Contents average float The average valve position maximum float The maximal valve position minimum float The minimal valve position

The valve position is in the range 0..1. The value 1 corresponds to a fully open valve. When there is no thermostats corresponding, the returned fields are 0.

elv_max_cube.trace ( "Average valve position: " + str (elv_max_cube.get_valve_statistics ( 0x0BB8F0 ) ['average']))

5.2.17. publish_mqtt_message

elv_max_cube.publish_mqtt_message (topic, message [, policy])

This method publishes topic with the message specified by message the statistics of the current thermostat valve positions. The parameter policy is specifies how to handle the message:

Policy Description Transient The message is not retained, only active subscriptions get it Retained The message always replaces the retained one Updated Ignored if same as the retained one, otherwise it replaces the old one. This is the default Initial Ignored if already retained Ignored The message is ignored

The following example illustrates publishing topic Greeting with the message Hi!

elv_max_cube.publish_mqtt_message ( "Greeting" , "Hi!" )

5.2.18. reconnect

elv_max_cube.reconnect (cube)

This method reconnects to the cube specified by the parameter cube which is the RF address of the cube. Note that the operation is asynchronous, the call only initiates reconnection. TypeError is raised when no such cube exists.

5.2.19. set_mode

elv_max_cube.set_mode (mode, cube, device, temperature, disposition, until)

This method sets one or several thermostats into the specified mode. The method takes keyed parameters with the names given in the following table:

Key Type Contents mode string The thermostat mode to set: Mode automatic manual boost vacation This parameter is obligatory. If omitted TypeError is raised. cube long The RF address of the cube of which thermostats must be set. This parameter must be always present. If omitted TypeError is raised. device thermostat long The RF address of the thermostat to set. This parameter is optional. Then absent the mode is set into all thermostats of the cube. The parameter key can be either device or thermostat. temperature float The temperature in Centigrade. This parameter is optional if the temperature is not required or specified by other means. When required but not specified TypeError is raised. The temperature is either absolute or relative to the current temperature as specified by the parameter disposition. disposition string The parameter specifies how to treat the temperature parameter: Disposition Meaning absolute The value of the parameter temperature is used as-is for the mode to set. The temperature parameter must be present, otherwise TypeError is raised. This is the default when the temperature parameter is specified. airing The airing mode temperature is used for the mode. The temperature parameter must be absent, otherwise TypeError is raised. comfort The comfort temperature is used for the mode. The temperature parameter must be absent, otherwise TypeError is raised. decrement The value of the parameter temperature is used to decrement the current temperature, the result is used for the mode. The temperature parameter must be present, otherwise TypeError is raised. eco The eco temperature is used for the mode. The temperature parameter must be absent, otherwise TypeError is raised. increment The value of the parameter temperature is used to increment the current temperature, the result is used for the mode. The temperature parameter must be present, otherwise TypeError is raised. until float The duration of the set mode in seconds. This parameter must appear when the mode is vacation. It must be absent for any other mode, otherwise TypeError is raised.

The method is asynchronous. It only initiates setting the mode, which can take a considerable time or fail when the RF traffic is exhausted. The following example sets all thermostats to hold the eco temperature for one day:

elv_max_cube.set_mode (cube= 0x0BB8F0 , mode= 'vacation' , disposition= 'eco' , until= 3600 * 24 )

5.2.20. trace

elv_max_cube.trace (message)

This method sends the argument message to the application's trace panel. There is no return value.

5.3. Maintaining state between calls of the script

Frequently it is necessary to keep certain data from one call to the script to another. When the script returns an object, which can be of any type, then this object is passed to the next call of the script as the single argument. During the first call the argument is set to None. The following example illustrates the concept by incrementing the count each time the script is called:

import elv_max_cube

def controller (*args):

if args [ 0 ] is None:

result = 0

else :

result = args [ 0 ] + 1

elv_max_cube.trace ( "Count =" + str (result))

return result

When the argument args [0] is None, 0 is returned. Otherwise args [0] is incremented and then returned. Naturally, the result can be of any type. E.g. it can be a dictionary or list.

5.4. Controlling a boiler with an ESP8266 and a relay

This example illustrates using scripting in connection with MQTT messaging to control a boiler. The following picture illustrates the setup:

Here the components are:

Radiator thermostats which valves are monitored in order to turn the boiler on or off;

MAX! cube;

Raspberry Pi 2 or 3 running a Linux OS with MAX! home automation;

NodeMCU ESP8266 12E running Arduino;

Wemos D1 mini relay put on top of ESP8226.

MAX! home automation runs the following script:

import elv_max_cube

def controller (*args):

if elv_max_cube.get_valve_statistics ( 0xBB8F0 ) [ "average" ] > 0.1 :

elv_max_cube.publish_mqtt_message ( "relay" , "on" )

else :

elv_max_cube.publish_mqtt_message ( "relay" , "off" )

The script takes the valves statistics of the radiator thermostats of the cube. Here BB8F0 is the cube's RF-address. It compares the averaged valve position. If that is bigger that 10% the topic relay is published as on. Otherwise it is published as off.

NodeMCU

ESP8266 12E Wemos D1

mini relay + =

Now the Arduino program in C++ for ESP8266:

#include <OneWire.h>

#include <ESP8266WiFi.h>

#include <PubSubClient.h>



const char * WiFi_SSID = "<your WiFi network>" ;

const char * WiFi_Password = "<your WiFi password>" ;

const char * MQTT_Server = "<your MAX! home automation IP address>" ;

const int MQTT_Port = 1883 ;

const int Relay_Pin = D1;



OneWire One_Wire_Bus ( 4 ); // onewire bus

WiFiClient Networking; // WiFi

PubSubClient MQTT_Client (Networking); // MQTT



void Update_Subscription ( char * topic, byte * payload, unsigned int length)

{ // This is called each time any subscribed topic arrive

if ( 0 == strcmp (topic, "relay" ))

{ // This is the topic "relay" we are interested in

if ( 2 <= length && 'o' == payload [ 0 ] && 'n' == payload [ 1 ])

{ // Turn on

Serial.println ( "Relay on" );

digitalWrite (Relay_Pin, HIGH);

}

else

{ // Turn off

Serial.println ( "Relay off" );

digitalWrite (Relay_Pin, LOW);

} }

}



void setup ()

{ // This is called once upon start

Serial.begin ( 9600 ); // Initiate serial console with 9600 baud

Serial.println ( "Starting" );



pinMode (Relay_Pin, OUTPUT); // Configure relay

setup_wifi (); // Setup WIFI



// Setup MQTT, but connect later from the main loop

MQTT_Client.setServer (MQTT_Server, MQTT_Port);

MQTT_Client.setCallback (&Update_Subscription);

}



void setup_wifi ()

{

delay ( 10 );

// We start by connecting to a WiFi network

Serial.println ();

Serial.print ( "Connecting to " );

Serial.println (WiFi_SSID);



WiFi.begin (WiFi_SSID, WiFi_Password);



while (WiFi.status () != WL_CONNECTED)

{

delay ( 500 );

Serial.print ( "." );

}

Serial.println ( "Connected, IP address: " );

Serial.println (WiFi.localIP ());

}



void Reconnect_MQTT ()

{

while (!MQTT_Client.connected ())

{ // Loop until we're reconnected

Serial.print ( "Connecting to MQTT broker ..." );

if (MQTT_Client.connect ( "ESP8266Client" ))

{

Serial.println ( "Connected" );

MQTT_Client.subscribe ( "relay" ); // Subscribe to the topic

}

else

{

Serial.print ( " failed to connect, rc=" );

Serial.print (MQTT_Client.state ());

Serial.println ( " try again in 5 seconds" );

delay ( 5000 ); // Wait 5 seconds before retrying

} }

}



void loop ()

{ // Main loop

if (!MQTT_Client.connected ())

{ // Connect to the MQTT broker

Reconnect_MQTT ();

}

MQTT_Client.loop (); // Pump network I/O

}

The program connects to the MAX! home automation MQTT broker and subscribes to the topic relay. When the payload is on the relay is set to closed, otherwise it is set to open. This is done by the command digitalWrite.

5.5. Controlling a boiler with a MAX! switch

MAX! switch eQ-3 BC-TS-Sw-Pl, shown on the picture below

is a device that can be paired with the cube. No wall thermostat in the same room is required to control the switch.

In the overview the switch appears as a radiator thermostat. It has the schedule just like radiator and wall-mounted thermostats do with the effect that the set temperature controls switching it on and off:

25.0 °C turns the switch on;

15.0°C turns the switch off.

The automatic mode schedule can be used for example for turning plant lights on and off. The same temperatures work in the manual mode too. This can be used for controlling a boiler. The following script illustrates the principle:

import elv_max_cube

def controller (*args):

if elv_max_cube.get_valve_statistics ( 0xBB8F0 ) [ "average" ] > 0.1 :

elv_max_cube.set_mode (mode= 'manual' , cube= 0xBB8F0 , device= 0x724EC , temperature= 25.0 )

else :

elv_max_cube.set_mode (mode= 'manual' , cube= 0xBB8F0 , device= 0x724EC , temperature= 15.0 )

The script looks the average valve positions and if that is greater then 0.1 (10% open) it sets the manual mode temperature to 25.0 °C. Otherwise it sets it to15.0°C. The sample code assumes the cube's RF address BB8F0 and the switch's address 724EC.

Another switching device which can be used this way is the MAX! boiler control actuator eQ-3 BC-TS-Sw2-WM:

6. Running headless and other issues

6.1. Headless

In order to run the application headless under Linux use the following command (bash):

>xvfb-run -a max_home_automation

Here Xvfb is X virtual frame buffer, an X11 server that uses memory instead of a physical display. You might need to install it on your system if the package xvfb is not there.

6.2. Running remotely

Before running the application headless you might wish to change some settings or monitor how things work in general. You can run it remotely using the PC's X11 local server as the display for the remotely running program.

Linux . Under Linux you use SSH with X11 forwarding. The command line looks like follows:

>ssh -X -v <user> @ <host> max_home_automation

Here <user> is the user you want to log in and <host> is the address or name of the machine where you want to run it, e.g. on your headless Raspberry PI. On the other side you must have the SSH daemon running.

Windows . Under Windows you need an X11 server and SSH client. There is a software that combines both: MobaXterm. Under MobaXterm you can configure an SSH session with X11 forwarding.

6.3. Permission denied

When you get error 13 permission denied upon HTTP start, that is because you lack permissions to bind the socket to the port 80. Usually only root can use ports with numbers below 1024 while the default HTTP port is 80. You may use another port or else run the software as root.

6.4. Settings file

The settings are kept in the GTK recent manager file named *.xbel. The file location depends on the OS, usually it is where other user specific files are. E.g. under Windows it is C:\Users\<user-name>\AppData\Local\recently-used.xbel. The file has XML format. The file contains data of all GTK applications, not only ones of MAX! home automation.

The settings related to MAX! home automation can be exported into a separate text file using the button at the bottom of the settings pane. The file lines have the format:

<setting> =" <value> "

It is one line per setting. When the value contains quotation marks ("), they are doubled. The file can also contain comment lines starting with -- . Note that defaulted settings are not exported, only ones actually changed from the default value and in effect are. Here is an example of exported the settings file:

----------------------------- Created 2019-12-07 12:00 -----------------------------

-- The file contains settings that are different from the defaults.

cors-max-age="86400"

cors-headers="X-PINGOTHER, Content-Type"

cors-methods="POST, GET, OPTIONS"

cors-origin=""

http-port="80"

http-address=""

host=""

height="978"

width="985"

y="104"

x="670"

mqtt-max-connections="100"

mqtt-max-subscriptions="1"

poll="10.0"

In order to restore previously exported settings MAX! home automation is started with the option --restore specified in the command line:

>max_home_automation --restore= <exported-settings-file>

This will remove all existing settings related to MAX! home automation and put ones from the file instead.

On Debian or Ubuntu ARM target you may have get messages like:

(max_home_automation: 1722 ): dbind-WARNING **: 17:36:59.591 : Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files

In order to fix these install the package at-spi2-core:

>apt install at-spi2-core

6.6. Styles, languages, icons etc

The appearance of the program graphical elements depends on the current GTK theme and styles. This includes texts that appear in the program as labels and tooltips. Such texts are defined as style properties. Some of the icons and pictures are embedded and cannot be changed, otherwise an icon of any button is defined by the current theme and can be changed too.

For changing the GTK, please, refer to the official GTK documentation. The default GTK styles can be modified for the given user globally by creating and editing a CSS file named gtk-3.0.css. CSS stands for Cascading Style Sheets. The file is placed into:

Under Windows : %LOCALAPPDATA%\gtk-3.0

Under Linux: $HOME/.config/gtk-3.0

Note that these style settings will be shared by all GTK programs.

Additionally to that a custom CSS file can be loaded using the option --load-css specified in the command line:

>max_home_automation --load-css= <custom-css-file>

When no option --load-css specified the program attempts to load file names max_home_automation.css from the working directory if it exists.

6.7. CSS file template

The number of styles is immense. They can be enumerated and exported into a CSS file supplied with current values using the button Export CSS at the bottom of the settings pane. Parts which need to be changed can be moved to one of the CSS files described above.

7. Building from sources

The program uses Simple Components for Ada, GtkAda contributions which are distributed with it. In order to compile it, GtkAda must be installed first. Then with GNAT compiler it can be built using the following command:

>gprbuild -XDevelopment=Release -p -P max_home_automation.gpr

issued in the directory containing the sources. Additionally depending on the target the following scenario variables are set:

-Xodbc=unixODBC on Linux targets, for Windows the value is ODBC32;

on Linux targets, for Windows the value is -XTarget_OS=Linux on Linux targets, for Windows the value is Windows_NT;

on Linux targets, for Windows the value is -XAtomic_Access=Pragma_Atomic and -Xarch=x86_64 on 64-bit targets. Other values for arch are i686 and armhf . On 32-bit platforms use -XAtomic_Access=GCC-long-offset.

For example building on Windows 64-bit:

>gprbuild -XDevelopment=Release

-XAtomic_Access=Pragma-atomic

-Xarch=x86_64

-p -P max_home_automation.gpr

For example building on a Linux 64-bit:

>gprbuild -XDevelopment=Release

-XAtomic_Access=Pragma-atomic

-Xarch=x86_64

-Xodbc=unixODBC

-XTarget_OS=Linux

-p -P max_home_automation.gpr

Building on an ARM Linux 32-bit:

>gprbuild -XDevelopment=Release

-XAtomic_Access=GCC-long-offsets

-Xarch=armhf

-Xodbc=unixODBC

-XTarget_OS=Linux

-p -P max_home_automation.gpr

You can also use the max_home_automation.gpr project with the GPS and compile it from there selecting scenarios described above.

8. Changes log

The following versions were tested with the compilers:

GNAT Studio Community 2020 (20200427)

GNAT Community 2019 (20190517-83)

GNAT Community 2018 (20180523-73)

GNAT 8

GNAT 9

and the GtkAda versions:

GtkAda 3.14.15

Changes (1 September 2020) to the version 3.17:

Legacy configuration file loading bug fixed;

Time zone handing under Windows 7 fixed to work around its dealing with RTZ (Russian Time Zone).

Changes (2 June 2020) to the version 3.16:

Daylight saving time zone settings bug fixed.

The following versions were tested with the compilers:

GNAT Community 2019 (20190517-83)

GNAT Community 2018 (20180523-73)

GNAT 8

GNAT 9

and the GtkAda versions:

GtkAda 3.14.15

GtkAda 2019

Changes (7 May 2020) to the version 3.15:

Built for Fedora 32.

Changes (11 March 2020) to the version 3.14:

Double click on the thermostat opens its configuration panel;

A custom web-page can be added to the HTTP server in order to provide user interface;

A custom dashboard sample code in FusionCharts was added;

HTTP REST API JSON cubes list request added;

Exporting current parameters in effect into a CSS file was added to the settings page;

A command line option to load styles from a custom CSS file was added;

HTTP REST API JSON room list request bug fix;

Updating the room name in the monitor pane upon renaming fixed.

Changes (1 March 2020) to the version 3.13:

The update check was added to be about box.

Changes (31 January 2020) to the version 3.12:

MQTT topic added to reflect device error state.

Changes (14 January 2020) to the version 3.11:

The temperature scale of the graphs can be switched from auto-scaling to fixed scale mode;

The graph paper and refresh settings have effect on all graphs;

The monitor pane was added to show graphical overview of all rooms.

Changes (3 January 2020) to the version 3.10:

The order of cubes on the overview pane can be changed. The modified order is then restored upon next application start.

Changes (10 December 2019) to the version 3.9:

Bug in handling cube time skew fixed. The bug caused crashes on 32-bit systems, e.g. armhf, when the cube's time was unset (detected as large time difference to the host time);

Linux dependencies of the prebuilt package are fixed.

Changes (7 December 2019) to the version 3.8:

Exporting settings into and importing them from a text file was added;

MQTT server persistent session rejection fix, when the number of accumulated messages is too large;

The size of output MQTT server messages buffer can be set.

Changes (1 December 2019) to the version 3.7:

Database offset and valve columns insert bug fix;

MQTT set mode topic added.

Changes (24 November 2019) to the version 3.6:

Minor code improvements.

Changes (21 November 2019) to the version 3.5:

LAN scanning dialog is improved, tooltip added;

Debian and Ubuntu package dependencies fixed.

Changes (2 November 2019) to the version 3.4:

Bug fix in the I/O worker task causing its exit;

Added changing the thermostat's eco temperature per HTTP and MQTT.

Changes (7 October 2019) to the version 3.3:

Bug fix in setting thermostat schedule;

Bug fix in turning off the trace file append mode.

Changes (6 August 2019) to the version 3.2:

Bug causing an existing trace file overridden when newly selected and marked to append was fixed.

Changes (6 August 2019) to the version 3.1:

The graph panes are ordered in the same order as the corresponding rooms in the overview page;

Reduced number of situations when an error popup dialog appear;

Program crash when renaming a thermostat fixed;

Bug fix when setting thermostat schedule remotely over HTTP.

The following versions were tested with the compilers:

GNAT Community 2018 (20180523-73)

GNAT 8

GNAT 9

and the GtkAda versions:

GtkAda 3.14.15

Changes (18 May 2019) to the version 3.0:

Graph pane bug fixed. The bug prevent showing more than one graph tab per cube;

Bug fix in MQTT server that prevented it from start (indicated by Constraint_Error in trace).

The following versions were tested with the compilers:

GNAT Community 2018 (20180523-73)

GNAT 8

and the GtkAda versions:

GtkAda 3.14.15

Changes (14 May 2019) to the version 2.18:

Thermostat scanning procedure's error handling has been improved;

Indicating of faulty and orphaned devices was added;

A button and dialog were added to remove faulty devices;

Pairing devices was added;

Devices and rooms renaming was added;

Devices deletion was added;

Devices wake up button has been added;

Exporting devices to ELV MAX! software was added;

Saving and restoring the topology of devices and rooms was added;

Cube's NTP server setting was added;

Tracing to file in appending mode added;

When restoring configuration of devices or setting parameters of devices the devices are woken up.

Changes (11 Jan 2019) to the version 2.17:

Cube communication bug fix for 32-bit operating systems.

Changes (8 Jan 2019) to the version 2.16:

Guards added against malfunctioned cubes.

Changes (22 Dec 2018) to the version 2.15:

Python scripting exception handing changed to guard against invalid object references;

Loading Python library under Windows was improved for the cases when Python is installed in another directory, error hits were added;

Python 3.7 support added;

Python script settings bug fixed;

The Valve positions average shown for the cubes on the overview pane;

The Duty cycle queries were added to MQTT, HTTP and Python;

GTK libraries for Windows 64-bit were replaced with the build having no (or just lesser?) memory leak;

Airing time getting/setting bug fix.

Changes (11 Dec 2018) to the version 2.14:

Time indication rounding replaced with ceiling;

Polling interval wait is continued rather than started over after sending a command;

Application exits immediately without waiting for expiration of the polling interval;

Python script set_mode can have only positional parameters;

Python script set_mode device key was made equivalent to the thermostat key. Either spelling is acceptable;

key was made equivalent to the key. Either spelling is acceptable; Python script get_connection, disconnect, reconnect were added to query and manage the cube connection;

Several cubes to connect to can be specified at the settings page;

Added buttons to disconnect and reconnect to a cube;

Cube connection state is now reflected in the cube icon, link icon and connection/disconnection time stamp;

HTTP server was enhanced with queries get-connection , reconnect , disconnect ;

, , ; MQTT broker changed to publish the cube connection status and to allow controlling the cube connection.

Changes (2 Dec 2018) to the version 2.13:

Indication of the time to the next polling of the cube;

Scroll bars for the setting page panes;

Wall thermostat display can be set to indicate measured or set temperature.

Changes (25 Nov 2018) to the version 2.12:

MQTT broker publishes link tokens for shutter contacts and thermostats;

A thermostat schedule can be set using the MQTT broker or the HTTP server;

Tracing is extended.

Changes (18 Nov 2018) to the version 2.11:

Bug fix in simultaneous mode change of multiple thermostats;

Wall thermostat temperature offset added to the settings page. The offset can be stored and restored. Legacy configuration files are still eligible with the wall thermostat offset is defaulted to zero.

Changes (9 Nov 2018) to the version 2.10:

Bug fix in the graph pane caused daylight saving shift;

Bug fix in the MQTT broker.

Changes (5 Aug 2018) to the version 2.9:

This version supports both 32-bit and natively 64-bit Windows.

The following versions were tested with the compilers:

GNAT 8

GNAT 7

GNAT 6

and the GtkAda versions:

GtkAda 3.14.2

Changes (2 June 2018) to the version 2.8:

Handling faulty devices added;

Calendar date selection dialog bug fixed (wrong month).

Changes (4 May 2018) to the version 2.7:

Rooms and devices in the overview panel can be reordered. The order is saved and restored by the next start;

SSL/TLS tracing added;

SMTP mail-client workarounds added to handle non-conform server responses;

Erroneous device state is indicated together with the link errors.

Changes (10 February 2018) to the version 2.6:

Behavior under system load improved.

Changes (1 February 2018) to the version 2.5:

Database logging bug fixed.

Changes (28 January 2018) to the version 2.4:

Database errors recovery added. On errors connection is reopened and failed record re-inserted;

A check box added on the settings page to turn on and off adding the valve offset to the set-temperature on the graphs pane;

Memory leak fixed;

The device name added to the response to the HTTP request get-status-csv;

Tracing database operations added to the settings pane;

Servicing requests under high CPU load was balanced to ensure cube polling;

Bug fixes in database communication;

Minor bug fixes in saving and restoring settings.

Changes (9 January 2018) to the version 2.3:

HTTP queries get-rooms-list and get-rooms-json-list were added to get the list of rooms with the device addresses in each room.

Changes (6 January 2018) to the version 2.2:

Minor bug fixed;

Example of controlling a relay from a script.

Changes (26 December 2017) to the version 2.1:

Extended trace option added;

Added trace to file option.

Changes (21 December 2017) to the version 2.0:

MQTT maximum connections and subscriptions settings added.

Changes (18 December 2017) to the version 1.12:

MQTT broker bug fix that prevented publishing thermostat temperatures;

MQTT broker settings allow specifying if the broker should accept publishing requests from the clients on unknown topics. The topics can be limited by a list of MQTT topic patterns.

Python scripts added.

Changes (26 November 2017) to the version 1.11:

Saving and restoring wall thermostat settings added.

Changes (5 September 2017) to the version 1.10:

Preview window added to the configuration file chooser dialog;

Restoring configuration from a file is made more lenient towards data format errors.

Changes (26 July 2017) to the version 1.9:

HTTP requests added to get the average, maximum, minimum valve positions;

MQTT topics added to subscribe to the average, maximum, minimum valve positions.

The following versions were tested with the compilers:

GNAT GPL 2016 (20160515-49)

GNAT 6

and the GtkAda versions:

GtkAda 3.14.2

Changes (18 April 2017) to the version 1.8:

MQTT on WebSockets added;

The MQTT server's topic names have the leading '/' removed because some MQTT clients reported having troubles with names starting with '/';

When controlled over HTTP or MQTT the target temperature can be specified to be airing, comfort, and eco according to the thermostat settings;

The thermostat address can be omitted when in control commands over HTTP or MQTT. In this case the command applies to all thermostats;

HTTP access control (CORS) support added;

HTTP server JSONP support added.

Changes (22 February 2017) to the version 1.7:

Save changes warning dialog on schedule tab closing;

The thermostat temperature can be set in the automatic mode;

The graph page contains set temperature + offset curve;

Various minor bug fixes;

Data logger added. The logger is backed by a database accessed either through ODBC or by SQLite.

Changes (5 February 2017) to the version 1.6:

Multiple selection is supported, e.g. to change the modes of all thermostats;

E-mail reports of the devices which batteries are low;

Bug fix in the wall thermostat parameters setting page;

CSV output of device status reports queried through HTTP;

Graph page contains valve position curves.

Changes (21 November 2016) to the version 1.5:

Monitoring duty cycle was added;

Commands that control thermostats are serialized and monitored for failures;

Cube radiator thermostats configuration save and restore added;

Thermostat schedule and parameters upload diagnostics added;

Offset temperature can be set negative;

HTTP server request documentation get-set_temperature changed to get-set-temperature ;

changed to ; Save file dialogs ask override confirmation;

Bug fix in HTTP server that prevented querying the thermostat's measured temperature;

Valve position is correctly reported;

Thermostat parameters and schedule optimized to minimize RF traffic;

Documentation extended with instructions how to run the application remotely or in the headless mode.

Changes (25 July 2016) to the version 1.4:

Compatibility with GtkAda 3.14

The following versions were tested with the compilers:

GNAT GPL 2015 (20150428-49)

GNAT 5.3

GNAT 6

and the GtkAda versions:

GtkAda 3.8.3

Changes (31 May 2016) to the version 1.3:

Debian version is moved to gcc-6;

Minor bug fixes in the MQTT server.

The following versions were tested with the compilers:

GNAT GPL 2015 (20150428-49)

GNAT 5.3

and the GtkAda versions:

GtkAda 3.8.3

Changes (13 April 2016) to the version 1.2:

MQTT server added;

LAN discovery by default scans all known interfaces;

Settings page allows explicit setting of the host address to scan or cube address to connect.

Changes (5 March 2016) to the version 1.1:

Scanning radiator thermostats technique implemented;

Graphs page changed to indicate radiator thermostat temperatures.

Changes (18 October 2015) to the version 1.0:

Wall thermostat support;

Graphs page to represent wall-mounted thermostat temperatures as running curves;

Integrated HTTP server for remote control and automation;

Bug fixes.

Version 1.0 released (24 August 2015).

9. Table of Contents