Skip to content

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

3 watching

Forks

Latest commit

 

History

562 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linux - Home Control Daemon (homectld)

Written by: Jörg Wendel (linux at jwendel dot de) Project page: https://github.com/horchi/linux-homectld

License

This code is distributed under the terms and conditions of the GNU GENERAL PUBLIC LICENSE. See the file LICENSE for details.

Donation

If you like the project and want to support it

paypal

Features

Supported Sensors / Interfaces

These sensors and interfaces are supported out of the box

These sensors and interfaces are indirectly supported

  • HomeMatic - by using NodeRed as an adapter to the CCU

Some Screenshots

As Camper Control

As Pool Control

Disclaimer

USE AT YOUR OWN RISK. No warranty. This software is a private contribution and not to any products and can also cause unintended behavior. Use at your own risk! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The software was created for personal use, it's published here for free under the GPLv2. The construction of the required hardware, especially in the handling of mains voltage (230V, 110V, ...), is carried out on one's own responsibility, local guidelines and regulations must also be observed especially in connection with water and pool! All explanations about the hardware are only indications of how it is technically feasible, security and legal provisions are not discussed here!

Prerequisites:

The described installation method and dependencies fit to ubuntu 24.04, the homectld work also with other Linux distributions

Language pack 'de_DE.UTF-8' is required as language package:

dpkg-reconfigure locales
dpkg-reconfigure keyboard-configuration
reboot

Set system timezone, for example Berlin

timedatectl set-timezone Europe/Berlin

Change hostname

hostnamectl set-hostname new-name

Building and installing by source

Preliminary

Update your system

apt update
apt dist-upgrade

Perform all following steps as root user! Either by getting root or by prefix each command with sudo. To become root type: sudo -i

Installation of the MySQL Database

It's not required to host the database local at the Raspberry. A remote database is supported as well.

apt -y install mariadb-server

or if you like mariadb-server 10.6 on ubuntu 20.04 follow this instructions: https://community.hetzner.com/tutorials/how-to-install-mariadb-10-6-on-ubuntu-20-04

Set the database root (db admin) password during installation!

Local database setup:

If the database server is located locally (on same host as the daemon):

> mysql -u root -Dmysql -p
  CREATE DATABASE homectl charset utf8;
  CREATE USER 'homectl'@'localhost' IDENTIFIED BY 'homectl';
  GRANT ALL PRIVILEGES ON homectl.* TO 'homectl'@'localhost' IDENTIFIED BY 'homectl';
  flush privileges;

Remote database setup:

if the database is running remote, or you like to have remote access to the database:

> mysql -u root -Dmysql -p
 CREATE DATABASE homectl charset utf8;
 CREATE USER 'homectl'@'%' IDENTIFIED BY 'homectl';
 GRANT ALL PRIVILEGES ON homectl.* TO 'homectl'@'%' IDENTIFIED BY 'homectl';
 flush privileges;

install the build dependencies

The short way, after Make.user is set up:

make install-dependencies

It installs the packages below via apt, only the missing ones, installed packages are never upgraded. libgpiod (version 2 needed) and libwebsockets (4.3.0 needed) are built from source if the versions of the distribution are too old (e.g. Ubuntu 24.04 / Armbian noble ship libgpiod 1.6), the sources go to SRC_DIR from Make.user (default /usr/src). The optional parts follow the switches of Make.user: WOMO adds mariadb-server, mosquitto and the network tools (dnsmasq, NetworkManager, chrony, LTE modem, iptables, openvpn), MOPEKA the python BLE modules, THETFORD paho-mqtt and usblini, GARMIN the python venv, GPS the python modules of the u-blox GPS service (gps.u-blox, installed with make install like the others). Always call it via make, the script contrib/install-dependencies.sh gets the switches from there. DRYRUN=1 make install-dependencies only shows the commands. The manual way:

apt -y install build-essential libssl-dev libcurl4-openssl-dev uuid-dev libcap-dev libsystemd-dev cmake pkg-config net-tools nodejs
apt -y install libjansson-dev libmariadb-dev liblua5.3-dev mosquitto-clients jq jo bc aptitude util-linux-extra libgpiod2 libgpiod-dev

If you integrate bluetooth devices

apt -y install libbluetooth-dev

If you NOT use a global mosquitto borker somewhere in your local network

apt -y install mosquitto

to make the broker accessible via network

Put this lines into your /etc/mosquitto/mosquitto.conf:

listener 1883
allow_anonymous true

and restart mosquitto systemctl restart mosquitto

Raspberry Pi

Mit libgpiod (besser) - zwingend Version 2 !

apt -y install libgpiod-dev

zur Not fallback auf wiringpi

apt -y install wiringpi

Odroid

Mit libgpiod (besser) - zwingend Version 2 !

apt -y install libgpiod-dev

zur Not fallback auf wiringpi

apt -y install libwiringpi-dev libwiringpi2 odroid-wiringpi

get and install libwebsock

We need to install it manually in case the version shipped with the distribution is to old (we need at least version v4.3.0)

if the version shipped with the distribution is okay simple

apt -y install libwebsockets-dev

else get it from git

cd /usr/src/
git clone https://github.com/warmcat/libwebsockets.git
cd libwebsockets
mkdir build
cd build
cmake ..
make install
ldconfig

get, build and install the homectld

cd /usr/src/
git clone https://github.com/horchi/linux-homectld/
cd linux-homectld
make clean all
make install

Now the home control daemon is installed in folder /usr/local/bin Check /etc/homectld/daemon.conf file for setting of your database login. If you have used the defaults above no change is needed.

One Wire Sensors

Enable One Wire Sensors at your Raspberry PI

To make the sensors available to the Raspberry PI you have to load the w1-gpio module by registering it in /etc/modules (here in detail : http://www.netzmafia.de/skripten/hardware/RasPi/Projekt-Onewire/index.html)

echo "w1-gpio" >> /etc/modules
echo "w1_therm" >> /etc/modules
echo "dtoverlay=w1-gpio,gpioin=4,pullup=on" >> /boot/config.txt

Enable One Wire Sensors at your Odroid N2+

echo "w1-gpio" >> /etc/modules-load.d/1wire.conf
echo "w1_therm" >> /etc/modules-load.d/1wire.conf

Addd this two lines into the [generic] section of the /boot/config.ini

overlays="w1-gpio"
overlay_param="gpiopin=476"

Reboot to apply this settings!

The homectld checks automatically if there are 'One Wire Sensors' connected, each detected sensor will be configurable via the web interface.

HASP Panel (openHASP Display)

homectld can drive an openHASP touch panel (e.g. the Elecrow CrowPanel 5.0) as a small wall or vehicle display. The pages of the panel are configured in the WEBIF under Setup -> HASP Panel, stored in the tables hasppages / hasppagewidgets and sent to the panel via MQTT.

Prerequisites

  • openHASP firmware on the panel, MQTT configured to the same broker as homectld (the group topic of the panel is hasp/plates by default)
  • configuration item HASP Panel -> haspMqttTopic (hasp/plates or hasp/<hostname>); empty = disabled
  • for MDI symbols the complete Material Design Icons font has to be uploaded to the panel once as /mdi.ttf (the file of the WEBIF: htdocs/mds/fonts/materialdesignicons-webfont.ttf), e.g. curl -F "data=@materialdesignicons-webfont.ttf;filename=/mdi.ttf" http://<panel-ip>/edit, then reboot the panel

Pages and widgets

  • a page has up to 4 rows, each row 1..3 widgets (cards); pages are changed by swiping left/right on the panel
  • per slot a sensor is selected (filter and sorting by type / name), Setup opens the widget dialog
  • when a sensor is selected its settings are copied once from its dashboard widget (if it is on a dashboard, otherwise the sensor defaults); afterwards the HASP settings are independent of the dashboard
  • widget types: Symbol (MDI symbol, color shows the state, acts as toggle button; with the option Spin bei Aktivität a ring turns around the symbol while the sensor is on, also for SymbolValue), SymbolValue, Value, Text, Meter (gauge), MeterLinear (vertical bar with scale, thermometer for °C), Time (clock and date, rendered by the panel), Level (MDI symbol filled by percentage in the bar color, e.g. mdi-water, mdi-propane-tank, mdi-car-battery; range from the scale of the widget) (the fill refers to the drawn height of the symbol; the option Füllung als Fläche fills a rectangle behind the symbol instead, for frame symbols without a filled variant, e.g. mdi-crop-landscape as a tank) and Choice (the choices of the sensor as button matrix, 2 per row up to 4 choices, otherwise 3; the current value is the highlighted button in the 'on' color, a touch sends the choice to the command topic of the sensor like the dashboard choice widget; without a command topic the selection is reverted) and Balance (horizontal bar with zero in the middle for bipolar values like the battery current or the grid power: negative values fill to the left in the first color (default red), positive to the right in the second (default green); Skala Max is the full scale, the range is symmetric unless a negative Skala Min is given; Richtung tauschen mirrors the bar for sensors reporting discharge positive; Level and Balance exist as dashboard widgets too) and Timer (pseudo sensor Timer in the sensor list: countdown of 1..60 minutes with mm:ss, progress bar and step buttons (Schritte, comma separated minutes or seconds with suffix s, e.g. -30s,30s,5,30, default -1,1,5, up to 6) plus Start-Stop; it runs in the panel firmware and keeps running without WLAN, the board buzzer beeps for a minute when the time is up, any touch on the card stops it; a touch on the time starts/stops, a long press resets to the configured minutes; needs the custom firmware >= 0.7.0.2, see openHASP/README.md)
  • the pages are sent after saving, with the button An Panel senden and automatically whenever the panel comes online (openHASP forgets objects received via MQTT on reboot); further buttons: Panel wecken (display on, the idle cycle restarts), Panel neu starten and Screenshot (the screenshot page of the panel's web server, the IP is taken from the panel's status report)
  • the idle times of the panel (Leerlauf, seconds) are set on the same page: after the short one the panel dims, after the long one the display goes off (what exactly happens is defined by the scripts idle_short.cmd / idle_long.cmd / idle_off.cmd on the panel). They are stored as the configuration items haspIdleShort / haspIdleLong and sent with the pages (config/gui, idle1 / idle2); 0 switches a stage off, both 0 leaves the panel's own setting untouched
  • values are pushed on every sensor change; openHASP has no chart object, so charts are not available

GPS Tours (recording of trips)

With a GPS receiver (see gps.u-blox, sensor type GPS) the page Map of the WEBIF shows the live position. In addition trips can be recorded as tours and displayed later. Any receiver delivering NMEA 0183 on a serial port works (USB pucks, modules on a UART), see the receiver notes in gps.u-blox/README.md; the receiver settings of the GPS configuration are u-blox specific. While a tour is recorded the Map tab of the main menu and the Touren button of the tool bar are colored (red, orange while paused), the tour head shows the driving time (duration minus pauses); a pause / night stop entered during an automatic pause takes over its begin, end and duration.

Recording

  • Map -> ● Tour aufzeichnen: enter a name and optionally the odometer reading [km] and start the recording (needs control rights), ■ Tour beenden stops it and asks for the odometer at the end. Only one tour can be active at a time.
  • while a tour is active the tool bar offers buttons for events: fuel stop (liters, total price, odometer), toll, pause, night stop (each with optional costs) and a note. Every event is stored with its time and the current GPS position (table gpstourevents). The odometer readings at start and end are columns of gpstours, they can be corrected later together with the name (pencil button in the tour list)
  • costs and fuel consumption are calculated from the events: total costs (fuel + toll + other), consumption per fuel stop = liters / km since the previous full tank and the average over all fuel stops with a known distance. The chain starts at the last full fuel stop before the tour (of any tour or entered outside of a tour, see below), so the first fill of a tour is not charged to the tour's km only; without any earlier fuel stop the odometer of the tour start is used (marked ab Tour-Start, assumes a full tank at the start). The distance is taken from the odometer readings (Tacho) and in parallel from the GPS distance of the tour at the moment of the fuel stop (GPS, stored with the event, tour-bound by nature), both values are shown. A fuel stop marked nicht voll (partial fill) gets no value of its own, its liters are added to the next full one. They are shown in the tour list, in the info box of the displayed tour and in the control panel while recording
  • while a tour is active a point is stored whenever the position moved at least gpsTourMinDistance meters (Setup -> Configuration -> GPS, default 25 m) since the last stored point. Standing still therefore does not produce data.
  • without movement for gpsTourPauseAfter minutes (default 5) the tour is paused, on the next movement it continues automatically. The pause time is summed up and shown in the tour list. A GPS receiver drifts by 100-200 m at standstill, therefore 'movement' for the pause means leaving the radius gpsTourPauseRadius (default 200 m) around the position of the last movement; while paused no points are stored. Each pause is kept as event Stillstand with begin, end and duration in the event list of the tour (only its note is editable).
  • the active tour survives a restart of the daemon: it is continued automatically, including the distance and the point count.
  • the recording is bound to the sensor GPS:0x0a (Coordinate), it has to be active. While a tour is active its Record flag in Setup -> IO Setup is set by the daemon, when the tour is stopped it is cleared.
  • u-blox receivers can suppress the drift themselves: Empfänger: Static Hold (Setup -> Configuration -> GPS, km/h, 0 = off) freezes the position below that speed, Dynamikmodell Automotive selects the vehicle platform model, Galileo aktivieren switches a u-blox 8 to GPS + Galileo + GLONASS (the u-blox 7 has no Galileo). The daemon sends them to gpsmqtt.py (which owns the serial port) at its start, on change and when the script registers, the script writes them into the receiver on every port open (see gps.u-blox/README.md).

Dish alignment

The view Sat (satellite button of the tool bar) helps aligning the TV dish: from the live position it calculates azimuth (true north), elevation and LNB skew to the satellite of TV-Satellit: Orbitposition (Setup -> Configuration -> GPS, default 19.2 = Astra 19.2 E, west negative) and draws the bearing on the map (dashed blue arrow of 75 m with the azimuth), so that a landmark in that direction can be picked. The vehicle axis is the course of the last movement (sensor GPS:0x0b Heading of gpsmqtt.py, kept while standing, has to be active), drawn in blue with the outline of the motorhome (7 x 2.3 m, the GPS position as its center; orange and pointing north, gray, as long as the heading is unknown); the box shows the angle to the satellite relative to it ('44° links') with the vehicle from above, the switch rückwärts eingeparkt adds 180° when the vehicle was reversed into the pitch. For a crank mast (TV-Satellit: Kurbel, degrees per turn, default 12 for the Kathrein HDM 140, flat antenna horizontal at 0) the box shows the crank turns for the elevation, (90° − elevation) / degrees per turn. With the script sensor dvb-signal.sh the box also shows the signal monitor (lock, level, C/N about once per second) with start / stop. The sensor is configured as TV-Satellit: Signal-Sensor (e.g. SC:0x30), without it the view is not offered.

Storage

  • table gpstours holds only the tour itself: name, start, stop, distance [m], number of points and pause time [s]
  • the points are the samples of GPS:0x0a (text lat/lng) between start and stop of the tour. With each point the speed (GPS:0x03) and the altitude (GPS:0x05) are stored with the same time stamp (if the sensors are active), e.g. for speed or altitude profiles. Outside of a tour nothing is stored by the tour recording
  • tour samples have AGGREGATE = 'T'; they are never aggregated or deleted by the aggregation, so the tours stay complete. Regular samples ('S', sensors with the Record flag) are aggregated as before
  • deleting a tour in the WEBIF also deletes its samples (coordinate, speed, altitude) from samples and its events from gpstourevents

Display

  • Map -> Live: the map follows the live position (button Live-Tracking on the map)
  • on stop the daemon proposes the arrival at the final position as end of the tour (the time the position came within Tour: Toleranz Ankunft meters of the final position and stayed there, default 200 m) - for a forgotten stop or a long wait at the reception; jetzt is offered as well
  • Map -> Touren: the recorded tours are listed by name in the panel on the left, the selected one shows its details on the right (start, end, duration, pause, distance, odometer, costs, consumption) and the list of its events (time, kind, liters, price, odometer, consumption, note) where events can be changed or deleted (control rights); Karte draws the tour on the map (green marker = start, red marker = end) with the events as icons, their popup shows the details and offers the same actions; the pencil button changes name, odometer readings and a free comment for the whole tour (shown above the details), the trash button deletes the tour (control rights). The active tour can be displayed too but not deleted. Entering an event never changes the view.
  • Zentrieren (button on the map) brings the whole route into view

Fuel stops and consumption

  • Map -> Tankstopps (gas station icon) lists all fuel stops: the ones entered during tours (with a link to the tour) and the ones entered here outside of a tour (Tankstopp eintragen, control rights) with date / time, odometer, liters, total price, voll getankt and a note. The position is the current GPS position if available
  • the consumption is calculated across the tours from full tank to full tank by the odometer readings: liters / km since the previous full fuel stop, partial fills are added to the next full one. The head shows the average over all counted fills, the total liters and costs and the average price per liter. A fuel stop without odometer or the first one gets no value
  • the fuel stops are the events of type fuel in gpstourevents, standalone ones have TOURID = 0; editing / deleting works from this list as well as from the tour

Garmin Activities

The tab Garmin shows the activities of a Garmin Connect account (e.g. recorded with a Fenix watch), grouped by activity type. It is offered when the Garmin login exists (/etc/homectld/garmin).

  • installation and the one time login: see garmin/README.md (garmin.py login, works with and without two factor authentication, the tokens are valid for about a year)
  • nothing is polled - the activities are fetched only with the button Abgleich mit Garmin, which asks for the period (since the last activity, since a date or all). Data volume about 3-4 kB per activity, tracks are not downloaded
  • the activities are stored in the table activities, not as sensors and not in samples
  • groups per type are collapsible and show count, total duration, total distance and the maximum speed; water sports (windsurfing, kiteboarding, sailing, ...) show the speeds in knots, running and walking as pace
  • Details shows the values of the activity. The sync already stores most of them from the list entry (speeds, heart rate, calories, elevation, training effect, min/max temperature, ...) as a preliminary version; Details nachladen fetches the rest from Garmin (min. heart rate, average temperature), the dot in the list marks the fully loaded ones
  • a click on a row opens the details, the pencil button a dialog to change the type (e.g. activities recorded with a wrong profile), the name and the location at Garmin, the trash button deletes the activity at Garmin (after confirmation); both need control rights
  • bulk edit: check boxes in the rows (the one in the group header selects the whole group, a click with Shift or Ctrl selects all rows from the last clicked one to this one), Auswahl bearbeiten changes type, name and/or location of all selected activities, empty fields stay as they are; Garmin has no bulk update, garmin.py sends one request per activity with all changed fields (about 1 s each)
  • filters: type, period, name and location (name / location are regular expressions, the location field suggests the known locations); a click on a column header sorts the lists (again: reverse)
  • Track in the details loads the GPS track, simplified by Garmin to 1000 points or at least one point per 15 seconds, and shows it on a map colored by speed, hovering a segment shows speed, time and altitude; details and tracks are cached in the tables activities / activitytracks, Neu laden fetches them again
  • distance check: the watch sums up (almost) no distance in some water sport sessions although the GPS track is complete (e.g. 6 m for 35 minutes of windsurfing). When a track is loaded its length (sum of the segments) is stored in activities.trackdistance; if Garmin's distance deviates by more than the configured percentage (Distanz aus dem Track ab Abweichung, default 50 %, 0 = off) the list, the group sums and the details show the track's distance (in italics, the details show both values). The sync of the list does not load tracks, so the check only applies to activities with a loaded track
  • Karte: world map with the start positions of the activities as points colored by type (the legend filters by type), a click on a point opens the track; the period, name and location filters apply
  • the maps have a Zentrieren button which brings the whole route / all points into view

I2c Bus

Rasperyy Pi

How to enable i2c on a Rasperyy Pi is well documented in the www

Odroid

for Odroid I use Armbian (noble), you find a script to check the i2c bus in the contrib folder. It supports the n2+ and the m1! To check:

./contrib/check-i2c-header.sh
...
...
=== Ergebnis: I2C auf Pins 3/5 verfügbar ===
    Scan: i2cdetect -y 2

-> all fine. If Not try to fix the device tree by calling:

./contrib/armbian-enable-header-i2c.sh
Board: Hardkernel ODROID-N2Plus

→ N2+ erkannt: DTB-Patch

DTB: /boot/dtb/amlogic/meson-g12b-odroid-n2-plus.dtb (→ /boot/dtb-6.18.8-current-meson64/amlogic/meson-g12b-odroid-n2-plus.dtb)
i2c2_sda_x phandle: 0x82  (130)
i2c2_sck_x phandle: 0x83  (131)
Aktuell status:    status=okay
Aktuell pinctrl-0:                              pinctrl-0 = <0x82 0x83>;
Bereits korrekt — kein Patch nötig.

If you use the i2c2mqtt service shipped with the homectld you hav to use tbe i2c bus number provided by the check scipt, in case of the output above 2. Therefor use the device /dev/i2c-2.

To scan the bus:

root@odroidn2 (gpiod-dev u=) ~/source/homectld> i2cmqtt -d /dev/i2c-2 -S
start scan of i2c device '/dev/i2c-2' ..
0x38
.. done

And to read this sensor (In my case it's a DHT20) call:

root@odroidn2 (gpiod-dev u=) ~/source/homectld> i2cmqtt -d /dev/i2c-2 --dht 0x38 -s
-----------------------
DHT38
Temperature 23.95 °C
Humidity 51 %
-----------------------

A INA219 current/power monitor (default i2c address 0x40) is read with:

root@odroidn2 (gpiod-dev u=) ~/source/homectld> i2cmqtt -d /dev/i2c-2 --ina 0x40 -s
-----------------------
INA40 (shunt 0.1000 Ohm)
Bus voltage   12.416 V
Shunt voltage 21.340 mV
Current       0.213 A
Power         2.650 W
-----------------------

The shunt resistor defaults to 0.1 Ohm, which is the value most breakout boards are equipped with. If your board uses another one append it to the address, e.g. --ina 0x40:0.01 for a 10 mOhm shunt. Voltage, current and power are published as three values with the addresses 0, 1 and 2.

calling: i2cmqtt --help show the options and supported sensor types. You can start it as a service and provide the data by MQTT to the homerctld.

Time to first start of homectld

systemctl start homectld

And enable it for automatic start at boot

systemctl enable homectld

to check it's current state call

systemctl status homectld

it should now 'enabled' and in state 'running'!

also check the syslog about errors of the homectld, this will show all its current log messages

grep -i "error" /var/log/homectld.log

The WEB interface:

The default username and password for login to the web interface is:

User: homectl
Password: homectl

Connect to the WEB interface:

The default Port is 61109. You reacg the WEB interface with this url (replace your-ip):

http://your-ip:61109

URL Parameters

kiosk - start in kiosk mode:

// 0 - with menu, normal dash symbols, use normal-widget-height-factor // 1 - without menu, big dash symbols, use kiosk-widget-height-factor // 2 - with menu, big dash symbols, use kiosk-widget-height-factor // 3 - with menu, big dash symbols, use normal-widget-height-factor

The big dash symbols can also be enabled permanently by the config option 'Große Dashboard Symbole' (Setup -> Konfiguration -> WEB Interface), the URL parameter kiosk has priority.

heightFactor group backTime page - specify the start page (dashboard, setup, chart, ...) dash - specify the start page of the dashboard (dashbord ID)

For example to start in kiosk mode 2

http://your-ip:61109/index.html&kiosk=2

iOS home screen app

The WEB interface can be added to the iOS home screen (Safari -> share -> 'Add to Home Screen') and runs as a standalone app then. The status bar is transparent, the menu row is fixed at the top (since iOS 27 the status bar blurs scrolling content below it, a fixed element stays sharp). Note: iOS caches the page settings per icon, after an update of homectld delete and re-add the icon if the layout looks odd.

Fist steps with the WEB interface

To enable some sensors

  1. Log in to the web interface
  2. Go to Setup -> IO Setup
  3. Enable the desired sensors (for example, script sensors)

Script sensors are the scripts in /etc/homectld/scripts.d (templates example_sensor.sh and example_switch.sh), one sensor per script. dvb-signal.sh is a switch for aligning a satellite dish: a click starts a monitor which reads dvb-fe-tool --femon (package dvb-tools) and shows lock, signal level and C/N about once per second as the text of the widget (type SymbolText), a second click or 30 minutes stop it. The adapter number is the argument of the sensor (IO setup, default 0); a lock and the C/N need a tuned frontend, e.g. a running stream of minisatip.

Some sensor categories are only available once the corresponding interface service has been activated and is running. You can implement this interface service yourself using C/C++, Python, a shell script, and many other languages. The interface between these services and homectld is MQTT, it's not yet fully documented here; for now, you can only refer to the examples and the code.

Services are already provided for some common components/devices:

  • Theford refrigerators
  • Mopeka gas level sensor
  • One-Wire sensors
  • Votronic MPP charge controller
  • Victron (at least the interface to the inverter is already working with it)
  • RTL433 wireless sensors
  • BMS (the widely used version, which is also used by Liontron)

Points to check

  • reboot the device to check if homectld is starting automatically during startup

LUA Interface

homectld embeds a Lua 5.3 scripting engine that lets you compute values and control digital outputs directly inside the daemon — no external process needed.

Where scripts are used

Sensor type Purpose
CV (Calculated Value) Compute a numeric, boolean, or text result from one or more other sensors
DO (Digital Output, Auto mode) Decide automatically whether an output should be on or off

Scripts are entered in the sensor's settings in the web interface (Setup → IO Setup → edit the sensor).

When a script is called

A script is executed when at least one sensor declared via watch() has changed its value since the last run of the script, or when the interval requested via every(seconds) has elapsed.

The very first run (at startup and after the script was saved) is always executed so that the watch() and every() calls can be collected. From then on the daemon only re-runs the script when one of them is due. A script declaring neither watch() nor every() is run every 60 seconds. A script with watch() only runs on changes of the watched sensors, so a script that also depends on the passing of time (hold times, timeouts, runtime counters) has to add an every().

Available globals

sensors — the sensor map

All known sensors are available as a nested table:

sensors["TYPE"][address]

TYPE is the sensor type string (e.g. "AI", "DI", "DO", "CV", "OW", "WEA", …). address is the numeric address of the sensor — you can write it as a decimal integer or as a hex literal:

sensors["AI"][1]       -- decimal address 1
sensors["DO"][0x02]    -- hex address 2  (both forms are equivalent)

Fields of a sensor entry

Field Type Description
value number Numeric measurement value
state boolean Boolean state (for status-kind sensors and digital outputs)
text string Text value
kind string "value", "status", "text", or "trigger"
last integer Unix timestamp of the last received update
changedAt integer Unix timestamp of the last actual value change
name string Internal name
title string Display title
unit string Unit string (e.g. "°C", "V", "%")
valid boolean true if the sensor has delivered at least one valid reading

All fields are read-only — the script cannot modify sensor data directly.

watch(type, address) — run on change

Declares a sensor the script depends on. The script is re-run as soon as that sensor has changed since the last run (within 0.5 s, GPIO inputs immediately when read).

watch("GPIO", 0x0c)

every(seconds) — run periodically

Requests a run in the given interval, in addition to the watch() dependencies and the forced run once per minute. Useful for scripts that accumulate time or poll something; the resolution is one second, the actual call happens within 0.5 s after the interval has elapsed.

every(10)         -- run at least every 10 seconds

tell(level, message) — logging

Writes a message to the homectld log. level is one of the predefined constants:

Constant When visible
eloAlways always
eloInfo info verbosity
eloLua Lua verbosity
eloDebug debug verbosity

Lua handles the formatting itself via string.format:

tell(eloLua, "temperature is " .. sensors["AI"][1].value)
tell(eloAlways, string.format("value out of range: %.2f", sensors["AI"][1].value))

signal — boolean

true when the script was triggered by an explicit signal/event, false during a regular cyclic evaluation.

Return value

The script must return a value. The type of the returned value determines what is written back to the CV sensor:

Lua return type Effect on the sensor
number (integer) value field is updated as integer
number (float) value field is updated as float
boolean state field is updated
string text field is updated
no return / nil nothing is written (warning logged)

Examples

Average of two analog inputs

watch("AI", 1)
watch("AI", 2)

local a = sensors["AI"][1]
local b = sensors["AI"][2]

if not a.valid or not b.valid then
    return 0.0
end

return (a.value + b.value) / 2.0

Digital output: turn on when temperature is above threshold

watch("AI", 0x01)

local temp = sensors["AI"][0x01]

if not temp.valid then
    return false
end

return temp.value > 25.0   -- true = ON, false = OFF

Combine state and value into a status text

watch("DO", 1)
watch("AI", 3)

local pump = sensors["DO"][1]
local flow = sensors["AI"][3]

if pump.state then
    return string.format("running, flow %.1f l/min", flow.value)
else
    return "stopped"
end

React to a signal event

watch("AI", 1)

if signal then
    return sensors["AI"][1].value * 2.0
end

return sensors["AI"][1].value

Tips

  • Always call watch() for every sensor the script reads — only watched sensors trigger re-evaluation.
  • Use sensor.valid before accessing sensor.value or sensor.state to avoid stale data on startup.
  • Both decimal (1) and hex (0x01) address literals work in watch() and in sensors[...] access.
  • The standard Lua libraries (math, string, table, os, …) are available.

Windy App

📊 Available Widgets / Sensor Modes

The application dynamically renders weather widgets based on the data received from the backend. Depending on your configuration and hardware setup, two distinct sensor outputs are generated:

  • Windy App by Spot ID This widget displays the wind and weather forecast for your fixed, pre-configured favorite windsurfing location. It uses the Windy Spot ID defined in your settings and will load this specific spot consistently, making it ideal for checking your home spot.

  • Windy App by Spot GPS This widget automatically adapts to your current physical position. If a GPS receiver is connected and active (e.g., while traveling in your camper), the system uses your live coordinates to automatically find and display the nearest windsurfing spot registered in the Windy.app database.

🛠️ How to Get Your Windy.app API Key

The weather widgets require a free Application ID from Windy.app. You do not need to register a developer account; the ID is automatically generated directly through their website.

Method 1: Using Any Windsurf/Weather Spot (Fastest)

  1. Go to any weather spot page on Windy.app (e.g., the Gialova Navarino Spot).
  2. Scroll down the page until you find the section titled "Live wind map. Install on your site." or "Weather widget".
  3. Look at the generated HTML code snippet. It will look like this:
    <div data-windywidget="forecast" data-thememode="white" data-spotid="444217" data-appid="e6865e0c4a1713e956bd11fc0b5fec63"></div>

Method 2: Via the Official Widget Generator

  1. Open the Windy.app Widget Generator.
  2. Choose your preferred widget layout.
  3. Click the button to generate the HTML code.
  4. Extract the hex string found inside the data-appid field from the generated code block.

Copy this number and paste it into your configuration (Setup -> WEB Interface -> Windy App Spot ID).

⚠️ Note on the widgets_ prefix: If this application automatically prepends widgets_ to the key in the frontend, you only need to provide the raw 32-character key in your configuration file. Do not include the widgets_ prefix yourself.

How to Find a Specific Windsurf Spot ID

If you want the widget to always load a specific favorite windsurfing location by default, you can provide its unique Spot ID:

  1. Open your browser and go to Windy.app.
  2. Use the search bar to find your favorite windsurfing spot (e.g., search for "Gialova Navarino").
  3. Look at the URL in your browser's address bar. It will look like this: https://windy.app
  4. The number right after /spot/ is your unique Spot ID. In this example, the ID is 444217.

Copy this number and paste it into your configuration (Setup -> WEB Interface -> Windy App Spot ID). Every spot ID stored there is remembered and offered as suggestion of the input field with the spot's name (the setup page asks Windy for it), so a previously used spot can be picked by name; the trash button forgets the entered value. Windy offers no lookup of a spot by name, only the ID is accepted.

Additional hints for a 'Stand Alone Server'

Some specials for a 'Stand Alone Server' like I use in my camper. Since the mini PC (in my case, an Odroid N2+) is being used as a server in this scenario, the network setup is different: eth0 has a static IP address and cannot access the internet. Internet access is provided by a Wi-Fi dongle configured as wlan0. A Wi-Fi access point for the RV’s “internal” network is connected to eth0.

The server also handles IP masquerading to provide all internal clients with Internet access. Additionally, dnsmasq runs as a DNS server/relay and DHCP service. Crony is used instead of systemd-timesyncd because the latter proved to be too “fussy” in this setup.

To enable the install some of the provided service configurations enable it by setting 'WOMO = 1' in Make.user in the projects (source) root directory of the homectld. make config edits Make.user interactively (switches and personalization, needs apt install dialog), the possible settings and their defaults are in Make.user.template.

if you use this 'server' for IP masquerading and/or as firewall or like to use openvpn

apt -y install iptables openvpn

Static/Fixed IP

to get a fixed IP for your eth0 device you find a teplate in contrib/womo/netplan/ just copy it to /etc/netplan:

cp contrib/womo/netplan/01-netcfg.yaml /etc/netplan/

and adjust the settings, such as the IP address, to your preferences. The route set there can be deleted, but it generally doesn't cause any problems. In my setup, it is used to route SatIp broadcat signals from the Minisatip service which I'm running on the Odroid.

DNS/DHCP

apt -y install dnsmasq dnsmasq-utils

if you call 'make install' of the homectld AFTER the install of dnsmasq the setup (/etc/dnsmasq*) will prepared automatically, just adjust your inrternal internal subnet by setting SUBNET = SUBNET = 192.168.220 in Make.user (to your subnet)

Time synchronization

Falls es Aufgrund der Art der Netzwerk/Internet Anbindung mit dem systemd-timesyncd zu Probmelmen kommt auf chroony umstellen

apt -y install chrony
systemctl disable --now systemd-timesyncd
systemctl enable --now chrony

Check

chronyc tracking
timedatectl

IP masquerading

to act as a router some settings IP table rules are necessary, this are prepared in the fwpn script. which is also set up as a systems service (fwpn.service). All you have to do is update your subnet in /etc/default/fwpn. The other options in fwpn can also be overridden via /etc/default/fwpn. Note that the MSQ_DEV in fwpn can be switched via the msqdevice.sh script service in the web frontend if the internet is accessed via usb0 rather than wlan0, for example

Alfa Tube-U4G LTE USB Modem

apt install modemmanager usb-modeswitch libqmi-utils network-manager

Für den Internet Zugangmittels einem Alfa Tube-U4G LTE USB Modem wird das Script lte-autoconnect.sh nebst zugehörigen Service lte-modem.service installiert. Damit kann die Verbindung mittels:

systemctl start lte-modem.service
systemctl stop lte-modem.service

gestartet bzw. wieder beendet werden. Das ist auch ins Dashboard der GUI integriert. Sofern die Verbindung nach jedem boot automatisch aktiviert werden soll kann man den Service stattdessen enabeln:

systemctl enable lte-modem.service

Check der Verbindung und Signal Qualität

mmcli -m $(mmcli -L | grep -oP 'Modem/\K[0-9]+') --signal-get

Zyklischen Check der LTE Verbindung mit Auto Reconnect bei Verbindungsverlust einrichten

Alle 5 Minuten mittels crontab Eintrag checken (crontab -l 2>/dev/null | grep -Fv "/usr/local/bin/lte-autoconnect.sh"; echo "*/5 * * * * /usr/local/bin/lte-autoconnect.sh > /dev/null 2>&1") | crontab - Wenn der Service 'lte-modem.service' absichtlich 'aus' ist führt das NICHT zu Neu-Verbindung!

Hinweis zum USB Port

Das Tube-U4G kann manchmal viel Strom ziehen (besonders bei schlechtem Empfang beim Senden). Da am Odroid bereits viele USB-Geräte hängen achte darauf, dass der Tube-U4G an einem der USB 3.0 Ports (blau) hängt. Falls es Verbindungsabbrüche gibt, könnte ein aktiver USB-Hub mit eigener Stromversorgung nötig sein, aber der N2+ liefert an den blauen Ports normalerweise genug Saft.

Backup

Backup the data of the homectl database including all recorded values:

homectld-backup.sh

This will create the at least this dumps:

config-dump.sql.gz
errors-dump.sql.gz
jobs-dump.sql.gz
menu-dump.sql.gz
samples-dump.sql.gz
schemaconf-dump.sql.gz
sensoralert-dump.sql.gz
smartconfig-dump.sql.gz
valuefacts-dump.sql.gz
timeranges-dump.sql.gz
hmsysvars-dump.sql.gz
scripts-dump.sql.gz
...
...

To import the backup:

gunzip NAME-dump.sql.gz
mysql -u homectl -phomectl -homectl <  NAME-dump.sql.gz

replace NAME with the name of the dump

Additional Hardware

BMS

If you like to connect a Battery-Management-Systems like them used commonly from LionTron a you can use the bmsmqtt service shipped with the homectld. Jus configure it in /etc/default/bmsmqtt, enable and start it:

systemctl enable bmsmqtt.service
systemctl start bmsmqtt.service

One Wire Sensors

The One Wire Sensors are managed by the w1mqtt service shipped with homectld, this service is enables by default and can be disabled if not needed. Disable w1mqtt

systemctl disable w1mqtt.service

The w1mqtt service is configured in /etc/default/w1mqtt

433 MHz Radio Sensors (RTL-433)

A RTL Radio/USB adapter like the https://amzn.eu/d/bKBwTOO is needed. As Sensor you van use any compatible, mostly all 433Mht radio sensors. For the interface to the homectld you can use rtl-433 which is includes in most linix distributions, whis can configured to write the data directly to MQTT where it's read and paresed by the homectld. To install the rtl-433 package:

apt -y install rtl-433

To start the rtl-433 service a Systemd unit file is shipped with the homectld, install, enable and start it by:

cd linux-homectld
cp contrib/rtl433.service /etc/systemd/system/
systemctl enable rtl433.service
systemctl start rtl433.service

To change the MQTT borker connection (host, port, ...) adjust it in /etc/systemd/system/rtl433.service and restart the service.

Caravan Industries (CI) Bus Interface

The CI bus adapter https://www.fischl.de/usblini/ is a lightweight solution. Details descibed here ./thetford/README.md

Information / HINTS

MySQL HINTS

If you cannot figure out why you get Access denied, remove all entries from the user table that have Host values with wildcards contained (entries that match % or _ characters). A very common error is to insert a new entry with Host='%' and User='some_user', thinking that this allows you to specify localhost to connect from the same machine. The reason that this does not work is that the default privileges include an entry with Host='localhost' and User=''. Because that entry has a Host value localhost that is more specific than %, it is used in preference to the new entry when connecting from localhost! The correct procedure is to insert a second entry with Host='localhost' and User='some_user', or to delete the entry with Host='localhost' and User=''. After deleting the entry, remember to issue a FLUSH PRIVILEGES statement to reload the grant tables.

To analyze this you can show all users:

use mysql
SELECT host, user FROM user;

Raspberry PI's GPIO information's

https://www.raspberrypi.org/documentation/usage/gpio/ https://projects.drogon.net/raspberry-pi/wiringpi/pins/ http://wiringpi.com/reference/setup/

Overview of IO pins

apt -y install python3-gpiozero
pinout

My notes to remember

Update Material Design Icons

wget https://cdn.jsdelivr.net/npm/@mdi/font@6.5.95/css/materialdesignicons.min.css copy to htdocs/css/

CCU

Rolladen an Hommatic CCU mit homectld ID 2 'HMB:2' komplette schließen mosquitto_pub --quiet -L mqtt://192.168.200.101:1883/homectld2mqtt/nodered -m '{ "id":"HMB:2","bri":0 }' auf 50% mosquitto_pub --quiet -L mqtt://192.168.200.101:1883/homectld2mqtt/nodered -m '{ "id":"HMB:2","bri":50 }'

Hardware which I use for my pool controller

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages