KaZa Home automation

Home automation

Integrator Guide

Integrator Guide

An integrator is someone who writes the home-specific application that sits on top of the KaZa platform. This is not the same as developing a plugin (which requires C++ and deals with a specific protocol). An integrator writes QML code — one part running on the server, one part compiled into the client — to describe their specific home: which devices exist, how they should respond to events, and how they should be presented to the user.

The reference example is KaZaExample: a production installation covering KNX lights and shutters, a ventilation system, a solar/battery installation, an EV charger, a pool controller, and power monitoring for over 20 individual circuits. Everything in this guide is illustrated with patterns drawn from that example.


Overview: What You Will Create

An integrator produces two artifacts:

  1. Server-side QML — a main.qml (and optional companion files) loaded by kazad at startup. This code runs continuously on the server. It declares plugins, wires up automation rules, schedules actions, and logs data.

  2. Client-side QML — a set of QML files compiled into a .rcc resource bundle. This bundle is served to connecting clients over the SSL port and provides the complete user interface.

The server and client share a common namespace: the object names you define on the server are the same strings the client uses in KzObject bindings.

Server machine                          Client devices
─────────────────────────────────       ──────────────────────────
/etc/kazad.conf
  qml.server = /opt/kaza/main.qml
  qml.client = /opt/kaza/app.rcc

main.qml ──► kazad (QML engine)
                
                
         KaZaObject registry
         (shared object names)
                
         SSL port 1756 ─────────────►  KaZa Client (native app)
         sends app.rcc on connect       Loads UI from app.rcc
                                        KzObject binds by name

Server-Side QML

Project Structure

There is no prescribed structure. A small installation fits in a single main.qml. A complex one should be split into thematic files:

my-home/
├── main.qml          # Root — imports all sub-components
├── Knx.qml           # KNX bus and power logging
├── Heating.qml       # Heating system logic
├── Energy.qml        # Solar, battery, grid
├── DataLogger.qml    # Periodic database inserts
└── Program.qml       # Schedulers and automation rules

Root Element: KaZaElement

Every server-side QML file uses KaZaElement as its root type, provided by org.kazoe.kaza 1.0. Companion files are instantiated as children of the root:

// main.qml
import QtQml 2.0
import org.kazoe.kaza 1.0

KaZaElement {
    id: root

    Component.onCompleted: {
        console.debug("Server started")
    }

    Knx      { id: knx }
    Heating  { id: heating }
    Energy   { id: energy }
    DataLogger { id: logger }
}
// Knx.qml
import QtQml 2.0
import org.kazoe.kaza 1.0
import org.kazoe.knx 1.0

KaZaElement {
    KnxBus {
        knxd: "ip:192.168.1.250"
        knxProj: "/etc/kaza/home.knxproj"
    }
    // KNX-specific objects and rules follow
}

Referencing Objects: KzObject

KzObject binds to a named object by string and mirrors its current value. It fires onValueChanged whenever the value changes on the server.

KzObject {
    id: loungeLightSwitch
    object: "light.switch.lounge"

    onValueChanged: {
        console.debug("Lounge light is now:", value)
    }
}

Writing a value from the server:

loungeLightSwitch.set(true)           // write without confirmation
loungeLightSwitch.set(true, true)     // write and request confirmation from hardware
loungeLightSwitch.refresh()           // ask the plugin to re-read hardware value

KzObject key properties:

Property/Method Type Description
object string Object name in the registry
value QVariant Current value (read-only)
unit string Unit string (read-only)
set(v, confirm) method Write a value
refresh() method Force hardware re-read
rawid() method Returns numeric object ID (useful as DB foreign key)

Creating Custom Objects: KaZaObject

For values that are not produced by a plugin — computed values, state flags, external API data — declare a KaZaObject directly in QML:

KaZaObject {
    id: tempoTomorrow
    name: "tempo.tomorrow"
}

Once declared, the object is in the registry and is synchronized to all clients. Assign values directly:

tempoTomorrow.value = jsonResponse.codeJour

From KaZaExample main.qml, this pattern exposes the next-day French electricity tariff color fetched from an HTTP API:

import QtQml 2.0
import org.kazoe.kaza 1.0

KaZaElement {
    id: root

    KaZaObject {
        id: tempoTomorrow
        name: "tempo.tomorrow"
    }

    function getTomorrowTempoData() {
        var xhr = new XMLHttpRequest()
        xhr.open("GET", "https://www.api-couleur-tempo.fr/api/jourTempo/tomorrow")
        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                var json = JSON.parse(xhr.responseText)
                tempoTomorrow.value = json.codeJour
            }
        }
        xhr.send()
    }

    Timer {
        interval: 15 * 60 * 1000
        running: true
        repeat: true
        onTriggered: root.getTomorrowTempoData()
    }

    Component.onCompleted: root.getTomorrowTempoData()

    // sub-components
    Knx      { id: knx }
    Tesla    { id: tesla }
    Helios   { id: helios }
    Program  { id: prog }
    DataLogger { id: datalogger }
}

Object Naming Convention

Use a hierarchical dot-separated scheme. Consistent naming makes the namespace readable and supports fuzzy filtering with the kls tool.

<domain>.<category>.<location_or_id>

Examples from KaZaExample:
  light.switch.lounge
  light.switch.kitchen
  blind.goto.lounge
  blind.goto.bedroom2
  meas.power.plugs_floor
  meas.power.dishwasher
  meas.power.pac
  meas.temperature.porch
  device.boiler.tank_top
  device.boiler.tank_bottom
  configuration.mode_manuel.lounge
  helios.inInsideTemperature
  victron.battery.soc
  internal.presence.fabien
  tempo.tomorrow
  tank.rainwater
  system.home.edf_tarif_current

KNX Integration

Import org.kazoe.knx 1.0 and declare a KnxBus. The plugin reads the .knxproj file (ETS export) and creates one KaZaObject per group address.

import org.kazoe.knx 1.0

KaZaElement {
    KnxBus {
        knxd: "ip:192.168.1.250"
        knxProj: "/etc/kaza/home.knxproj"
    }
}

All group addresses from the ETS project are then available by name. From KaZaExample Knx.qml, power meters are logged to the database on every value change:

KzObject {
    object: "meas.power.plugs_floor"
    onValueChanged: {
        kazamanager.runDbQuery(
            "INSERT INTO power (gad, power) VALUES ("
            + rawid() + "," + (value / 1000) + ")"
        )
    }
}

KzObject {
    object: "meas.power.dishwasher"
    onValueChanged: {
        kazamanager.runDbQuery(
            "INSERT INTO power (gad, power) VALUES ("
            + rawid() + "," + value + ")"
        )
    }
}

A scheduler reactivates detectors and purges old data every morning:

Scheduler {
    hour: "9"
    minute: "0"
    onTimeout: {
        detecteurLounge.set(1)
        detecteurKitchen.set(1)
        kazamanager.runDbQuery(
            "DELETE FROM power WHERE time < NOW() - INTERVAL '10 days'"
        )
    }
}

MQTT Integration

Import org.kazoe.mqtt 1.0:

import org.kazoe.mqtt 1.0

MqttClient {
    hostname: "localhost"
    port: 1883
    clientId: "kaza-server"

    MqttTopic {
        topic: "home/sensors/outdoor/temperature"
        onMessageReceived: function(message) {
            outdoorTemp.value = parseFloat(message)
        }
    }
}

KaZaObject {
    id: outdoorTemp
    name: "sensor.outdoor.temperature"
}

Time-Based Automation: Scheduler

Scheduler fires onTimeout when the current time matches all configured fields. Unset fields act as wildcards.

Property Example Meaning
year "2025" Match specific year
month "6,7,8" Summer months
day "1" First of month
wday "1,2,3,4,5" Weekdays (1=Monday)
hour "7" 7 AM
minute "*/15" Every 15 minutes
// Weekday morning routine
Scheduler {
    wday: "1,2,3,4,5"
    hour: "7"
    minute: "0"
    onTimeout: {
        blindBedroom.set(100)
        lightKitchen.set(true)
    }
}

// Night mode
Scheduler {
    hour: "23"
    minute: "0"
    onTimeout: {
        lightLounge.set(false)
        lightKitchen.set(false)
    }
}

// Every 5 minutes, log temperature
Scheduler {
    minute: "*/5"
    onTimeout: {
        kazamanager.runDbQuery(
            "INSERT INTO temperature (sensor, celsius) VALUES ('porch',"
            + kMeasTempOutside.value + ")"
        )
    }
}

// Monthly cleanup
Scheduler {
    day: "1"
    hour: "3"
    minute: "0"
    onTimeout: {
        kazamanager.runDbQuery(
            "DELETE FROM power WHERE time < NOW() - INTERVAL '1 year'"
        )
    }
}

Database Logging

If /etc/kazad.conf configures a PostgreSQL connection, kazamanager.runDbQuery(sql) runs SQL asynchronously:

-- Create once in psql
CREATE TABLE power (
    time   TIMESTAMP DEFAULT NOW(),
    gad    INTEGER,
    power  FLOAT
);
CREATE INDEX ON power(time);
CREATE INDEX ON power(gad);
KzObject {
    object: "meas.power.boiler_electric"
    onValueChanged: {
        kazamanager.runDbQuery(
            "INSERT INTO power (gad, power) VALUES ("
            + rawid() + "," + value + ")"
        )
    }
}

Client-Side QML

How the Client Shell Works

The KaZa Client is a shell that provides: - The SSL connection to the server - A manager singleton with a ready property - Automatic download of the .rcc bundle from the server - The KzObject type for binding to server objects

Your UI code lives entirely in the .rcc bundle loaded from qrc:/main.qml.

manager member Description
manager.ready true once connected and all objects received
manager.readyChanged Signal fired on connection state change

Typical main.qml Structure

import QtQuick 2.9
import QtQuick.Controls 2.15

Page {
    id: window
    width: 360
    height: 668
    title: "KaZa"

    function askClose(close) {
        if (stackView.depth > 1) {
            close.accepted = false
            stackView.pop()
        } else {
            close.accepted = true
        }
    }

    Component.onCompleted: {
        mainwindow.callBackClose = askClose
    }

    // Toolbar visible when connected
    header: Loader {
        active: manager.ready
        source: "KazaToolbar.qml"
    }

    // Sidebar drawer when connected
    Loader {
        active: manager.ready
        source: "KazaSidebar.qml"
    }

    // Waiting screen while connecting
    Loader {
        active: !manager.ready
        source: "WaitConnection.qml"
    }

    // Page stack — home page when connected
    StackView {
        id: stackView
        anchors.fill: parent
        initialItem: Loader {
            active: manager.ready
            source: "HomePage.qml"
        }
    }
}

Using KzObject in Client QML

import org.kazoe.kaza 1.0

KzObject {
    id: insideTemp
    object: "helios.inInsideTemperature"
}

KzObject {
    id: outsideTemp
    object: "meas.temperature.porch"
}

Button {
    text: outsideTemp.value !== undefined
          ? "Outside: " + outsideTemp.value.toFixed(2) + " °C"
          : "Outside: —"
    onClicked: stackView.push("HistoryMinAvgMax.qml", {
        mainTitle: "Outside temperature",
        object: "meas.temperature.porch",
        min: 0,
        max: 35
    })
}

Writing from the client:

// Simple write
kLightSwitch.set(true)

// Write with confirmation (server echoes back after actuating)
kModeLounge.set(0, true)

// Example: TV mode button from KaZaExample HomePage.qml
Button {
    property bool modeTV: (!kModeLounge.value && !kModeKitchen.value)
    text: "Séance TV"
    highlighted: modeTV

    onClicked: {
        if (modeTV) {
            kModeLounge.set(1, true)
            kModeKitchen.set(1, true)
        } else {
            kBlindLounge.set(100)
            kModeLounge.set(0, true)
            kModeKitchen.set(0, true)
            kLightSwitchLounge.set(0)
            kLightSwitchDiner.set(0)
            kLightSwitchKitchen.set(0)
        }
    }
}

Navigation

Push and pop pages on the stack:

Button { text: "Ground floor"; onClicked: stackView.push("PlanRDC.qml") }
Button { text: "Upper floor";  onClicked: stackView.push("PlanEtage.qml") }
Button { text: "Shutters";     onClicked: stackView.push("Volets.qml") }
Button { text: "Energy";       onClicked: stackView.push("Consommation.qml") }

Pass properties to the pushed page:

stackView.push("HistoryMinAvgMax.qml", {
    mainTitle: "Indoor temperature",
    object:    "helios.inInsideTemperature",
    min:       18,
    max:       28
})

Reusable Widget Components

KaZaExample organizes reusable UI elements in a Widget/ directory. Each widget takes an object string property and creates its own KzObject:

// Widget/LightSwitch.qml
import QtQuick.Controls 2.3
import org.kazoe.kaza 1.0

Item {
    property string object: ""
    property string label: ""

    KzObject { id: kObj; object: parent.object }

    Switch {
        text: label
        checked: kObj.value === true
        onToggled: kObj.set(checked)
    }
}
// Widget/Sensor.qml
import QtQuick 2.9
import org.kazoe.kaza 1.0

Item {
    property string object: ""
    property string label: ""

    KzObject { id: kObj; object: parent.object }

    Text {
        text: label + ": " + (kObj.value !== undefined
              ? kObj.value.toFixed(1) + " " + kObj.unit
              : "—")
    }
}

Widgets used in a room plan:

import "Widget"

LightSwitch { object: "light.switch.lounge";  label: "Lounge" }
LightSwitch { object: "light.switch.kitchen"; label: "Kitchen" }
Sensor      { object: "meas.temperature.porch"; label: "Outside" }
Shutter     { object: "blind.goto.lounge";    label: "Lounge blind" }

The KaZaExample Widget/ directory contains:

Widget Purpose
LightSwitch.qml Toggle switch for a boolean object
LightControler.qml Switch + dimmer slider
LightControlerSimple.qml Compact switch
Dimmer.qml Standalone dimmer slider
Shutter.qml Blind with position indicator and goto control
ShutterLine.qml Compact single-line shutter control
ShutterPopup.qml Full-screen shutter control popup
Sensor.qml Read-only value + unit display
SensorWater.qml Water flow sensor display
SensorPower.qml Power meter with trend
Battery.qml Battery level bar with icon
Cuve.qml Tank fill level indicator
Presence.qml Presence dot (colored circle)

Connecting to Historical Data

Clients can run SQL queries via FRAME_DBQUERY. The kaza-client exposes a DbQuery type:

import org.kazoe.kaza 1.0

property string object: ""

DbQuery {
    id: historyQuery
    sql: "SELECT date_trunc('hour', time) AS t,
                 MIN(power), AVG(power), MAX(power)
          FROM power
          WHERE gad = (SELECT rawid FROM objects WHERE name = '" + object + "')
            AND time > NOW() - INTERVAL '48 hours'
          GROUP BY t ORDER BY t"
    onResultReady: {
        chartModel.clear()
        for (var i = 0; i < rows.length; i++) {
            chartModel.append({ t: rows[i][0], min: rows[i][1],
                                avg: rows[i][2], max: rows[i][3] })
        }
    }
}

Building and Deploying

Compile the Client Bundle

# client.qrc — list all QML and asset files
cat > client.qrc << 'EOF'
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
    <file>main.qml</file>
    <file>HomePage.qml</file>
    <file>Volets.qml</file>
    <file>Settings.qml</file>
    <file>WaitConnection.qml</file>
    <file>KazaToolbar.qml</file>
    <file>KazaSidebar.qml</file>
    <file>Widget/LightSwitch.qml</file>
    <file>Widget/Shutter.qml</file>
    <file>Widget/Sensor.qml</file>
    <file>Images/home.png</file>
</qresource>
</RCC>
EOF

rcc --binary -o app.rcc client.qrc

Deploy to Server

scp -r Server/   pi@home.local:/opt/kaza/
scp app.rcc      pi@home.local:/opt/kaza/app.rcc
ssh pi@home.local "sudo systemctl restart kazad"

Server Configuration

/etc/kazad.conf:

[ssl]
port=1756
keypassword="your_server_key_password"
hostname="home.example.com"

[control]
port=43500
password="your_admin_password"
enable=true

[database]
driver="QPSQL"
hostname="127.0.0.1"
port=5432
dbName="kaza"
username="kaza"
password="your_db_password"

[qml]
server=/opt/kaza/main.qml
client=/opt/kaza/app.rcc

Provisioning Client Certificates

Each new device needs its own SSL certificate. Use the control port from the server:

nc localhost 43500
clientconf? <admin_password> <username> <user_password>

The server returns XML with the hostname, port, CA certificate, signed client certificate, and encrypted private key. The KaZa Client app parses this XML when setting up a new device: enter the server address, username, and password — the app retrieves everything else automatically.


Debugging

Server Logs

sudo journalctl -u kazad -f

QML console.log(), console.debug(), console.warn() output appears here.

Object Inspection with kls

kls                          # All objects and current values
kls /temperature             # Fuzzy: objects matching *t*e*m*p*e*r*a*t*u*r*e*
kls /"meas.power"            # Substring: all power objects

Control Port

nc localhost 43500
obj?                              # List all objects
obj? meas.temperature.porch       # Query one object
refresh meas.temperature.porch    # Force hardware re-read

Common Pitfalls

value is undefined on first render — Objects registered by plugins may not exist yet when the first frame renders. Always guard display code:

text: kObj.value !== undefined ? kObj.value.toFixed(1) + " °C" : "—"

Writing value directly on KzObject — On the server you can set value on a KaZaObject. On a KzObject (both server and client), always use set().

Blocking the main thread — Use XMLHttpRequest for HTTP calls (async). Synchronous I/O in a QML handler will freeze the server for all clients.