program-format: structured-OP AND record fully RE'd
Final RE pass on the multi-record AND extension. Authored "AND IF DATE IS EQUAL TO 12/31" and resolved the structured-OP case via the captured bytes 08 07 01 00 00 01 00 1f 0c 00 .... The structured-AND section now has: - A clear distinction between Traditional (OP=0) and Structured (OP>0) modes, with both using the same disk byte slots but different semantics. - A worked byte-by-byte example for "AND IF DATE IS EQUAL TO 12/31" showing how Read's LE-to-BE swap places each field into Data[]. - The clsConditionLine.Cond bridge documented inline, explaining why the Traditional case packs (family, instance) into the same byte positions that structured records use for (OP, Arg1_ArgType, Arg1_IX). - UI labels for each enuCondOP value (IS EQUAL TO, IS GREATER THAN, etc.) so readers can map the enum to what they'd see in PC Access. Python usage section updated to show the structured-AND properties: and_op, and_arg1_argtype, and_arg1_ix, and_arg1_field, and_arg2_argtype, and_arg2_ix, and_arg2_field, and_compconst.
This commit is contained in:
parent
8ece4fa9ed
commit
7bbe1b4372
@ -469,16 +469,18 @@ Pure discriminator — carries no payload. Marks the boundary between alternativ
|
||||
|
||||
Same `cmd` / `par` / `pr2` layout as compact-form records (`cmd` at byte 5, `par` at byte 6, `pr2` at bytes 7-8 LE).
|
||||
|
||||
### Structured-OP AND records (future RE)
|
||||
### Structured-OP AND records (`OP > 0`)
|
||||
|
||||
For the **traditional** AND case (`OP == 0`, the common case), the
|
||||
AND record's byte layout is the single-AND-IF table in the [per-record
|
||||
byte layouts](#per-record-byte-layouts-firmware-30-multi-record-form)
|
||||
section above: byte 1 = family + operand bits, bytes 3-4 BE = instance.
|
||||
The AND record has **two modes** distinguished by the `OP` byte:
|
||||
|
||||
For **structured-OP** cases (e.g. `AND IF TEMPERATURE > 70`), the C#
|
||||
encoder at `frmAutomationEditBlock.cs:988-1000` populates additional
|
||||
fields per the `clsProgram.cs:326-436` accessor view:
|
||||
- **Traditional (`OP == 0`):** the simple-AND-IF case covered in the
|
||||
table above. Byte 2 holds a `ProgramCond` family code (ZONE=4,
|
||||
CTRL=8, …) and bytes 3-4 hold `instance << 8`.
|
||||
- **Structured (`OP > 0`):** comparison-operator conditions like
|
||||
`DATE IS EQUAL TO 12/31`, `TEMPERATURE > 70`, `ZONE IS BYPASSED`.
|
||||
|
||||
Per the `clsProgram.cs:326-436` accessors, a structured AND record's
|
||||
14 bytes decompose as:
|
||||
|
||||
| Byte(s) (in-memory `Data[]`) | Field | Type |
|
||||
| --- | --- | --- |
|
||||
@ -491,20 +493,20 @@ fields per the `clsProgram.cs:326-436` accessor view:
|
||||
| 9 | `Arg2_Field` | byte |
|
||||
| 10-11 | `CompConst` | u16 |
|
||||
|
||||
`enuCondOP` (byte 1 when `OP > 0`):
|
||||
`enuCondOP` (byte 1):
|
||||
|
||||
| Value | Name |
|
||||
| ---: | --- |
|
||||
| 0 | `Arg1_Traditional` (= the common case, byte 1 = family + operand, see table above) |
|
||||
| 1 | `Arg1_EQ_Arg2` |
|
||||
| 2 | `Arg1_NE_Arg2` |
|
||||
| 3 | `Arg1_LT_Arg2` |
|
||||
| 4 | `Arg1_GT_Arg2` |
|
||||
| 5 | `Arg1_Odd` |
|
||||
| 6 | `Arg1_Even` |
|
||||
| 7 | `Arg1_Multiple_Arg2` |
|
||||
| 8 | `Arg1_IN_Arg2` |
|
||||
| 9 | `Arg1_NOT_IN_Arg2` |
|
||||
| Value | Name | UI label |
|
||||
| ---: | --- | --- |
|
||||
| 0 | `Arg1_Traditional` | (simple condition, see table above) |
|
||||
| 1 | `Arg1_EQ_Arg2` | IS EQUAL TO |
|
||||
| 2 | `Arg1_NE_Arg2` | IS NOT EQUAL TO |
|
||||
| 3 | `Arg1_LT_Arg2` | IS LESS THAN |
|
||||
| 4 | `Arg1_GT_Arg2` | IS GREATER THAN |
|
||||
| 5 | `Arg1_Odd` | IS ODD |
|
||||
| 6 | `Arg1_Even` | IS EVEN |
|
||||
| 7 | `Arg1_Multiple_Arg2` | IS A MULTIPLE OF |
|
||||
| 8 | `Arg1_IN_Arg2` | IS IN |
|
||||
| 9 | `Arg1_NOT_IN_Arg2` | IS NOT IN |
|
||||
|
||||
`enuCondArgType` (bytes 2, 6):
|
||||
|
||||
@ -523,12 +525,69 @@ fields per the `clsProgram.cs:326-436` accessor view:
|
||||
| 10 | `Message` |
|
||||
| 11 | `System` |
|
||||
|
||||
The disk-byte-order question for the structured-OP case (whether
|
||||
bytes 1-2 map to `OP` + `Arg1_ArgType` after the LE Read-time swap,
|
||||
or whether the encoder uses a different path) is unresolved — needs a
|
||||
controlled `AND IF TEMP > 70` capture to disambiguate. The `omni_pca`
|
||||
decoder exposes `CondOP` and `CondArgType` enums for callers who want
|
||||
to inspect raw bytes once captures are available.
|
||||
#### Worked example: `AND IF DATE IS EQUAL TO 12/31`
|
||||
|
||||
Authored as a 5-line block, the AND record landed at:
|
||||
|
||||
```
|
||||
08 07 01 00 00 01 00 1f 0c 00 00 00 00 00
|
||||
```
|
||||
|
||||
Tracing the byte flow through Read (LE u16) → setter (swap to BE in `Data[]`) → accessor (BE read):
|
||||
|
||||
| Byte position | Disk | Read u16 LE | After setter (Data[]) | Accessor reads |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1-2 | `07 01` | Cond=`0x0107` | `Data[1]=01, Data[2]=07` | OP=`1` (`Arg1_EQ_Arg2`), Arg1_ArgType=`7` (`TimeDate`) |
|
||||
| 3-4 | `00 00` | Cond2=`0x0000` | `Data[3]=00, Data[4]=00` | Arg1_IX=`0` (CURRENT_DATE) |
|
||||
| 5 | `01` | — | `Data[5]=01` | Arg1_Field=`1` |
|
||||
| 6 | `00` | — | `Data[6]=00` | Arg2_ArgType=`0` (`Constant`) |
|
||||
| 7-8 | `1f 0c` | Pr2=`0x0c1f` | `Data[7]=0c, Data[8]=1f` | Arg2_IX=`0x0c1f` = (month=12, day=31) |
|
||||
| 9 | `00` | — | `Data[9]=00` | Arg2_Field=`0` |
|
||||
| 10-11 | `00 00` | — | `Data[10]=00, Data[11]=00` | CompConst=`0` |
|
||||
|
||||
#### How Traditional and Structured share the same byte slots
|
||||
|
||||
The C# `clsConditionLine.Cond` property at `clsConditionLine.cs:17-33`
|
||||
is the bridge between the two modes:
|
||||
|
||||
```csharp
|
||||
public ushort Cond {
|
||||
get {
|
||||
if (OP == enuCondOP.Arg1_Traditional)
|
||||
return ((Arg1.ArgType << 8) | (Arg1.IX >> 8));
|
||||
return 0;
|
||||
}
|
||||
set {
|
||||
OP = enuCondOP.Arg1_Traditional;
|
||||
Arg1.ArgType = (enuCondArgType)(value >> 8);
|
||||
Arg1.IX = (ushort)((value & 0xFF) << 8);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When you set `Cond = 0x0405` for "Zone 5 Secure":
|
||||
- `Arg1.ArgType` receives `0x04` (the family code value, stored as a
|
||||
raw byte — semantically a `ProgramCond.ZONE` even though the field
|
||||
type is `enuCondArgType`)
|
||||
- `Arg1.IX` receives `(0x05 << 8) = 0x0500` (the instance number
|
||||
shifted up by 8 bits)
|
||||
|
||||
These get serialised through the same `clsProgram.Cond` /
|
||||
`Cond2` setters as compact-form records, ending up on disk as
|
||||
`04 00 00 05` at bytes 1-4. The same disk slots therefore double
|
||||
as:
|
||||
|
||||
- `OP` (byte 2 LE → `Data[1]`) and `Arg1_ArgType` (byte 1 LE →
|
||||
`Data[2]`) for structured records, OR
|
||||
- `family << 8 | instance >> 8` packed into the `Cond` u16 for
|
||||
Traditional records (using the same byte storage but interpreted
|
||||
via the `clsConditionLine.Cond` synthesis)
|
||||
|
||||
The `omni_pca.Program` class exposes `and_op`, `and_arg1_argtype`,
|
||||
`and_arg1_ix`, `and_arg2_ix` etc. as direct accessors for the
|
||||
structured case, plus `and_family` / `and_instance` as
|
||||
Traditional-friendly aliases (with `and_instance` doing the right
|
||||
shift for the Traditional case automatically).
|
||||
|
||||
### Other open items
|
||||
|
||||
@ -581,11 +640,23 @@ if p.is_multi_record():
|
||||
elif p.prog_type == ProgramType.EVERY:
|
||||
interval = p.every_interval # e.g. 5 for "5 SECONDS"
|
||||
|
||||
# AND (8) — one AND-IF condition
|
||||
# AND (8) — one AND-IF condition (either Traditional or Structured)
|
||||
elif p.prog_type == ProgramType.AND:
|
||||
family = p.and_family # high byte: 0x04=ZONE, 0x08=CTRL, 0x0A=CTRL+ON, ...
|
||||
operand = (family & 0x02) >> 1 # ON / NOT_READY / ENABLED
|
||||
instance = p.and_instance # zone#, unit#, MiscConditional value
|
||||
if p.and_op == 0:
|
||||
# Traditional: family code at byte 1, instance at byte 4
|
||||
family = p.and_family # 0x04=ZONE, 0x08=CTRL, 0x0A=CTRL+ON, ...
|
||||
operand = (family & 0x02) >> 1 # ON / NOT_READY / ENABLED
|
||||
instance = p.and_instance # zone#, unit#, MiscConditional value
|
||||
else:
|
||||
# Structured: e.g. "AND IF DATE IS EQUAL TO 12/31"
|
||||
op_ = p.and_op # enuCondOP (1=EQ, 2=NE, 3=LT, 4=GT, ...)
|
||||
arg1_type = p.and_arg1_argtype # enuCondArgType (TimeDate=7, ...)
|
||||
arg1_ix = p.and_arg1_ix # object index or sub-field code
|
||||
arg1_fld = p.and_arg1_field
|
||||
arg2_type = p.and_arg2_argtype # often Constant (=0)
|
||||
arg2_ix = p.and_arg2_ix # constant value or 2nd object
|
||||
arg2_fld = p.and_arg2_field
|
||||
compconst = p.and_compconst
|
||||
|
||||
# OR (9) — pure separator, no fields
|
||||
elif p.prog_type == ProgramType.OR:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user