# Air conditioner

Process

Generic air conditioner control. Holds the logic common to every unit and delegates the communication to an attached implementation block, which is the only part that knows a given brand or model.

# Implementing blocks

NameIDDescription
Daikin BRP069daikin_brp069Drives a Daikin air conditioner over the local network, using the plain HTTP API of the BRP069 family of Wi-Fi adapters (BRP069A4x, BRP069B4x, BRP072A4x). The newer BRP072C4x and BRP084 adapters speak different protocols and need their own driver.
Air conditioner
P
M
TT
FS
SV
SH
CLN
P
M
TT
FS
SV
SH
CLN
RT
F

# Inputs

IDAbbrevNameTypeDefaultDescription
powerPPowerBOOLEANfalseSwitches the unit on when high and off when low
modeMModeNUMBER0Operating mode: 0 - auto, 1 - heating, 2 - cooling, 3 - drying, 4 - fan only. A unit without the requested mode uses its closest equivalent.
target_tempTTTarget temperatureNUMBER21.0Requested target temperature, limited to the configured range
fan_speedFSFan speedNUMBER0Fan speed: 0 - auto, 1 - quiet, 2 - low, 3 - medium, 4 - high, 5 - powerful (boost). A unit with fewer steps uses its closest equivalent.
swing_vSVVertical airflowNUMBER0Vertical vane: 0 - auto, 1 - swing, 2 to 6 - fixed position from the highest to the lowest
swing_hSHHorizontal airflowNUMBER0Horizontal vane: 0 - auto, 1 - swing, 2 to 6 - fixed position from the leftmost to the rightmost
air_cleanCLNAir cleaningBOOLEANfalseEnables the air cleaning feature of the unit (ionizer, streamer, plasma filter). Ignored by units without one.

# Outputs

IDAbbrevNameTypeDefaultDescription
powerPPowerBOOLEANfalseHigh when the unit is on. Reported by the unit when the implementation provides feedback, otherwise the commanded state.
modeMModeNUMBER0Effective operating mode, using the same values as the mode input. Units that report their status resolve the auto mode into heating or cooling here.
target_tempTTTarget temperatureNUMBER0.0Target temperature the unit works with, after the configured range has been applied
fan_speedFSFan speedNUMBER0Fan speed the unit was asked for, using the same values as the fan speed input. No unit reports this back, so it is always the commanded value.
swing_vSVVertical airflowNUMBER0Vertical vane setting the unit was asked for, using the same values as the vertical airflow input. No unit reports this back, so it is always the commanded value.
swing_hSHHorizontal airflowNUMBER0Horizontal vane setting the unit was asked for, using the same values as the horizontal airflow input. No unit reports this back, so it is always the commanded value.
air_cleanCLNAir cleaningBOOLEANfalseAir cleaning setting the unit was asked for. No unit reports this back, so it is always the commanded value.
room_tempRTRoom temperatureNUMBER-1000.0Indoor temperature measured by the unit. -1000 when the unit does not report it.
faultFFaultNUMBER00 - no fault, -1 - unit unreachable, above 0 - unit specific error code

# Configuration

IDNameTypeDefaultUnitDescription
min_target_tempMinimum target temperatureNUMBER10.0°CLower limit for the target temperature input. The unit may narrow it down further.

Details:

≤ max_target_temp
max_target_tempMaximum target temperatureNUMBER30.0°CUpper limit for the target temperature input. The unit may narrow it down further.

Details:

≥ min_target_temp

# State

IDNameTypeDefaultUnitDescription
unit_powerReported powerNUMBER-1Power state reported by the implementation block: -1 unknown, 0 off, 1 on
unit_modeReported modeNUMBER-1Mode reported by the implementation block, -1 when unknown
unit_target_tempReported target temperatureNUMBER-1000.0°CTarget temperature reported by the implementation block, -1000 when unknown
unit_room_tempReported room temperatureNUMBER-1000.0°CIndoor temperature reported by the implementation block, -1000 when unknown
unit_faultReported faultNUMBER0Fault reported by the implementation block
invoke_rcLast implementation resultNUMBER0Value returned by the last implementation call, published as a fault when the unit reports none
sentState sentBOOLEANfalseSet once the state has been accepted by the unit at least once
sent_powerSent powerBOOLEANfalseLast power state accepted by the unit
sent_modeSent modeNUMBER0Last mode accepted by the unit
sent_target_tempSent target temperatureNUMBER0.0°CLast target temperature accepted by the unit
sent_fan_speedSent fan speedNUMBER0Last fan speed accepted by the unit
sent_swing_vSent vertical airflowNUMBER0Last vertical vane setting accepted by the unit
sent_swing_hSent horizontal airflowNUMBER0Last horizontal vane setting accepted by the unit
sent_air_cleanSent air cleaningBOOLEANfalseLast air cleaning setting accepted by the unit

# Source Code

View Volang source
// Air conditioner - abstract block.
//
// Turns the input channels into one desired unit state and hands it to the
// attached implementation (driver), the only part that knows how to reach a
// particular unit (IR, LAN, Modbus, ...).
//
// Value encodings, shared by the inputs, the outputs and the driver call:
//   mode       0 auto, 1 heating, 2 cooling, 3 drying, 4 fan only
//   fan_speed  0 auto, 1 quiet, 2 low, 3 medium, 4 high, 5 powerful
//   swing_v    0 auto, 1 swing, 2..6 fixed vane (2 highest .. 6 lowest)
//   swing_h    0 auto, 1 swing, 2..6 fixed vane (2 leftmost .. 6 rightmost)
//   fault      0 none, -1 unit unreachable, > 0 unit specific error code
// A unit that cannot do what was asked for falls back to its closest
// equivalent - that mapping belongs to the driver.
//
// The driver must declare one function:
//
//   ac_apply(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
//       Pushes the complete desired state, called whenever a value changes.
//       It only has to accept the work, not finish it: a driver on the network
//       returns as soon as the request is out, and one still busy with an
//       earlier exchange keeps the state and sends it once that is over.
//       Returns 0 when accepted, otherwise an error code that is published on
//       the fault output.
//
// Whether and how often the unit is polled for its status is up to the driver,
// which can be scheduled on its own. Either way it reports back through this
// block's state, then returns non-zero from the run that stored the readings,
// so the runtime calls std::on_driver_update here:
//   unit_power        -1 unknown, 0 off, 1 on
//   unit_mode         -1 unknown, otherwise a mode value
//   unit_target_temp  -1000 unknown, otherwise °C
//   unit_room_temp    -1000 unknown, otherwise °C
//   unit_fault        the fault value to publish
// Whatever is left unknown is published as the value this block commanded.

fn choice(value, hi) {
    v = math::int(math::round(value))
    if (v < 0) {
        return 0
    }
    if (v > hi) {
        return hi
    }
    return v
}

fn clamp(value, lo, hi) {
    if (value < lo) {
        return lo
    }
    if (value > hi) {
        return hi
    }
    return value
}

// Airflow and air cleaning have no counterpart in the readings - no unit
// reports them back - so they are published exactly as commanded.
fn publish(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean) {
    unit_power = state::get("unit_power")
    if (unit_power >= 0) {
        power = unit_power != 0
    }
    unit_mode = state::get("unit_mode")
    if (unit_mode >= 0) {
        mode = unit_mode
    }
    unit_target_temp = state::get("unit_target_temp")
    if (unit_target_temp > -1000) {
        target_temp = unit_target_temp
    }
    fault = state::get("unit_fault")
    if (fault == 0) {
        fault = state::get("invoke_rc")
    }
    output::set("power", power)
    output::set("mode", mode)
    output::set("target_temp", target_temp)
    output::set("fan_speed", fan_speed)
    output::set("swing_v", swing_v)
    output::set("swing_h", swing_h)
    output::set("air_clean", air_clean)
    output::set("room_temp", state::get("unit_room_temp"))
    output::set("fault", fault)
}

fn changed(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean) {
    if (!state::get("sent")) {
        return true
    }
    if (state::get("sent_power") != power) {
        return true
    }
    if (state::get("sent_air_clean") != air_clean) {
        return true
    }
    if (state::get("sent_mode") != mode) {
        return true
    }
    if (state::get("sent_target_temp") != target_temp) {
        return true
    }
    if (state::get("sent_fan_speed") != fan_speed) {
        return true
    }
    if (state::get("sent_swing_v") != swing_v) {
        return true
    }
    if (state::get("sent_swing_h") != swing_h) {
        return true
    }
    return false
}

// The last readings describe the unit before a command, so they must not mask
// what was just asked for; the read that follows fills them in again. The room
// temperature is a measurement, not a setting, and stays.
fn forget_readings() {
    state::set("unit_power", -1)
    state::set("unit_mode", -1)
    state::set("unit_target_temp", -1000.0)
}

fn remember(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean) {
    state::set("sent_power", power)
    state::set("sent_mode", mode)
    state::set("sent_target_temp", target_temp)
    state::set("sent_fan_speed", fan_speed)
    state::set("sent_swing_v", swing_v)
    state::set("sent_swing_h", swing_h)
    state::set("sent_air_clean", air_clean)
    state::set("sent", true)
}

// Called by the runtime after the driver stored fresh readings. The commanded
// values are only a fallback for whatever the unit did not report.
extern fn std::on_driver_update() {
    power = state::get("sent_power")
    mode = state::get("sent_mode")
    target_temp = state::get("sent_target_temp")
    fan_speed = state::get("sent_fan_speed")
    swing_v = state::get("sent_swing_v")
    swing_h = state::get("sent_swing_h")
    air_clean = state::get("sent_air_clean")
    publish(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
}

power = input::get("power")
mode = choice(input::get("mode"), 4)
fan_speed = choice(input::get("fan_speed"), 5)
swing_v = choice(input::get("swing_v"), 6)
swing_h = choice(input::get("swing_h"), 6)
air_clean = input::get("air_clean")
min_temp = config::get("min_target_temp")
max_temp = config::get("max_target_temp")
target_temp = clamp(input::get("target_temp"), min_temp, max_temp)

if (changed(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)) {
    // Report the commanded state right away, so the outputs stay meaningful
    // even when the driver call below fails or no driver is attached at all.
    publish(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
    rc = std::invoke("ac_apply", power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
    state::set("invoke_rc", rc)
    if (rc == 0) {
        remember(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
        forget_readings()
    }
}
publish(power, mode, target_temp, fan_speed, swing_v, swing_h, air_clean)
Learn how the Air conditioner logic block works, when to use it, and how to configure it in your Voldeno smart home automation.