diff --git a/src/content/docs/reference/program-format.mdx b/src/content/docs/reference/program-format.mdx index d5c13bd..3ebe7a5 100644 --- a/src/content/docs/reference/program-format.mdx +++ b/src/content/docs/reference/program-format.mdx @@ -95,19 +95,19 @@ for p in iter_defined(acct.programs): Source: `enuProgramType.cs`. -### `ProgramCond` (high bits of `cond` / `cond2`) +### `ConditionFamily` (high bits of `cond` / `cond2`) | Value | Name | Family | |------:|---|---| -| 0 | `OTHER` | catch-all / miscellaneous | +| 0 | `OTHER` | misc-conditional (`DARK`, `AC_POWER_OFF`, …) | | 4 | `ZONE` | zone-state condition | -| 8 | `CTRL` | control-unit condition | +| 8 | `CTRL` | control-unit (light/output) condition | | 12 | `TIME` | time-clock condition | -| 16 | `SEC` | security-mode condition | +| 16 | `SEC` | security-mode condition (catch-all) | -Source: `enuProgramCond.cs`. The internal bit-split of `cond` (selector vs -operand within the 16-bit field) is **not yet decoded** — see "What we don't -yet know" below. +Family is found by `(cond >> 8) & 0xFC` — bits 2-7 of the high byte. +Source: `enuProgramCond.cs`. See ["cond / cond2 bit +split"](#condcond2-bit-split) below for the full per-family decode. ### `Days` (byte 11) @@ -152,6 +152,67 @@ Decoded as a file record: So: "TIMED program firing at 07:15 on weekdays, doing command 0x44 with par=3 and pr2=256, gated by the condition pair 0x8d09 / 0x9b09." +## `cond`/`cond2` bit split + +The 16-bit `cond` (and `cond2`) field packs **family + selector + operand**. +The high byte's bits 2-7 (i.e. `(cond >> 8) & 0xFC`) discriminate the family; +the bottom byte and low bits of the high byte carry the rest. + +| Family | Match | Bit layout | Selector | Operand | +|---|---|---|---|---| +| `OTHER` | `(cond >> 8) & 0xFC == 0x00` | `....‥‥‥‥ ‥‥‥‥mmmm` | bits 0-3 = `MiscConditional` | (no operand) | +| `ZONE` | `... == 0x04` | `000001oo zzzzzzzz` | bits 0-7 = zone # | bit 9 = `0`=SECURE, `1`=NOT_READY | +| `CTRL` | `... == 0x08` | `000010oo uuuuuuuu (+ bit 8 = high u)` | bits 0-8 = unit # | bit 9 = `0`=OFF, `1`=ON | +| `TIME` | `... == 0x0C` | `000011oo cccccccc` | bits 0-7 = clock # | bit 9 = `0`=DISABLED, `1`=ENABLED | +| `SEC` | anything else (incl. `0x10`+) | `xmmm aaaa ........` | bits 8-11 = area # (0 = any) | bits 12-14 = `SecurityMode`, bit 15 = arming-transition flag | + +Worked examples: + +``` +cond = 0x0605 → ZONE, zone 5, NOT_READY ("Zone 5 NOT_READY") +cond = 0x0A0F → CTRL, unit 15, ON ("Unit 15 ON") +cond = 0x0E03 → TIME, clock 3, ENABLED ("Time clock 3 ENABLED") +cond = 0x000B → OTHER, BATTERY_OK +cond = 0x8100 → SEC, area 1, mode=OFF ("Area 1 OFF") +cond = 0xB100 → SEC, area 1, ARMING AWAY (bit 15 + mode=3) +``` + +Python usage: + +```python +from omni_pca.programs import Condition, ConditionFamily, Program, ProgramType + +p = Program( + prog_type=int(ProgramType.TIMED), + cond=0x0605, # "Zone 5 NOT_READY" + cond2=0xB100, # "Area 1 ARMING AWAY" +) +c1 = p.condition() +assert c1.family is ConditionFamily.ZONE +assert c1.selector == 5 and c1.operand == 1 +assert c1.describe() == "Zone 5 NOT_READY" +``` + +`Condition.describe()` does the rendering with index-based labels +(`"Zone 5"`, `"Unit 12"`) — name lookups need the panel name tables and +are left to the caller. A future helper that takes a name map would be +the obvious next step for a UI editor. + +A few details worth knowing: + +- **`OTHER` ignores high bits.** `cond=0x010B` and `cond=0x000B` both decode + to `BATTERY_OK` — PC Access's encoder sometimes leaves high bits set on + Other-family conditions; the decoder masks them off. +- **`SEC` with mode=Off + bit 15** is the plain "Area X is OFF" encoding, + NOT an arming transition. The arming-transition rendering kicks in only + when bit 15 *and* the mode bits 12-14 are non-zero (per `clsText.cs:2263`). +- **Area `0` in `SEC`** is the "no specific area" / "any area" form — + PC Access renders the area name as blank in this case. + +Source: `clsText.GetConditionalText` at clsText.cs:2224-2273 (decode) and +`frmAutomationEditCondition.cs:615-2550` (encode/UI). The `MiscConditional` +enum mirrors `enuMiscConditional.cs`. + ## TIMED programs: absolute time vs sunrise/sunset offset The `hour` byte at offset 12 is **overloaded** as a one-of-three @@ -258,13 +319,6 @@ specific 16-bit value refers to — is a separate reverse-engineering pass. So is the multi-record clausal encoding hinted at by the `WHEN/AT/EVERY/AND/OR/THEN` connector values in `ProgramType`. Concretely: -- **`cond` / `cond2` internal bit split.** The high bits encode the - `ProgramCond` family (Zone / Ctrl / Time / Sec); we don't yet know - where the selector index (zone number, etc.) and the operand - ("not ready", "Day mode", …) live in the low bits. None of the 330 - defined programs in our fixture is enough to triangulate this — we'd - need to author known programs in PC Access and diff the exported - bytes. - **Multi-record clausal encoding.** No program in our live fixture uses the `WHEN / AT / EVERY / AND / OR / THEN` ProgType values — so we can't yet say whether they reference adjacent slots, use extra bytes