// A building on KNX through an IP interface: lights switched and dimmed, a setpoint written, a
// temperature asked for, what the wall switches do heard on the bus, addresses in both notations,
// and datapoint values encoded and decoded without any bus at all.
//
//   KNX_GATEWAY=192.168.1.30 quillo run knx.qwe      then open http://localhost:8080/lights
//
// One tunnel per program is plenty: most interfaces offer only a few. Groups here are 1/2/3 for a
// light, 1/2/4 for its dimmer, 3/1/7 for a thermostat.

use "knx.qwer"

let GATEWAY = env("KNX_GATEWAY", "127.0.0.1")
let tunnel = null

fn open() {
    if !tunnel { tunnel = knx_connect("house", GATEWAY, { timeout_ms: 1500 })? }
    tunnel
}

route GET /connect = open()

route GET /lights {
    open()
    {
        on: knx_write("house", "1/2/3", "1.001", true)?,
        dim: knx_write("house", "1/2/4", "5.001", 75)?,
        setpoint: knx_write("house", "3/1/8", "9.001", 21.5)?,
        name: knx_write("house", "3/1/9", "16.000", "Living room")?,
    }
}

route GET /state {
    open()
    knx_write("house", "1/2/3", "1.001", false)?
    {
        light: knx_read("house", "1/2/3", "1.001", 1000)?,
        temperature: knx_read("house", "3/1/7", "9.001", 1000)?,
        nobody: knx_read("house", "7/7/7", "9.001", 200)?,
    }
}

route GET /heard {
    open()
    let t = knx_receive("house", 10, 1500)?
    t | map((m) => [m.source, m.destination, m.service, m.destination == "3/1/7" ? knx_decode("9.001", m.data)? : m.data])
}

// A group no device listens on: the bus says so, and the program hears it.
route GET /failed {
    open()
    { no_device: knx_write("house", "9/1/1", "1.001", true).error, repeat_kept: knx_write("house", "1/2/3", "1.001", true)? }
}

route GET /busy = { refused: knx_connect("second", env("KNX_BUSY_GATEWAY", "127.0.0.1:3672"), { timeout_ms: 500 }).error, nothing: knx_connect("third", env("KNX_DEAD_GATEWAY", "127.0.0.1:9"), { timeout_ms: 200 }).error }

route GET /search = knx_search(GATEWAY, 1000)?

route GET /addresses = {
    three: knx_group_address("1/2/3")?,
    two: knx_group_address("1/515")?,
    number: knx_group_address(2563)?.three_level,
    device: knx_individual_address("1.1.10")?,
    back: knx_individual_address(4362)?.text,
    bad_group: knx_group_address("32/0/0").error,
    bad_device: knx_individual_address("1.16.1").error,
}

route GET /values = {
    temperature: knx_encode("9.001", 21.5)?,
    cold: knx_encode("9.001", -30)?,
    read_back: knx_decode("9.001", "0c33")?,
    negative_back: knx_decode("9.001", "8a24")?,
    invalid: knx_decode("9.001", "7fff")?,
    percent: knx_encode("5.001", 100)?,
    half: knx_decode("5.001", "80")?,
    energy: knx_encode("13.010", -1)?,
    power: knx_decode("14.056", "447a0000")?,
    text: knx_encode("16.000", "KNX is OK")?,
    time: knx_decode("10.001", knx_encode("10.001", "3 10:30:05")?)?,
    date: knx_decode("11.001", knx_encode("11.001", "2026-11-01")?)?,
    when: knx_encode("19.001", "2026-11-01T12:30:00")?,
    colour: knx_decode("232.600", knx_encode("232.600", "#ff8000")?)?,
    too_hot: knx_encode("5.001", 101).error,
    too_long: knx_encode("16.000", "fifteen letters").error,
    unknown: knx_encode("99.001", 1).error,
    cemi: knx_cemi_write("1/2/3", "9.001", 21.5)?,
    small: knx_cemi_write("1/2/3", "1.001", true)?,
    read_cemi: knx_parse_cemi("2900bce0120a0a03010081")?,
}
