Merge branch 'master' into b626-opcuaclient
This commit is contained in:
@@ -67,6 +67,9 @@ pieces for driver-side routing (e.g. deciding a bit needs read-modify-write):
|
||||
| `ST9:0` | String file (82-byte fixed-length) |
|
||||
| `T4:0.ACC` / `C5:0.PRE` | Timer / counter sub-element |
|
||||
| `I:0/0` / `O:1/2` / `S:1` | Input / output / status system files (no file number) |
|
||||
| `I:017/07` | **PLC-5 only:** `I:`/`O:` word and bit indices are **octal** (#244); a digit 8/9 is a parse error |
|
||||
| `RTC:0.HR` / `HSC:0.ACC` | **MicroLogix only:** function files RTC, HSC, DLS, MMI, PTO, PWM, STI, EII, IOS, BHI (#245) |
|
||||
| `N7:[N7:0]` / `N[N7:0]:5` | Indirect (index word read from the PLC) — parsed, **not supported at runtime** (#247) |
|
||||
|
||||
`AbLegacyDataType` covers the corresponding PCCC types: `Bit`, `Int` (N), `Long`
|
||||
(L), `Float` (F), `AnalogInt` (A), `String` (ST), and the `TimerElement` /
|
||||
@@ -85,6 +88,42 @@ this is an inherent limitation of any software-level RMW. Input-image (`I`) bit
|
||||
writes are forwarded to the PLC as-is; the device drives the input image from
|
||||
physical inputs and may reject the write.
|
||||
|
||||
### Family-specific address forms (#244 / #245 / #247)
|
||||
|
||||
`AbLegacyAddress.TryParse(text, family)` is family-aware; the driver passes the device's `PlcFamily`
|
||||
(the one-argument overload is the family-neutral grammar: decimal, no function files).
|
||||
|
||||
- **PLC-5 octal I/O (#244).** RSLogix 5 shows `I:`/`O:` word and bit indices in octal, so `I:017/07` is word 15,
|
||||
bit 7 and `I:017/17` is bit 15. Only `I`/`O` on PLC-5 (`OctalIoAddressing` on the family profile) — SLC,
|
||||
MicroLogix and Logix-PCCC stay decimal, and PLC-5 `N`/`B`/... files stay decimal. The record stores decimal and
|
||||
`ToLibplctagName()` **emits decimal**, so the libplctag `name=` string and the string the native transport
|
||||
re-parses carry identical numbers. This is not a double conversion: libplctag's `pccc.c`
|
||||
(`parse_pccc_elem_num` / `parse_pccc_bit_num`) parses decimal numerals only (a FIXME there says octal is
|
||||
unsupported), so the driver must convert. Pinned by `Plc5_io_indices_are_octal` (`I:017/17` -> `I:15/15`).
|
||||
The connectivity-probe address is normalised the same way.
|
||||
- ⚠️ **Behaviour change / migration.** Before this change a PLC-5 `I:`/`O:` address was passed to libplctag as
|
||||
written, i.e. read as **decimal**. An address copied from RSLogix in octal (e.g. `I:017`) therefore read the
|
||||
wrong word, and an authored decimal such as `I:1/12` now means bit 10 (octal 12). A previously "working"
|
||||
address containing the digits 8 or 9 (`I:018`, `O:1/9`) is no longer valid on PLC-5. Re-author any PLC-5
|
||||
I/O tags in RSLogix octal. Such a tag is refused (see below), never silently misread.
|
||||
- **A malformed / family-invalid address** (also: a function-file letter off MicroLogix) reads and writes
|
||||
`BadNodeIdUnknown` for that tag only, creates no runtime, leaves driver health alone, and logs **one**
|
||||
Warning per tag. The same check (`AbLegacyAddress.Validate`) runs in the AdminUI tag editor (family-independent:
|
||||
rejects only what *no* family accepts) and the deploy gate (`EquipmentTagConfigInspector`, using the device's
|
||||
`DeviceConfig.PlcFamily`, absent = SLC 500, unrecognised = unknown) — so `I:018` on a PLC-5 device warns at
|
||||
deploy (an error under `TagConfigValidationMode.Error`), and the editor is never stricter than the gate.
|
||||
- **MicroLogix function files (#245).** `RTC`, `HSC`, `DLS`, `MMI`, `PTO`, `PWM`, `STI`, `EII`, `IOS`, `BHI`
|
||||
(single fixed instance — no file number — with a `.SUBELEMENT`; no bit index) parse on MicroLogix devices only.
|
||||
`AbLegacyFunctionFile.SubElementType` carries the sub-element catalogue (flags → Boolean, words → Int32; unknown
|
||||
members default to Int32). ⚠️ **Wire-unverified on both transports:** there is no capture and no simulator
|
||||
coverage; the libplctag path passes the name through and the native transport has no file-type code for them, so
|
||||
it refuses the tag as `Unsupported` (`BadNotSupported` to the client; see Wire evidence).
|
||||
- **Indirect addressing (#247).** `N7:[N7:0]` (word from another address) and `N[N7:0]:5` (file from another
|
||||
address) parse, one level deep (the inner address must be direct, and carries the family's octal rule).
|
||||
**Runtime resolution is not implemented** (it needs a pre-read of the index word and per-resolved-address
|
||||
runtimes): reads and writes of an indirect tag answer `BadNotSupported` before any runtime is created, on both
|
||||
transports.
|
||||
|
||||
## Configuration
|
||||
|
||||
`AbLegacyDriverOptions` (`Driver.AbLegacy.Contracts/AbLegacyDriverOptions.cs`)
|
||||
@@ -172,6 +211,7 @@ Everything below is against the `ab_server` simulator; no real PCCC controller h
|
||||
| N-file write (`N7:5`) | capture `slc500-write-n7` + native `AbLegacyReadSmokeTests` write/read round trip (SLC 500) |
|
||||
| F-file read (`F8`) | one capture, `slc500-read-f8` (codec parity only; no native F write on a wire) |
|
||||
| S (incl. the default probe `S:0`), B, T, C, R, L, ST, A, O, I | **none** — encoded from the PCCC file-type table |
|
||||
| MicroLogix function files (RTC/HSC/...), PLC-5 octal I/O, indirect addressing | **none** — parsed only; native refuses function files and indirect names as Unsupported |
|
||||
| T/C/R sub-elements (`.PRE`/`.ACC`/`.LEN`/`.POS`), 6-byte T/C/R element layout | **none** |
|
||||
|
||||
### Native known gaps
|
||||
|
||||
@@ -91,6 +91,10 @@ on.
|
||||
| `MACRO:500` | Custom macro variable | System / user macro variable number |
|
||||
| `FIXED:Identity/SeriesNumber` | Fixed-tree field | A path into the [fixed node tree](#fixed-node-tree) — read-only |
|
||||
|
||||
PMC letters are series-gated by `FocasCapabilityMatrix.PmcLetters`; the 16i accepts `X Y F G R D` (F/G are
|
||||
the CNC-to-PMC / PMC-to-CNC handshake groups its ladders use, #265 — reads only, FOCAS stays read-only), while
|
||||
`M C E A K T` remain 0i-F / 30i-family.
|
||||
|
||||
The first three name a location the driver asks the CNC for on demand.
|
||||
`FIXED:` does not: it names a field of the per-device fixed-tree snapshot the
|
||||
driver's own poll loop already maintains, so a `FIXED:` read issues no wire
|
||||
|
||||
+59
-20
@@ -230,10 +230,37 @@ Other status codes an observation can surface:
|
||||
|---|---|
|
||||
| `BadTypeMismatch` (`0x80740000`) | The Agent's text isn't a value of the tag's coerced type at all |
|
||||
| `BadOutOfRange` (`0x803C0000`) | The Agent reported a number the coerced type can't represent |
|
||||
| `BadNotSupported` (`0x803D0000`) | A shape this build doesn't materialize — a `TIME_SERIES` vector, or a structured `DATA_SET`/`TABLE` observation (deferred to P1.5; see [Known limitations](#known-limitations)) |
|
||||
| `BadDecodingError` (`0x80070000`) | A `TIME_SERIES` vector whose element count disagrees with the observation's own `sampleCount` (or the tag's fixed `ArrayDim`) — see [TIME_SERIES](#time_series-arrays-627) |
|
||||
| `BadNotSupported` (`0x803D0000`) | A shape this build doesn't materialize — a structured `DATA_SET`/`TABLE` observation (decided: stays unsupported, #627), or an array tag declared as anything other than Float64 |
|
||||
| `BadWaitingForInitialData` | Tag is authored but the Agent hasn't reported it yet |
|
||||
| `BadNodeIdUnknown` | DataItem id is neither authored nor ever observed |
|
||||
|
||||
### TIME_SERIES arrays (#627)
|
||||
|
||||
A `SAMPLE` DataItem with `representation="TIME_SERIES"` is served as a
|
||||
**variable-length `double[]`** (`ValueRank = 1`, no fixed `ArrayDimensions` —
|
||||
a live Agent never declares `sampleCount` on the DataItem, see Known
|
||||
limitations 4; a probe that does declare one yields a fixed `ArrayDim`).
|
||||
Parsing: the observation text is split on any whitespace, each token parsed
|
||||
invariant-culture. The result is **all-or-nothing** — one non-numeric /
|
||||
non-finite token is `BadTypeMismatch`, a literal `UNAVAILABLE` token or an
|
||||
empty body is `BadNoCommunication`, and a count that disagrees with the
|
||||
observation's own `sampleCount` attribute (or a fixed `ArrayDim`) is
|
||||
`BadDecodingError`. A partial vector is never served as Good. Only a
|
||||
**Float64** array tag is served; any other declared element type stays
|
||||
`BadNotSupported`. An authored tag needs `"isArray": true` and
|
||||
`"dataType": "Float64"` in its TagConfig (the AdminUI editor shows an advisory
|
||||
for any other array type; a browse-commit does not stamp `isArray`, so set it
|
||||
in the Tag modal's array section).
|
||||
|
||||
**Sample rate is not exposed on the served node.** The observation's
|
||||
`sampleRate` and `sampleCount` attributes are parsed (`MTConnectObservation`)
|
||||
but the OPC UA node has no description/property slot through `DriverAttributeInfo`,
|
||||
and a sibling property node would need a new address-space mechanism. Decision:
|
||||
document it — the rate is a property of the Agent's DataItem (`sampleRate` in
|
||||
`/probe`), read it there. **`DATA_SET` / `TABLE` stay `BadNotSupported`** (decision, #627): they are
|
||||
key/value maps with no scalar or array OPC UA shape that preserves the keys.
|
||||
|
||||
### Subscribe — the `/sample` pump
|
||||
|
||||
One shared `/sample` long-poll stream per driver instance (the Agent streams
|
||||
@@ -383,14 +410,30 @@ for anything beyond values-and-conditions.
|
||||
`GetHostStatuses()` fresh, and the result rides `DriverHealthChanged` to
|
||||
the AdminUI `/hosts` page (no SQL; the `DriverHostStatus` table was
|
||||
dropped). See `docs/Telemetry.md`.
|
||||
2. **`RawTagEntry.DeviceName` is accepted on the wire shape but ignored for
|
||||
routing.** One driver instance owns exactly one Agent client scoped by
|
||||
`MTConnectDriverOptions.DeviceName` (the top-level config key); the
|
||||
per-tag `RawTagEntry.DeviceName` field the v3 raw-tag identity carries is
|
||||
never read by the driver. Two `/raw` Devices authored under one MTConnect
|
||||
driver instance both resolve to the same one Agent connection. Real
|
||||
per-device routing (a client per device) would be a design change, not a
|
||||
bug fix.
|
||||
2. **One MTConnect driver instance = one Agent client = at most one `/raw`
|
||||
Device (#628, decision (b)).** The driver never reads
|
||||
`RawTagEntry.DeviceName`; every raw tag binds through the single Agent client
|
||||
scoped by `MTConnectDriverOptions.DeviceName`. The v3 raw tree always has a
|
||||
Device level (a new driver auto-creates `Device1`), and that single-Device
|
||||
layout is the normal, valid shape — its `DeviceConfig` is merged up into the
|
||||
driver config by `DriverDeviceConfigMerger` (this is how the endpoint lands).
|
||||
With **two or more** Devices under one MTConnect driver there is no merge-up
|
||||
and no routing, so which Device supplies the endpoint is ambiguous. That is
|
||||
now a **deploy-time `DraftValidator` error** (`MTConnectMultipleDevices`),
|
||||
and the AdminUI refuses to *create* the second Device
|
||||
(`RawTreeService.CreateDeviceAsync`, same message): "MTConnect binds all tags
|
||||
through one agent client per driver instance; author one MTConnect driver per
|
||||
agent device." Option (a), a client per Device, was rejected as a design
|
||||
change with no demand. A multi-device Agent is served by several driver
|
||||
instances, each with its own `DeviceName` scope. The driver's init-time
|
||||
Warning for differing `RawTagEntry.DeviceName` values stays as defence in
|
||||
depth for configs that predate the rule.
|
||||
|
||||
⚠️ **Upgrade note:** an MTConnect driver that *already* has two or more Device
|
||||
rows fails its next deploy with `MTConnectMultipleDevices` (the rule counts every
|
||||
Device row, enabled or not, because nothing at runtime reads `Device.Enabled`).
|
||||
Before upgrading, move each extra Device's tags under its own MTConnect driver
|
||||
and delete the extra Device rows.
|
||||
|
||||
**The write path is the one place a device name is resolved per DataItem**,
|
||||
and it does not use `RawTagEntry.DeviceName` either: it reads the owning
|
||||
@@ -399,18 +442,13 @@ for anything beyond values-and-conditions.
|
||||
So `MTConnectDriverOptions.DeviceName` does double duty — request scope for
|
||||
reads/browse, fallback write target for writes.
|
||||
|
||||
**It is no longer silent (#613).** When the instance's raw tags carry two or
|
||||
more distinct non-blank `RawTagEntry.DeviceName` values, driver init logs one
|
||||
Warning naming them and the driver-level scope they collapse into. A single
|
||||
value is not reported (it is just the `/raw` Device's own name). Real
|
||||
per-device routing remains deferred.
|
||||
3. **Authoring only *warns* when a declared coercion type cannot hold the
|
||||
inferred one (#613).** The AdminUI tag editor (`MTConnectTagConfigModel.Warnings()`)
|
||||
compares the declared type with `MTConnectDataTypeInference` over the
|
||||
`/probe` metadata a browse-commit stamped (`mtCategory`/`mtType`), and shows an
|
||||
advisory line — it never blocks a save (the deploy gate does not refuse it) and
|
||||
is a no-op for hand-authored blobs with no stamped metadata. `representation`
|
||||
is not on the model, so TIME_SERIES arrays are not covered. Beyond that,
|
||||
is not on the model; TIME_SERIES arrays get their own `isArray` advisory (declare Float64). Beyond that,
|
||||
nothing validates a raw tag's declared OPC UA `DataType` against the
|
||||
blob's coercion type. The `/probe`-sourced inference
|
||||
(`MTConnectDataTypeInference`) narrows how often an author picks a wrong
|
||||
@@ -427,9 +465,11 @@ for anything beyond values-and-conditions.
|
||||
declare a `sampleCount`, is unrealistic on this point and exists only to
|
||||
pin the parsing rule itself.
|
||||
5. **CONDITION is a plain `String`, not a native Part 9 alarm** (see above).
|
||||
6. **`TIME_SERIES` arrays and structured `DATA_SET`/`TABLE` observations
|
||||
surface as `BadNotSupported`**, not an approximation — see the Quality
|
||||
mapping table.
|
||||
6. **Structured `DATA_SET`/`TABLE` observations surface as
|
||||
`BadNotSupported`**, not an approximation — see the Quality mapping table.
|
||||
`TIME_SERIES` is served as `double[]` (#627, see
|
||||
[TIME_SERIES arrays](#time_series-arrays-627)); the sample rate is not
|
||||
surfaced on the node.
|
||||
|
||||
## Deferred (not in this build)
|
||||
|
||||
@@ -447,8 +487,7 @@ for anything beyond values-and-conditions.
|
||||
- **P1.5 fast-follow** (design
|
||||
[§9](../plans/2026-07-15-mtconnect-driver-design.md#9-phasing--effort)):
|
||||
CONDITION → native OPC UA Part 9 alarms via `IAlarmSource` (the Galaxy
|
||||
native-alarm pattern); `TIME_SERIES` SAMPLE arrays materialized as real
|
||||
OPC UA arrays; EVENT controlled-vocabulary values → OPC UA enumerations.
|
||||
native-alarm pattern); EVENT controlled-vocabulary values → OPC UA enumerations.
|
||||
- **P2 (on demand): SHDR adapter ingest.** A `SourceMode: "Agent" | "Shdr"`
|
||||
switch that opens the raw pipe-delimited SHDR TCP socket directly. Loses
|
||||
auto-discovery (no device model without an Agent in front, so
|
||||
|
||||
+74
-8
@@ -65,6 +65,30 @@ hardware. A wrong slot causes a connection refusal during the handshake. See
|
||||
`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Contracts/S7DriverOptions.cs` for the
|
||||
per-field defaults.
|
||||
|
||||
### TSAP / connection-type override
|
||||
|
||||
By default S7.Net derives both ISO-TSAPs from `CpuType` / `Rack` / `Slot` (local `01.00`; remote
|
||||
`03.<rack×0x20+slot>` for the S7-300/400/1200/1500 families). Three optional keys override that
|
||||
for CPUs and gateways that hand out connection resources by TSAP (S7-400H, CP343, LOGO!):
|
||||
|
||||
| Key | Form | Effect |
|
||||
|---|---|---|
|
||||
| `localTsap` | `"01.00"` (also `"0100"`, `"0x0100"`, or a 16-bit number) | replaces the local TSAP |
|
||||
| `remoteTsap` | `"03.01"` (same forms) | replaces the whole remote TSAP |
|
||||
| `connectionType` | `"PG"` \| `"OP"` \| `"S7Basic"` (string) | writes `1` / `2` / `3` into the **first** remote-TSAP byte, keeping `rack×0x20+slot` as the second |
|
||||
|
||||
`remoteTsap` and `connectionType` are mutually exclusive (a config carrying both fails to load); a
|
||||
malformed TSAP fails to load rather than silently falling back to the slot-derived default. With none of
|
||||
the three set the connection is built exactly as before. The override applies to the driver, the AdminUI
|
||||
Test Connect probe and the AdminUI S7 driver form (Connection type / Local TSAP / Remote TSAP fields).
|
||||
|
||||
### Test Connect and PUT/GET
|
||||
|
||||
After the handshake, Test Connect reads 2 bytes from `MB0`. A CPU with PUT/GET disabled (the S7-1200/1500
|
||||
default) completes the handshake but denies every read, so the probe fails with an actionable "enable
|
||||
PUT/GET communication" message instead of a false green tick. A CPU that merely rejects that address (no
|
||||
`MB0`) still passes — the connection is fine; any other read fault fails the probe with its message.
|
||||
|
||||
## Address forms
|
||||
|
||||
Addresses use Siemens TIA-Portal / STEP 7 Classic syntax, parsed by
|
||||
@@ -102,19 +126,52 @@ extra PLC round-trip.
|
||||
| `UInt64` | ULINT/LWORD | 8 bytes | yes | yes |
|
||||
| `Float64` | LREAL | 8 bytes | yes | yes |
|
||||
| `String` | STRING | `StringLength + 2` bytes | yes | yes |
|
||||
| `DateTime` | DATE_AND_TIME | 8 bytes | yes | yes |
|
||||
| `WString` | WSTRING | `4 + 2 × StringLength` bytes | yes | yes |
|
||||
| `Char` | CHAR | 1 byte | yes | yes |
|
||||
| `WChar` | WCHAR | 2 bytes | yes | yes |
|
||||
| `DateTime` | DATE_AND_TIME (DT) | 8 bytes (BCD) | yes | yes |
|
||||
| `Dtl` | DTL | 12 bytes | yes | yes |
|
||||
| `Date` | DATE | 2 bytes | yes | yes |
|
||||
| `Tod` | TIME_OF_DAY | 4 bytes | yes | yes |
|
||||
| `Time` | TIME | 4 bytes | yes | yes |
|
||||
| `S5Time` | S5TIME | 2 bytes | yes | yes |
|
||||
| Timer (`T{n}`) | TIME | — | yes | **read-only** |
|
||||
| Counter (`C{n}`) | COUNTER | — | yes | **read-only** |
|
||||
|
||||
Wide types (`Int64`, `UInt64`, `Float64`, `String`, `DateTime`) and Timer/Counter require
|
||||
byte-anchored addressing — see the section below.
|
||||
Wide types (`Int64`, `UInt64`, `Float64`, `String`, `DateTime`, and the string / date-time
|
||||
types below) and Timer/Counter require byte-anchored addressing — see the section below.
|
||||
|
||||
### String, character and date/time/duration types
|
||||
|
||||
These add no new OPC UA data types — they map onto existing ones:
|
||||
|
||||
| DataType | OPC UA type | Representation | Write refuses (`BadOutOfRange`, nothing sent) |
|
||||
|---|---|---|---|
|
||||
| `String` | String | S7 `[maxLen][curLen][chars]`, ASCII | over `StringLength` (S7.Net behaviour) |
|
||||
| `WString` | String | `[maxLen UInt16][curLen UInt16][UTF-16BE]`; `stringLength` is the declared max in characters, **0–16382** (default 254) | more UTF-16 code units than `StringLength` |
|
||||
| `Char` | String (1 char) | 1 byte, Latin-1 | a character above U+00FF |
|
||||
| `WChar` | String (1 char) | UTF-16BE code unit | — (a string whose length is not 1 is `BadTypeMismatch`) |
|
||||
| `DateTime` | DateTime | DT, 8-byte BCD (S7.Net codec; 1990–2089) | out-of-range year |
|
||||
| `Dtl` | DateTime | year UInt16 + month/day/weekday/h/m/s + UInt32 nanoseconds; 100 ns resolution | year outside 1970–2262 |
|
||||
| `Date` | DateTime (midnight) | UInt16 days since 1990-01-01 (max 2169-06-06) | before 1990-01-01 or after 2169-06-06 |
|
||||
| `Tod` | **Int32 ms** since midnight | UInt32 ms | outside 0–86 399 999 |
|
||||
| `Time` | **Int32 ms** (signed duration) | Int32 ms | outside the Int32 range |
|
||||
| `S5Time` | **Int32 ms** | 2-bit time base (10 ms / 100 ms / 1 s / 10 s) + 3 BCD digits | above 9 990 000 ms, negative, or not exactly representable (resolution is 10 ms up to 9 990 ms, then coarser) — **never rounded** |
|
||||
|
||||
A fractional millisecond `Double` is rounded to the nearest whole millisecond (banker's rounding), exactly
|
||||
as the driver does for every other Int32 tag write; only values off the S5TIME time-base grid are refused.
|
||||
A write of a `TimeSpan` is also accepted for `Tod` / `Time` / `S5Time`. A PLC buffer the codec cannot
|
||||
decode — an uninitialised (all-zero) `Dtl`, a non-BCD `S5Time` digit, a `WString` whose current length
|
||||
exceeds its reserved length — reads `BadOutOfRange` for that tag only, without degrading driver health
|
||||
or condemning the connection. `S5Time` writes pick the finest time base that holds the value. The
|
||||
`Timer` area (`T{n}`) keeps its own S5TIME-to-**seconds** (`Float64`) decode.
|
||||
|
||||
## Wide types & Timer/Counter
|
||||
|
||||
### Byte-anchored addressing for wide / structured types
|
||||
|
||||
Wide types (`Int64`, `UInt64`, `Float64`/LReal, `String`, `DateTime`) are
|
||||
**byte-anchored**: the address must use the `B` suffix pointing at the **first
|
||||
Wide types (`Int64`, `UInt64`, `Float64`/LReal, `String`, `DateTime`, `WString`, `Char`,
|
||||
`WChar`, `Dtl`, `Date`, `Tod`, `Time`, `S5Time`) are **byte-anchored**: the address must use the `B` suffix pointing at the **first
|
||||
byte** of the value; the driver reads the correct number of contiguous bytes
|
||||
based on the DataType.
|
||||
|
||||
@@ -125,6 +182,8 @@ based on the DataType.
|
||||
| `Float64` | same | 8 | `DB1.DBB8` → LREAL at bytes 8–15 |
|
||||
| `DateTime` | same | 8 | `DB3.DBB0` → DATE_AND_TIME at bytes 0–7 |
|
||||
| `String` | same | `StringLength + 2` | `DB4.DBB0`, StringLength=40 → 42 bytes |
|
||||
| `WString` | same | `4 + 2 × StringLength` | `DB5.DBB0`, StringLength=10 → 24 bytes |
|
||||
| `Dtl` | same | 12 | `DB6.DBB0` → DTL at bytes 0–11 |
|
||||
|
||||
Using a `W` or `D` suffix with a wide DataType is a config error caught at
|
||||
`InitializeAsync`. For array tags (`isArray: true`), wide-type element arrays
|
||||
@@ -155,11 +214,18 @@ The following are explicitly deferred and will produce a config error or
|
||||
`BadNotSupported` if attempted:
|
||||
|
||||
- **Wide-type arrays** — array tags (`isArray: true`) with element types `Int64`,
|
||||
`UInt64`, `Float64`, `String`, or `DateTime`.
|
||||
- **`S7WString`** (2-byte character strings; distinct from classic `STRING`).
|
||||
- **`DTL` / `DateTimeLong`** (12-byte Siemens date-and-time-long).
|
||||
`UInt64`, `Float64`, `String`, `DateTime`, or any of the string / date-time types above.
|
||||
- **Timer / Counter writes** — surfaced as `BadNotWritable` until write support lands.
|
||||
|
||||
## Negotiated PDU size
|
||||
|
||||
The PDU size the CPU negotiates during the connection handshake (typically 240 bytes; 480 / 960 with
|
||||
extended PDU) is snapshotted at every (re)connect and exposed as `S7Driver.NegotiatedPduSize` /
|
||||
`S7Driver.GetDiagnostics()["S7.NegotiatedPduSize"]` (0 before the first connect and after shutdown). It is
|
||||
also written to the `S7Driver connected` / `reconnected` log lines and to the AdminUI Test Connect
|
||||
message (`S7 connected (CPU S71500, PDU 240 bytes)`). `DriverHealth` has no diagnostics bag in this
|
||||
codebase, so the value is not yet on the `/hosts` health snapshot — adding one is a Core change.
|
||||
|
||||
## Capability surface
|
||||
|
||||
`S7Driver : IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe`
|
||||
|
||||
+24
-1
@@ -135,6 +135,29 @@ found against a real TwinCAT 2 PLC:
|
||||
registers, and the probe-tick retry re-checks it after a PLC program change.
|
||||
Reads go through the Beckhoff symbol cache, which converts numeric values by
|
||||
the PLC's own type, so only the string-kind rule applies there.
|
||||
- **IEC temporal types publish native values (#306, behaviour change).** `TIME` and
|
||||
`TOD` (TIME_OF_DAY) publish **`Double` milliseconds** (`DriverDataType.Float64`);
|
||||
`DATE` and `DT` (DATE_AND_TIME) publish a **UTC `DateTime`** decoded from the
|
||||
UDINT seconds-since-1970 counter (`DriverDataType.DateTime`). They used to
|
||||
publish the raw counter as `UInt32`. Reads, notifications, array elements and
|
||||
browse/discovery all agree (`TwinCATTemporalCodec`). No new `DriverDataType`
|
||||
member was added (`Duration` would touch every switch in Core, the node
|
||||
manager, historian mapper and editors); a duration is a Double ms, which is
|
||||
what OPC UA `Duration` is on the wire. `LTIME`/`LDT` are not in the driver's
|
||||
type surface and are still unsupported. **Writes** accept a `Double` ms /
|
||||
`TimeSpan` (TIME, TOD) or a `DateTime` (DATE — truncated to a whole day — and
|
||||
DT); negative, NaN, pre-1970 or > UDINT values are refused `BadOutOfRange`, and
|
||||
a duration written to a date (or vice versa) `BadTypeMismatch`.
|
||||
**Existing tags:** a tag authored with `dataType: "Time"` (etc.) now surfaces
|
||||
the decoded type — re-browse/redeploy to pick up the new node type. A tag
|
||||
authored `UDInt` against a TIME/DATE symbol is unchanged: it still reads and
|
||||
writes the raw 32-bit counter (the guard tolerates the same-width divergence).
|
||||
**Historian:** a historized tag authored `Date`/`DateTime` now publishes
|
||||
`DriverDataType.DateTime`, which continuous historization skips as non-numeric
|
||||
(it was historized as `UInt32` before) — such tags stop recording. Author them
|
||||
`UDInt` to keep historizing the raw counter. `Time`/`TimeOfDay` are now
|
||||
`Float64` and still historize. A `TimeOfDay` write of 86 400 000 ms or more
|
||||
is refused `BadOutOfRange`.
|
||||
- **Sizes come from the resolved data type, not the symbol.** On a TwinCAT 2
|
||||
target the Beckhoff library reports a dotted *member* symbol's `ByteSize` as if
|
||||
the PLC's byte count were a bit count (`.OPC_MACHINEDATA.PROCESSSTATUS`
|
||||
@@ -315,7 +338,7 @@ works:
|
||||
| `.ARRFAULTCODE` | `ARRAY [0..12] OF UINT` | UInt × 13 | `UInt16[13]` (element 3 = 32, matching `.ARRFAULTCODE[3]` scalar) |
|
||||
| `.ARRUSPOWER` | `ARRAY [1..3, 1..2] OF REAL` / `[1..2, 1..2]` | Real × 6 / 4 | `Single[6]` / `Single[4]` — changing (~1060 W on Z33733); **23/23** notifications identical to an immediate poll |
|
||||
| `P_INPUTVALUESFILTER.FBUSPWR1G1.SAMPLE` | `ARRAY [1..20] OF REAL` (member, symbol 10 B) | Real × 20, and × 5 (sub-range) | `Single[20]` changing, **14/14** identical; `Single[5]` = its first 5 |
|
||||
| `.THROTTLE_MODE_DEFAULT` | `ARRAY [0..12] OF TIME` | Time × 13 | `UInt32[13]` `[0, 10, 20, 40, …]` ms |
|
||||
| `.THROTTLE_MODE_DEFAULT` | `ARRAY [0..12] OF TIME` | Time × 13 | `Double[13]` `[0, 10, 20, 40, …]` ms (was `UInt32[13]` before #306; declare `UDInt` for the raw counter) |
|
||||
| `.ARGTYPE_IS_BIT_TYPE` | `ARRAY [0..18] OF BOOL` | Bool × 19 | `Boolean[19]` |
|
||||
| `.FORMAT_DEFAULT_PRECISION` / `.DWORD_DECADE_DIVIDER` / `.DIXTIMEALARM` | `INT` / `DWORD` / `UDINT` arrays | Int / UDInt | `Int16[12]` / `UInt32[10]` / `UInt32[101]` |
|
||||
| `.IMQN_TABLE` / `.AMSNETID` / `.ARRMODULENO` | `LREAL` / `USINT` / `BYTE` arrays | LReal / USInt / SInt | `Double[32]` / `Byte[6]` / `SByte[13]` (changing on Z33856) |
|
||||
|
||||
Reference in New Issue
Block a user