@@ -21,6 +21,7 @@ Each FOCAS-equivalent call gets a stable wire-protocol command id. Ids are
|
||||
| ... | ... | ... |
|
||||
| **`0x0102`** | **`cnc_wrparam`** | **IODBPSD parameter-write packet (issue #269, plan PR F4-b)** |
|
||||
| **`0x0103`** | **`cnc_wrmacro`** | **ODBM macro-write packet (issue #269, plan PR F4-b)** |
|
||||
| **`0x0104`** | **`pmc_wrpmcrng`** | **IODBPMC PMC range-write packet (issue #270, plan PR F4-c)** |
|
||||
| `0x0F1A` | **`cnc_rdalmhistry`** | **ODBALMHIS alarm-history ring-buffer dump (issue #267, plan PR F3-a)** |
|
||||
|
||||
## ODBALMHIS — alarm history (`cnc_rdalmhistry`, command `0x0F1A`)
|
||||
@@ -154,3 +155,62 @@ the same PR; the unit test
|
||||
`FocasWriteParameterTests.ParameterWrite_round_trip_stores_value_visible_to_subsequent_read`
|
||||
exercises encode → store → decode with the fake wire client and is the
|
||||
canary for symmetry regressions.
|
||||
|
||||
## IODBPMC — PMC range write (`pmc_wrpmcrng`, command `0x0104`)
|
||||
|
||||
Issue #270, plan PR F4-c. The write-side payload is the read-side
|
||||
`pmc_rdpmcrng` IODBPMC packet with the data direction inverted: the
|
||||
caller fills the `data[]` byte run and the simulator / Fwlib32 stores
|
||||
it; the response is the small status envelope rather than the populated
|
||||
data buffer the read side returns.
|
||||
|
||||
### Request
|
||||
|
||||
| Offset | Width | Field |
|
||||
| --- | --- | --- |
|
||||
| 0 | `int16 LE` | `type_a` — PMC address-type code (R=5, G=4, F=3, D=8, X=1, Y=2, K=10, A=11, E=12, T=6, C=7) |
|
||||
| 2 | `int16 LE` | `type_d` — data type (`0` = byte; only byte writes are issued — bit writes wrap the byte path with a read-modify-write helper) |
|
||||
| 4 | `uint16 LE` | `datano_s` — first byte address (inclusive) |
|
||||
| 6 | `uint16 LE` | `datano_e` — last byte address (inclusive) — `(datano_e - datano_s + 1)` is the byte count |
|
||||
| 8 | `bytes` | `data[]` — payload, exactly `(datano_e - datano_s + 1)` bytes |
|
||||
|
||||
The header is 8 bytes; the FWLIB `IODBPMC.data` field caps at 32 bytes
|
||||
(40-byte total per call), so larger ranges are chunked into 32-byte
|
||||
sub-calls by the wire client. The simulator MUST honour the same chunk
|
||||
ceiling so chunked-vs-single round-trips produce the same final bytes.
|
||||
|
||||
### Response
|
||||
|
||||
Same single-int16 envelope as `cnc_wrparam` / `cnc_wrmacro`:
|
||||
|
||||
| Offset | Width | Field |
|
||||
| --- | --- | --- |
|
||||
| 0 | `int16 LE` | `ew_status` — `0` = success, non-zero = FANUC `EW_*` |
|
||||
|
||||
`EW_NOOPT` (option not installed), `EW_NUMBER` (out-of-range address),
|
||||
`EW_LENGTH` (chunk size mismatch) are the typical failures the simulator
|
||||
reproduces; the mapper translates them to OPC UA status codes the same
|
||||
way the read-side does.
|
||||
|
||||
### Bit-level RMW (driver-side, no extra wire op)
|
||||
|
||||
`pmc_wrpmcrng` is **byte-addressed** — there is no sub-byte write op on
|
||||
the wire. Bit writes go through `IFocasClient.WritePmcBitAsync` which:
|
||||
|
||||
1. Issues a 1-byte `pmc_rdpmcrng` to fetch the parent byte.
|
||||
2. Masks the target bit (set: OR; clear: AND-NOT).
|
||||
3. Issues a 1-byte `pmc_wrpmcrng` with the modified byte.
|
||||
|
||||
A per-byte semaphore in `FwlibFocasClient` serialises concurrent bit
|
||||
writes against the same byte so two updates that race never lose one
|
||||
another's bit. The simulator's handler implements the same byte-aligned
|
||||
semantics — bit writes never reach it as a separate frame.
|
||||
|
||||
### Symmetry note
|
||||
|
||||
The encoder is the `pmc_rdpmcrng` decoder reversed: the read side parses
|
||||
`(type_a, type_d, datano_s, datano_e)` from the request and emits the
|
||||
data buffer in the response; the write side parses all five fields plus
|
||||
the data buffer from the request and emits a status int16 in the
|
||||
response. Tests `FocasWritePmcTests.PMC_*` exercise the round-trip on
|
||||
the fake wire client.
|
||||
|
||||
Reference in New Issue
Block a user