<!--
.. title: Integrator Guide
.. slug: 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:

```qml
// 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 }
}
```

```qml
// 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.

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

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

Writing a value from the server:

```qml
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:

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

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

```qml
tempoTomorrow.value = jsonResponse.codeJour
```

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

```qml
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.

```qml
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:

```qml
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:

```qml
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`:

```qml
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 |

```qml
// 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:

```sql
-- 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);
```

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

---

### Notifications

KaZa provides two complementary notification mechanisms: **immediate notifications** sent to currently connected clients, and **persistent alarms** that the Android client can retrieve even when the app is in the background.

#### Immediate notifications: `kazamanager.notify()`

`kazamanager.notify(message)` is a QML slot that pushes a text message to all connected clients over the SSL channel as a `FRAME_SYSTEM` frame. On Android the client converts it into a native system notification. On Linux and Windows it is currently delivered as a text command that the client QML can display however it chooses.

```qml
// Send to every connected client
kazamanager.notify("Front door opened")

// Triggered by a sensor
KzObject {
    object: "knx.sensor.entrance.door"
    onValueChanged: {
        if (value === true) {
            kazamanager.notify("Front door opened")
        }
    }
}

// Triggered by a scheduler — daily reminder
Scheduler {
    hour: "8"
    minute: "0"
    onTimeout: {
        var msg = "Good morning! Outdoor: "
                  + kMeasTempOutside.value.toFixed(1) + " °C"
        kazamanager.notify(msg)
    }
}
```

#### Targeting specific users

Prefix the message with one or more `/username` tokens to deliver the notification only to clients authenticated as those users. The username is the one used during certificate provisioning.

```qml
// Send only to the user "fabien"
kazamanager.notify("/fabien Washing machine finished")

// Send to two users, leave others out
kazamanager.notify("/fabien /aurelie Pool pump alarm!")

// No prefix → broadcast to everyone connected
kazamanager.notify("Power outage detected")
```

The server extracts every token that starts with `/` from the beginning of the string, strips them, and delivers the remaining text only to connections whose authenticated username matches.

#### `KzAlarm` — Persistent background alarms

`kazamanager.notify()` only reaches clients that are currently connected. For notifications that must be delivered even when the app is in the background or the phone screen is off, use `KzAlarm`.

`KzAlarm` is a QML type that registers a named alarm condition with the server. The Android client runs a background service (`NotificationService`) that periodically opens a short SSL connection and requests the current alarm list via the `ALARMS:` command. When an alarm is active (`enable: true`) the service triggers a native Android notification, regardless of whether the main app is running.

```qml
import org.kazoe.kaza 1.0

KaZaElement {

    // Declare a persistent alarm
    KzAlarm {
        id: poolAlarm
        title: "Pool alert"
        message: "Pool pump has not run for 24 hours"
        enable: false   // set to true to activate
    }

    // Activate/deactivate based on a sensor
    KzObject {
        id: pumpStatus
        object: "device.pool.pump_running"
        onValueChanged: {
            // If pump hasn't run — flag the alarm
            poolAlarm.enable = !value
        }
    }
}
```

`KzAlarm` properties:

| Property | Type | Description |
|----------|------|-------------|
| `enable` | bool | `true` = alarm is active and will be delivered to polling clients |
| `title` | string | Notification title shown on the device |
| `message` | string | Notification body text |
| `admin` | bool | If `true`, alarm is only visible to admin users |
| `debug` | bool | If `true`, alarm is suppressed in production builds |

Multiple `KzAlarm` instances can coexist. Each one is independent. The client background service receives all active alarms for the authenticated user in a single compressed response.

#### Combining both mechanisms

For time-critical events (gas leak, alarm triggered, water sensor) you typically want both: an immediate notification to clients that are open, and a persistent alarm that will wake up the phone if the app is not running.

```qml
KzAlarm {
    id: waterAlarm
    title: "Water leak!"
    message: "Water detected in the basement"
    enable: false
}

KzObject {
    object: "knx.sensor.basement.water"
    onValueChanged: {
        if (value === true) {
            // Immediate push to anyone connected right now
            kazamanager.notify("URGENT: Water leak in basement!")

            // Persistent alarm — wakes Android clients in background
            waterAlarm.enable = true
        } else {
            waterAlarm.enable = false
            kazamanager.notify("Water leak resolved")
        }
    }
}
```

#### Control port

Notifications can also be sent from the control port (43500) for testing or external scripting:

```bash
nc localhost 43500
notify Front door opened
notify /fabien Your washing machine finished
```

---

## 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

```qml
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

```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:

```qml
// 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:

```qml
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:

```qml
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`:

```qml
// 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)
    }
}
```

```qml
// 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:

```qml
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:

```qml
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

```bash
# 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

```bash
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`:

```ini
[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:

```bash
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

```bash
sudo journalctl -u kazad -f
```

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

### Object Inspection with `kls`

```bash
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

```bash
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:

```qml
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.
