docs(phase4): Modbus Int64/UInt64, FOCAS fail-fast+scaling, Historian Total+dead-letter cap
This commit is contained in:
@@ -111,7 +111,12 @@ collision waits out the file lock instead of failing fast.
|
||||
4. The remaining batch is handed to `IAlarmHistorianWriter.WriteBatchAsync`, and
|
||||
each outcome is applied in one transaction: `Ack` deletes the row,
|
||||
`PermanentFail` flips its `DeadLettered` flag, `RetryPlease` bumps its attempt
|
||||
count and leaves it queued.
|
||||
count and leaves it queued. A row whose `AttemptCount` has reached the configured
|
||||
**`MaxAttempts`** cap (default 10) is dead-lettered automatically on the next drain
|
||||
tick rather than retried — this breaks infinite retry loops for poison events whose
|
||||
payload the historian will always reject (e.g. a malformed alarm record that triggers
|
||||
a permanent SDK error on every attempt). The dead-lettered row remains inspectable
|
||||
via `RetryDeadLettered()` for the configured retention window.
|
||||
5. The timer re-arms its next due-time to `max(tickInterval, currentBackoff)`.
|
||||
|
||||
**Backoff ladder** (applied to the timer's next due-time, so a historian outage
|
||||
@@ -196,6 +201,7 @@ When `Enabled` is `false` (the default), `AddAlarmHistorian` registers
|
||||
| `DatabasePath` | string | — | Absolute path to the SQLite queue file. Created on first use (WAL mode). Required when `Enabled`. |
|
||||
| `SharedSecret` | string | — | Shared secret token the sidecar expects on every connection. Required when `Enabled`. |
|
||||
| `BatchSize` | int | `100` | Max rows per drain cycle handed to `IAlarmHistorianWriter.WriteBatchAsync`. |
|
||||
| `MaxAttempts` | int | `10` | Maximum delivery attempts before a poison (perpetually-retrying) row is dead-lettered automatically. Must be > 0. |
|
||||
| `AlarmHistorian:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. |
|
||||
| `AlarmHistorian:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). |
|
||||
| `AlarmHistorian:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. |
|
||||
|
||||
+21
-2
@@ -154,6 +154,23 @@ are disposed when the session closes). Resuming an unknown / evicted / released
|
||||
`BadContinuationPointInvalid`. `releaseContinuationPoints` drops the stored cursors without reading
|
||||
data.
|
||||
|
||||
### Total aggregate derivation
|
||||
|
||||
The OPC UA `Total` aggregate is **supported** over the Wonderware backend. Because the
|
||||
Wonderware `AnalogSummary` query exposes no `Total` column, the value is derived client-side
|
||||
using the time-integral identity:
|
||||
|
||||
> **Total = time-weighted Average × interval-seconds**
|
||||
|
||||
The wire request is issued with the `Average` column; each returned bucket's value is
|
||||
multiplied by `interval.TotalSeconds` before the result is returned to the OPC UA client.
|
||||
Bucket status codes and timestamps are preserved unchanged. Null (unavailable) Average
|
||||
buckets produce a null Total (`BadNoData` downstream) — the scaling is not applied.
|
||||
|
||||
This derivation is exact for piecewise-constant (step) signals. For continuously varying
|
||||
signals it is an approximation identical to the one Wonderware would apply internally, so
|
||||
the result is consistent with what AVEVA Historian reports for the same window.
|
||||
|
||||
### Known limitations
|
||||
|
||||
- **Processed and AtTime are single-shot** (no continuation points). Unlike Raw, neither
|
||||
@@ -225,9 +242,11 @@ otopcua-cli historyread \
|
||||
-U reader -P password
|
||||
```
|
||||
|
||||
Supported `--aggregate` values: `Average`, `Minimum`, `Maximum`, `Count`, `Start`, `End`,
|
||||
`StandardDeviation` (aliases: `avg`, `min`, `max`, `stddev`/`stdev`, `first`, `last`).
|
||||
Supported `--aggregate` values: `Average`, `Minimum`, `Maximum`, `Total`, `Count`, `Start`, `End`,
|
||||
`StandardDeviation` (aliases: `avg`, `min`, `max`, `total`, `stddev`/`stdev`, `first`, `last`).
|
||||
`--interval` is the processing interval in milliseconds (default 3600000 = 1 hour).
|
||||
`Total` is derived client-side as time-weighted Average × interval-seconds (see "Total aggregate
|
||||
derivation" above).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+29
-5
@@ -78,6 +78,13 @@ The driver picks its client from `Config.Backend`:
|
||||
| `wire` (default) | `WireFocasClient` | Production — pure-managed FOCAS2 over TCP |
|
||||
| `unimplemented` / `none` / `stub` | `UnimplementedFocasClientFactory` | Scaffolding a DriverInstance row before the CNC endpoint is reachable |
|
||||
|
||||
**Fail-fast on `unimplemented` / `none` / `stub`:** a driver instance configured with any of
|
||||
these backends now **faults immediately at `InitializeAsync`** with a clear error message rather
|
||||
than reporting healthy and then failing on the first read/write/subscribe. The driver moves to
|
||||
`Faulted` state and the error is visible on the Admin UI driver-status panel. This is intentional
|
||||
— an operator who forgets to switch to `"wire"` before deploying to production sees a driver
|
||||
fault at startup, not a phantom-healthy driver that silently rejects every request.
|
||||
|
||||
Previous backends (`fwlib`, `fwlib32`, `ipc`) have been retired along
|
||||
with `Driver.FOCAS.Host` and the Fwlib P/Invoke path. Configs that still
|
||||
reference them will throw at startup with a message pointing here.
|
||||
@@ -140,11 +147,26 @@ covers `Spindle/`, `Program/` + `OperationMode/`, `Timers/`, and
|
||||
per-axis `ServoLoad` independently. Identity + `Axes/*` position reads
|
||||
(which every Fanuc CNC supports) are always emitted.
|
||||
|
||||
Position values are scaled integers (matching FOCAS's convention). The
|
||||
managed side exposes them as `Float64` OPC UA nodes; a future
|
||||
`cnc_getfigure` integration will add per-axis decimal scaling. Until
|
||||
then, treat the raw integer as the value the CNC reports and scale on
|
||||
the client side if decimal precision matters.
|
||||
Position values (`AbsolutePosition`, `MachinePosition`, `RelativePosition`, `DistanceToGo`)
|
||||
are CNC-internal scaled integers exposed as `Float64` OPC UA nodes. The driver converts them
|
||||
to engineering units using the per-device `PositionDecimalPlaces` config field (default `0`
|
||||
= no scaling). When set to a positive integer *d*, each position value is divided by `10^d`
|
||||
before publishing, so a CNC that reports millimetres × 1000 is corrected by setting
|
||||
`PositionDecimalPlaces: 3`.
|
||||
|
||||
```jsonc
|
||||
"Devices": [
|
||||
{
|
||||
"HostAddress": "focas://10.20.30.40:8193",
|
||||
"Series": "ThirtyOne_i",
|
||||
"PositionDecimalPlaces": 3 // 123456 → 123.456 mm
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Auto-fetching the decimal-place count via `cnc_getfigure` is deferred (wire-gated). Until
|
||||
that lands, the config field is the authoritative source — consult the MTB / machine
|
||||
parameter sheets for the correct value. Negative values are clamped to `0`.
|
||||
|
||||
**Still user-authored**: `PARAM:6711`, `MACRO:500`, `R100` etc. — specific
|
||||
numbers whose meaning is MTB-specific. Those go under the device folder
|
||||
@@ -221,10 +243,12 @@ latency spike once per cadence.
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| Driver faults immediately at startup with "unimplemented" in the error | `Backend` is `"unimplemented"` / `"none"` / `"stub"` | Change `Backend` to `"wire"` and supply the real CNC endpoint in `Devices[]` |
|
||||
| `BadCommunicationError` on every read | CNC unreachable on TCP:8193 | Check firewall / LAN reachability; FOCAS Ethernet option must be licensed on the CNC side |
|
||||
| Every read returns `BadNotWritable` on writes | Expected — OtOpcUa is read-only against FOCAS | If you actually need writes, open a feature request — the driver's managed wire client doesn't expose the write commands |
|
||||
| `BadOutOfRange` on reads for a macro/parameter | Config address outside the declared `Series` range | Check `docs/v2/focas-version-matrix.md` — either fix the address or widen the `Series` |
|
||||
| Alarm events never fire | `AlarmProjection.Enabled` left at default (false) | Set it to `true` in the driver config |
|
||||
| Axis position values seem 1000× too large | `PositionDecimalPlaces` not set | Add `"PositionDecimalPlaces": 3` (or the MTB-specific value) to the device entry in `Devices[]` |
|
||||
|
||||
## Further reading
|
||||
|
||||
|
||||
@@ -116,3 +116,4 @@ is reproduced in [docs/v2/driver-specs.md §2](../v2/driver-specs.md).
|
||||
- **Wrong-endian readings are silently plausible.** A byte-order misconfiguration produces a wrong number, not a Bad quality code — surface byte-order mismatches as data-validation alerts, not status codes (see [docs/v2/driver-specs.md §2](../v2/driver-specs.md)).
|
||||
- **`WriteOnChangeOnly` + write-only tags** — the suppression cache is only invalidated by a read that returns a divergent value. A tag that is never subscribed/polled never refreshes its cache entry, so a re-asserted value can be suppressed indefinitely. Subscribe every tag that needs deterministic re-writes, or leave the option off.
|
||||
- **Auto-prohibited ranges** are visible via `GetAutoProhibitedRanges` and logged on first occurrence / on clear — use them to find protected register holes in a device's map.
|
||||
- **Int64 / UInt64 OPC UA node DataType** — tags declared with `DataType: Int64` or `DataType: UInt64` advertise the correct OPC UA scalar type (`Int64` / `UInt64`) on their node. Values outside the 32-bit range are preserved end-to-end; the wire codec (4-register read/write) was already correct before this fix.
|
||||
|
||||
Reference in New Issue
Block a user