First library, its tested and working
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,203 @@
|
||||
# atMb native TCP + RTU + TCP slave — development revision 0.9
|
||||
|
||||
The IEC export and C implementation have no ModbusFB dependency. All socket/
|
||||
serial/frame processing runs in the native component. Public namespace: atMb.
|
||||
Standard and SysTime are still required for IEC timers and time measurement.
|
||||
|
||||
This is a tested host development build, not a compiled CODESYS library or a
|
||||
hardware-qualified Pi release. Full CODESYS compilation and physical RTU testing
|
||||
remain required. Online-change ownership/pointer relocation is future work.
|
||||
|
||||
## Rebuild on the Pi (required)
|
||||
|
||||
Extract this bundle on the Pi, preserving component-source and ExtensionSDK
|
||||
as siblings. Install build-essential, m4, dos2unix, make and python3 if absent.
|
||||
|
||||
```sh
|
||||
cd component-source
|
||||
make -C component SDKDIR="$(realpath ../ExtensionSDK)" clean
|
||||
make -C component SDKDIR="$(realpath ../ExtensionSDK)" all
|
||||
file component/out/libCmpAtomModbus.so
|
||||
make test
|
||||
```
|
||||
|
||||
The component registration version is now 0.3.0.0. The external atMbCall_cext
|
||||
function ABI, signature 0x709EE65B and generated API version 0.2.0.0 are unchanged.
|
||||
No regeneration of runtime interface files is required for these extra commands.
|
||||
Keep the supplied generated interface; do not regenerate blindly from a changed
|
||||
library title/version. No prebuilt Pi binary is included. A strict host compile
|
||||
of the component function used the supplied SDK headers and original real
|
||||
parameter struct; the full SDK/M4/component frame build must run on your Pi.
|
||||
|
||||
## Build the IEC library without legacy objects
|
||||
|
||||
1. Create a fresh library project saved as CmpAtomModbus.library (or a backup copy
|
||||
with the old Modbus master/slave folders removed). Import atMb-Native.export.
|
||||
A fresh project is preferred because import does not delete omitted old methods.
|
||||
2. Keep Project Information title CmpAtomModbus and default namespace atMb, as
|
||||
supplied in the export. This is one combined library, not a wrapper referencing
|
||||
a second CmpAtomModbus/atMb library. Remove those self/wrapper references.
|
||||
3. Add only Standard and SysTime references needed by this IEC code. Remove the
|
||||
ModbusFB reference. Resolve their ordinary dependencies if the IDE requests it.
|
||||
4. Add a C Implemented Library object and embed your NEWLY BUILT ARM64
|
||||
component/out/libCmpAtomModbus.so for the exact Raspberry Pi 64 SL target.
|
||||
Use CmpAtomModbus for the component name. The old TCP-only embedded object was
|
||||
deliberately omitted from this export; do not reuse its binary.
|
||||
5. Compile, save/install and update references in the Atom library/application.
|
||||
6. Perform a full download. Verify runtime loads the new component version.
|
||||
|
||||
For automatic deployment, stop using the old manually preloaded component
|
||||
before testing the embedded one: remove only its ComponentManager entry, then
|
||||
restart the runtime during maintenance. Alternatively, for a manual bench test,
|
||||
stop the runtime and replace /usr/lib/libCmpAtomModbus.so with the new binary.
|
||||
Inspect /var/log/codesyscontrol.log. No production install is performed by this kit.
|
||||
|
||||
## TCP client
|
||||
|
||||
Existing atMb.Client and ReqReadHolding/ReqReadInput/ReqReadCoil/ReqReadDiscrete/
|
||||
ReqWriteHolding/ReqWriteCoil calls remain. Supported C function codes are
|
||||
1,2,3,4,5,6,15,16. Automatic retry continues while enabled; normal Modbus
|
||||
exception replies do not force reconnects. The default retry delay is 100 ms.
|
||||
|
||||
## Native RTU
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
bus : atMb.ClientRTU;
|
||||
read : atMb.ReqReadHolding;
|
||||
registers : ARRAY[0..9] OF UINT;
|
||||
END_VAR
|
||||
```
|
||||
|
||||
```iecst
|
||||
bus(
|
||||
in_device := '/dev/ttyUSB0',
|
||||
in_baudRate := 9600,
|
||||
in_parity := 0,
|
||||
in_dataBits := 8,
|
||||
in_stopBits := 1,
|
||||
in_timeout := T#500MS,
|
||||
in_reconnectDelay := T#100MS
|
||||
);
|
||||
read(client := bus, in_unitId := 1, in_startAddr := 0,
|
||||
in_quantity := 10, in_dataOffset := 0, in_minDelay := T#100MS,
|
||||
data := registers);
|
||||
```
|
||||
|
||||
Call client/requests every scan in ONE task. One RTU client owns each serial bus;
|
||||
register different unit IDs on that same client, rather than opening multiple
|
||||
clients on the same tty. The native open uses exclusive serial access.
|
||||
|
||||
Use /dev/serial/by-id/... when it fits the 127-byte path limit, or a stable
|
||||
short udev symlink. The runtime OS user must have access to the device; check the
|
||||
actual service user and device group on your Pi. Stop other serial consumers.
|
||||
The old numeric in_port selection was removed; a numeric index is not a portable
|
||||
Linux device mapping. in_baudRate is now UDINT so 115200 fits.
|
||||
|
||||
Supported baud rates: 1200,2400,4800,9600,19200,38400,57600,115200.
|
||||
RTU uses 8 data bits; parity 0 none/1 odd/2 even; stop bits 1 or 2.
|
||||
Unit IDs 1..247 are accepted. Broadcast unit 0 is explicitly rejected because
|
||||
broadcast completion cannot be reported as an acknowledged write.
|
||||
|
||||
For automatic-direction USB RS485 adapters leave in_rs485 FALSE. TRUE requests
|
||||
Linux TIOCSRS485 with RTS during transmit; unsupported drivers report an OS error.
|
||||
No software GPIO direction control or adapter-specific configuration is included.
|
||||
|
||||
CRC, unit/function, length and write echo are validated before accepting replies.
|
||||
Faults close/reopen the tty after the configured retry delay. No raw write packet
|
||||
is replayed by the engine. The existing scheduler can issue fresh cyclic writes;
|
||||
use in_holdUncertainWrites TRUE for commands requiring an application decision.
|
||||
Delayed serial replies have no transaction ID: use suitable timeout/retry settings
|
||||
for slow devices. Serial silence is conservatively scheduled in milliseconds;
|
||||
physical adapter/baud/parity/line-turnaround tests are still necessary. PTY tests
|
||||
cannot certify electrical timing, parity generation or RS485 direction behavior.
|
||||
|
||||
out_connected on RTU means the tty is open/ready, not proof that a remote unit
|
||||
responds. Use per-request online/stale/error status. out_socketError carries the
|
||||
OS errno for tty failures too. All existing client status counters are shared.
|
||||
|
||||
## Native TCP slave
|
||||
|
||||
atMb.ModbusSlave and atMb.SlaveTCP both run the C server (no ModbusFB server).
|
||||
Map four BOOL/UINT arrays using aDiscreteInputsMemory/aCoilsMemory/
|
||||
aInputRegistersMemory/aHoldingRegistersMemory. Their actual LOWER_BOUND is their
|
||||
zero-based protocol starting address; each table is limited to 2000 items and
|
||||
must fit within addresses 0..65535. A full 65536-item table is not supported.
|
||||
|
||||
Use in_ipAddress DWORD = 0 to bind all addresses, or numeric IPv4 in human octet
|
||||
order (16#7F000001 for 127.0.0.1). port defaults to 502. The legacy interfaceName
|
||||
field is retained but must be empty; numeric bind IP replaces interface-name
|
||||
binding. Nonempty names are reported as parameter errors rather than ignored.
|
||||
|
||||
Example: `slave : atMb.ModbusSlave;` with all four arrays passed every scan:
|
||||
|
||||
```iecst
|
||||
slave(in_enable := TRUE, in_ipAddress := DWORD#0, port := 502,
|
||||
aDiscreteInputsMemory := discreteInputs,
|
||||
aCoilsMemory := coils,
|
||||
aInputRegistersMemory := inputRegisters,
|
||||
aHoldingRegistersMemory := holdingRegisters);
|
||||
```
|
||||
|
||||
FC1/2/3/4/5/6/15/16 are implemented, with exception replies for unsupported
|
||||
functions, invalid quantities or unmapped addresses. Four server contexts,
|
||||
eight simultaneous peers per server. Requests are bounded and processed in
|
||||
order per connection. A server tick accepts at most one peer and performs at
|
||||
most one nonblocking I/O action per peer per IEC call. Incomplete frames/send
|
||||
stall timeout after 5 seconds; idle peers close after 60 seconds.
|
||||
|
||||
The wrapper pushes PLC table snapshots, ticks the engine, then pulls completed
|
||||
remote writes in the same scan. C retains no PLC pointers between calls. Application
|
||||
writes between calls become the next snapshot. Arrange application order accordingly.
|
||||
The fixed server limits and scan-bounded throughput differ from the old ModbusFB
|
||||
server; load test your actual client count/polling rates. No RTU slave is provided.
|
||||
|
||||
in_enable controls listening, in_reset recreates the context, endpoint changes
|
||||
recreate it, and listener failures retry automatically after in_reconnectDelay.
|
||||
Outputs: out_running/out_error/out_errorId/out_parameterError/out_socketError,
|
||||
out_connectedClients/out_requestsReceived/out_repliesSent/out_exceptionReplies.
|
||||
|
||||
## Native lifecycle and API
|
||||
|
||||
All bridge/server contexts are fixed-pool, generation-checked and single-task.
|
||||
No RETAIN/PERSISTENT handles. release() disables client enable and closes its
|
||||
context. STOP alone does not guarantee FB_exit: disable and scan before STOP.
|
||||
New full download required; online-change safety is not claimed in this revision.
|
||||
|
||||
Own Error and ExceptionCodes DUTs replace the ModbusFB enums. Error retains the
|
||||
named compatibility labels used by request statistics. Legacy dynamically
|
||||
allocated request methods are removed or become native recreateRequest BOOL.
|
||||
Old allocation-alarm outputs/legacy request reset fields were removed because
|
||||
these contexts use fixed native pools. A pool-full result is native error 257.
|
||||
|
||||
Command 20: open RTU. data contains one device-path ASCII byte per WORD, quantity
|
||||
is path length; ipAddress=baud; unitId=parity; functionCode=bits;
|
||||
startAddress=stop bits; connectTimeoutMs=kernel RS485 flag (0/1).
|
||||
The existing commands 2..11 configure/tick/submit/close this client normally.
|
||||
Commands 30..36: server open/close/enable/disable/tick/push/pull. Push/pull use
|
||||
functionCode as table 0 coils/1 discrete/2 holding/3 input, startAddress as base
|
||||
and quantity as length. Server handles use distinct low-byte tags from clients.
|
||||
Server status indices: 0 handle,1 running,2 error,3 result,4 OS errno,5 peers,
|
||||
6 requests,7 replies,8 exception replies. Client status layout is unchanged.
|
||||
|
||||
## Tests and remaining checks
|
||||
|
||||
Run make test on Linux. Includes existing nine TCP scenarios directly and through
|
||||
the bridge, pool/handle tests, PTY RTU read/fragment/CRC/timeout/recovery/exception/
|
||||
register-write/coil-write/invalid-write-echo recovery tests and native slave FC1/2/3/4/5/6/15/16, nonzero mapping,
|
||||
exception/listener-recovery/stale-handle tests. All passed on the development host.
|
||||
The CODESYS XML has no ModbusFB identifiers and passes structural checks. It has
|
||||
not been compiled in the IDE; hardware Pi/serial and embedded deployment tests
|
||||
remain. Do not interpret host tests as target or online-change qualification.
|
||||
|
||||
|
||||
v0.9.1 export correction
|
||||
------------------------
|
||||
Restores ClientBase public diagnostics: msgLastSecondCnt, msgLastSecondTime,
|
||||
numMsgSent, numMsgReply, allRequestsOnline and reqStats. The generator now
|
||||
checks these declarations and balanced VAR sections. Replace matching objects
|
||||
when importing the corrected export. C sources are unchanged from v0.9; this
|
||||
correction alone does not require rebuilding an already installed v0.9 component.
|
||||
The full native migration still requires the C build and embedding described above.
|
||||
Validated XML and declaration checks locally; CODESYS compilation must be checked
|
||||
in the IDE.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Documentation validation
|
||||
|
||||
Documented 95 code objects.
|
||||
|
||||
- All interface executable tokens unchanged after removing comments/whitespace.
|
||||
- All XML content outside Interface text identical after parse/serialization.
|
||||
- Implementations, GUIDs, native payloads, project information and references unchanged.
|
||||
- Documented XML parses successfully.
|
||||
- No CODESYS IDE compile performed; this is a comments-only change.
|
||||
|
||||
Input SHA256: `5a07da11853e955633a977bdd59a7d1f8f292c594c2116bc220bd63c8edc9329`
|
||||
@@ -0,0 +1,216 @@
|
||||
# atMb usage and behavior
|
||||
|
||||
This guide describes the supplied working export, including its renamed `ClientTCP`. The documented export changes comments only and retains the embedded C Implemented Library object. Import with replacement of matching objects into your existing library project. No C rebuild is needed for these documentation changes.
|
||||
|
||||
## Choosing the block
|
||||
|
||||
| Application | Block | Data |
|
||||
|---|---|---|
|
||||
| TCP master/client | `atMb.ClientTCP` | IPv4 byte array and TCP port |
|
||||
| RTU master/client | `atMb.ClientRTU` | Linux serial device and serial settings |
|
||||
| Read coils, FC1 | `atMb.ReqReadCoil` | BOOL array |
|
||||
| Read discrete inputs, FC2 | `atMb.ReqReadDiscrete` | BOOL array |
|
||||
| Read holding registers, FC3 | `atMb.ReqReadHolding` | UINT array |
|
||||
| Read input registers, FC4 | `atMb.ReqReadInput` | UINT array |
|
||||
| Write coils, FC15 | `atMb.ReqWriteCoil` | BOOL array |
|
||||
| Write holding registers, FC16 | `atMb.ReqWriteHolding` | UINT array |
|
||||
| TCP slave/server | `atMb.SlaveTCP` or `atMb.ModbusSlave` | Four memory arrays |
|
||||
|
||||
The two slave blocks have identical code/interfaces. They do not provide RTU slave operation. `ClientBase` and `RequestBase` are abstract and are not instantiated directly.
|
||||
|
||||
## TCP read example
|
||||
|
||||
Declarations:
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
mbClient : atMb.ClientTCP;
|
||||
readRegisters : atMb.ReqReadHolding;
|
||||
registers : ARRAY[0..9] OF UINT;
|
||||
dataUsable : BOOL;
|
||||
END_VAR
|
||||
```
|
||||
|
||||
Cyclic body:
|
||||
|
||||
```iecst
|
||||
mbClient(
|
||||
in_ipAddress := atMb.To_IPAddress(192,168,50,10),
|
||||
in_port := 502,
|
||||
in_enable := TRUE,
|
||||
in_connectTimeout := T#2S,
|
||||
in_reconnectDelay := T#100MS,
|
||||
in_timeout := T#500MS);
|
||||
|
||||
readRegisters(
|
||||
client := mbClient,
|
||||
in_unitId := 1,
|
||||
in_startAddr := 0,
|
||||
in_quantity := 10,
|
||||
in_dataOffset := 0,
|
||||
in_minDelay := T#100MS,
|
||||
in_staleAfter := T#2S,
|
||||
data := registers);
|
||||
|
||||
dataUsable := readRegisters.out_validData AND NOT readRegisters.out_stale;
|
||||
```
|
||||
|
||||
Call the client and each request every scan from one IEC task. On the first scan the client initializes its native mode, and the request registers afterwards; polling can begin on a subsequent scan. The request body binds the current array and usable count; the client dispatches the request method. No separate execute trigger is required for cyclic polling.
|
||||
|
||||
Keep instances and arrays stable. Avoid temporarily allocated request objects or sharing native contexts across tasks. Multiple requests can share a client; they are serialized. The supplied C implementation has 16 client contexts shared between TCP/RTU, 100 registrations per IEC client, and one in-flight transaction per client.
|
||||
|
||||
## Addresses and lengths
|
||||
|
||||
Protocol addresses are zero-based: a manual describing its first holding register as 40001 often maps to `in_startAddr := 0`. Confirm the device's mapping; do not pass 40001 automatically.
|
||||
|
||||
`in_dataOffset` is the actual array index. With `ARRAY[1..10]`, use 1 to access the first element. `in_quantity` is clamped to the remaining array capacity rather than always rejected. Check `stats.reqLength` for the effective count.
|
||||
|
||||
Limits per transaction: FC1/2 2000 bits, FC3/4 125 registers, FC15 1968 coils, FC16 123 registers. A one-element write still uses FC15/16 in the public write blocks. Register values are unsigned 16-bit words; converting floats, signed values or multi-register values and selecting word order are application responsibilities.
|
||||
|
||||
## RTU example
|
||||
|
||||
Use the same requests with this client:
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
serialClient : atMb.ClientRTU;
|
||||
readRegisters : atMb.ReqReadHolding;
|
||||
registers : ARRAY[0..9] OF UINT;
|
||||
END_VAR
|
||||
```
|
||||
|
||||
```iecst
|
||||
serialClient(
|
||||
in_device := '/dev/ttyUSB0',
|
||||
in_baudRate := 9600,
|
||||
in_parity := 0,
|
||||
in_dataBits := 8,
|
||||
in_stopBits := 1,
|
||||
in_rs485 := FALSE,
|
||||
in_enable := TRUE,
|
||||
in_timeout := T#500MS,
|
||||
in_reconnectDelay := T#100MS);
|
||||
|
||||
readRegisters(client := serialClient, in_unitId := 1,
|
||||
in_startAddr := 0, in_quantity := 10, in_dataOffset := 0,
|
||||
data := registers);
|
||||
```
|
||||
|
||||
Set RTU unit ID explicitly to 1..247; the inherited request default is 0 and broadcast is unsupported. The Linux runtime account needs serial device permissions. Use a stable device path where available. `in_rs485` requests kernel-managed direction control and requires driver support; USB adapters that control direction automatically generally use FALSE. Serial-port configuration must match the device.
|
||||
|
||||
The supplied native source supports 1200/2400/4800/9600/19200/38400/57600/115200 baud, 8 bits, parity none/odd/even and one/two stop bits. Timing uses nonblocking I/O and scan-driven millisecond deadlines; physical adapter timing must be verified on the target hardware.
|
||||
|
||||
## Polling and priority
|
||||
|
||||
Requests become active when their effective length is nonzero. They register automatically and poll while the client is enabled. `in_quantity := 0` makes a registered request inactive; it does not remove it from the registry.
|
||||
|
||||
`in_minDelay` controls eligibility by age since the previous start. It is not a guaranteed polling period. Lowest `in_priorityCurrent` wins, and age breaks equal-priority ties. On success, current priority is restored to `in_prioritySetpoint`. On error it becomes `10 * MIN(consecutive errors,3)^2`: 10, 40, then 90. The current scheduler does not require priority to reach zero before submitting. Avoid overwriting current priority every scan unless that is intended.
|
||||
|
||||
## Writes and uncertainty
|
||||
|
||||
Write arrays are copied at submission, so changes made afterwards affect a later transaction. An active write request is cyclic, just like a read; `writeNow()` is a priority/hold-release operation, not a single-shot write API.
|
||||
|
||||
When a connection fails after a write may have been transmitted, `out_writeUncertain` means the target could already have applied it. With `in_holdUncertainWrites := FALSE`, normal scheduling can retry. With TRUE, that write request is held until its `writeNow()` is called. Other eligible requests can continue.
|
||||
|
||||
```iecst
|
||||
// On an application-controlled event, after preparing the source array:
|
||||
writeRegisters.writeNow();
|
||||
```
|
||||
|
||||
This clears the hold/uncertain output and sets priority 0. The client must still be enabled/connected, the request active and its minimum-delay condition satisfied. Confirm target behavior before retrying commands whose effects should occur only once.
|
||||
|
||||
## Timeouts and reconnects
|
||||
|
||||
Reply timeout precedence:
|
||||
|
||||
1. Request `in_timeout`: SYSTIME numeric milliseconds, when nonzero.
|
||||
2. Client `in_timeout`: TIME, when nonzero.
|
||||
3. Calculated timeout using client dataRate.
|
||||
4. A one-second fallback if dataRate is zero.
|
||||
|
||||
The internal property calculates microseconds; submission rounds up to milliseconds with a minimum of 1 ms. TCP defaults its dataRate hint to 105200; the calculated timeout is approximately 38 ms. RTU uses its baud rate; at 9600 the formula gives approximately 411 ms. Explicit client/request timeouts make behavior easier to control.
|
||||
|
||||
`in_connectTimeout` is the TCP connection deadline, separate from reply timeout. `in_reconnectDelay` defaults to 100 ms. It is the recovery wait after detecting failure, not a guarantee of recovery within 100 ms: scan time, connection timeout, reply timeout, OS behavior and target readiness also matter.
|
||||
|
||||
Endpoint/configuration changes release and recreate the native context. `recreateClient()` forces this process; it does not permanently disable the client. `release()` also sets `in_enable := FALSE`. To restart, set TRUE and continue scanning. Supplying TRUE as a call parameter every scan will override that disabled input on the next call.
|
||||
|
||||
## Data quality and statistics
|
||||
|
||||
| Diagnostic | Meaning |
|
||||
|---|---|
|
||||
| Client `out_connected` | Native transport is ready/sending/receiving |
|
||||
| Request `stats.online` | Valid response evidence, including Modbus exceptions |
|
||||
| Request `out_validData` | A successful read has populated data since invalidation |
|
||||
| Request `out_stale` | No valid data, or last successful read is older than threshold |
|
||||
| Request `out_dataAgeMs` | Age of successful read data |
|
||||
| Request `out_nativeError` | Last native request result |
|
||||
| Request `stats.lastErrorId` | Mapped IEC error classification |
|
||||
| Request `stats.lastExceptionCode` | Modbus exception or local statistics classification |
|
||||
| `out_socketError` | OS errno snapshot for socket/serial failure |
|
||||
|
||||
These represent different conditions. Exception replies can make communication online while the read fails. Read failures retain old values and may retain `out_validData`; use freshness as well. Releasing the client marks cached data invalid but does not erase arrays. Some completion paths return before updating age, so freshness outputs can refresh on the following scan.
|
||||
|
||||
`reqDone`/`reqError` are internal completion flags; the light outputs use a 100 ms off-delay for monitoring. `timeoutCnt` currently counts non-exception errors that statistics classify as TIMEOUT, not only native reply deadlines. Prefer native result 6 for a precise reply timeout.
|
||||
|
||||
Client `resetStats()` resets IEC send/reply/window counters and each request's four success/error counters. Request `stats.resetStats()` resets only `doneCnt`, `doneCntRst`, `errorCnt`, `errorCntRst`. Durations, timestamps, offline count, timeout count and native reconnect/errno history are retained. `in_resetStats` is consumed and cleared by the client; providing TRUE every scan keeps resetting it.
|
||||
|
||||
## TCP slave example
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
server : atMb.SlaveTCP;
|
||||
coils : ARRAY[0..15] OF BOOL;
|
||||
discreteInputs : ARRAY[0..15] OF BOOL;
|
||||
holding : ARRAY[0..31] OF UINT;
|
||||
inputs : ARRAY[0..31] OF UINT;
|
||||
END_VAR
|
||||
```
|
||||
|
||||
```iecst
|
||||
// Publish application inputs before the server call.
|
||||
inputs[0] := 123;
|
||||
server(in_enable := TRUE, in_ipAddress := DWORD#0, port := 1502,
|
||||
aCoilsMemory := coils,
|
||||
aDiscreteInputsMemory := discreteInputs,
|
||||
aHoldingRegistersMemory := holding,
|
||||
aInputRegistersMemory := inputs);
|
||||
// Consume network-written coils/holding values after this call.
|
||||
```
|
||||
|
||||
Port 1502 is an example; select the application's port. Keep `interfaceName` empty. `in_ipAddress := 0` binds all interfaces. A specific bind IPv4 is packed into DWORD, e.g. `DWORD#16#C0A8321F` for 192.168.50.31.
|
||||
|
||||
Each table has independent addresses. `ARRAY[100..109] OF UINT` maps addresses 100..109, not 0..9. All tables must use nonnegative bounds within 0..65535 and at most 2000 elements. The current API requires four arrays; a small placeholder can represent an otherwise unused table.
|
||||
|
||||
The wrapper snapshots all four application tables into native memory, processes bounded network work, then copies the tables back. Network writes affect coils and holding registers. Avoid overwriting externally controlled values unconditionally in application code each scan unless that override is intentional. The supplied C engine supports four server contexts and eight peers per server. Server contexts are separate from the 16-client pool.
|
||||
|
||||
`out_running` means the listener is bound; `out_connectedClients` indicates peers. `in_reset` is consumed and cleared; a constant TRUE recreates the server every scan. Listener bind failures retry while enabled. Counters reset with server context recreation. Some early-return paths leave prior outputs unchanged; interpret diagnostic outputs together.
|
||||
|
||||
## Native result codes and client states
|
||||
|
||||
| Result | Meaning |
|
||||
|---|---|
|
||||
| 0 | Success |
|
||||
| 1 | Invalid argument |
|
||||
| 2 | Native client busy |
|
||||
| 3 | Client offline/not ready |
|
||||
| 4 | Socket/serial OS error |
|
||||
| 5 | TCP connection deadline |
|
||||
| 6 | Reply deadline |
|
||||
| 7 | Peer closed |
|
||||
| 8 | Invalid protocol/frame/reply |
|
||||
| 9 | Modbus exception response |
|
||||
| 10 | Cancelled |
|
||||
| 256 | Invalid/stale native handle |
|
||||
| 257 | Native pool full |
|
||||
| 258 | Insufficient/invalid buffer capacity |
|
||||
| 259 | Unknown command |
|
||||
|
||||
These are distinct from the sparse IEC `Error` enum. IEC mapping combines 4/7 into `TcpSocketError`, 1/258 into `RequestParameterError`; other unmapped results become `InternalError`. TCP names also classify RTU failures.
|
||||
|
||||
States: 0 disabled, 1 retry wait, 2 connecting, 3 ready, 4 sending, 5 receiving.
|
||||
|
||||
## Maintenance and scope
|
||||
|
||||
The documented export preserves the working implementations, metadata and embedded payloads. Internal native fields are sometimes exposed as VAR_INPUT for compatibility; leave them under library control. Several legacy BOOL methods do not assign a return value; the API reference identifies them so callers rely on their side effects.
|
||||
|
||||
Lifecycle hooks exist, but reliable online-change ownership and pointer relocation remain future work. Documentation adds no behavior changes or online-change guarantee. Use the API reference for every method/property, including internal accessors.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Snapshot validation
|
||||
|
||||
- Export generator completed: 99 objects, no ModbusFB references.
|
||||
- Public diagnostics and balanced VAR declaration checks passed.
|
||||
- Strict host C compilation passed.
|
||||
- Bridge pool/stale handle/configuration/shutdown tests passed.
|
||||
- Nine TCP scenarios passed directly and through the native bridge.
|
||||
- RTU pseudo-terminal tests passed, including CRC/timeout recovery, partial frames,
|
||||
writes, exceptions and invalid write echo recovery.
|
||||
- TCP slave FC 1/2/3/4/5/6/15/16, exception, nonzero base, listener recovery
|
||||
and stale handle tests passed.
|
||||
|
||||
No CODESYS IDE compile, full ARM64 SDK build, physical serial hardware test,
|
||||
or online-change qualification was performed for this repository package.
|
||||
@@ -0,0 +1,194 @@
|
||||
from pathlib import Path
|
||||
import xml.etree.ElementTree as E
|
||||
import re,hashlib,json,zipfile
|
||||
B=Path(__file__).resolve().parent
|
||||
src=B.parent/'reference/Working-CmpAtomModbus.export'
|
||||
r=E.parse(src).getroot();entries=list(r.find('.//List2[@Name="EntryList"]'))
|
||||
def meta(e,n):return e.find('./Single[@Name="MetaObject"]/Single[@Name="'+n+'"]')
|
||||
names={meta(e,'Guid').text:meta(e,'Name').text for e in entries}
|
||||
def path(e):
|
||||
n=meta(e,'Name').text;p=e.find('./Single[@Name="ParentSVNodeGuid"]').text
|
||||
return names.get(p,''),n
|
||||
def blob(e,p):return e.find('./Single[@Name="Object"]/Single[@Name="'+p+'"]//Single[@Name="TextBlobForSerialisation"]')
|
||||
summary={
|
||||
'ClientBase':'Abstract shared client scheduler and native transport owner. Use ClientTCP or ClientRTU. Holds up to 100 registered requests and runs one transaction at a time. Call the concrete client once per scan, from the same task as its requests.',
|
||||
'ClientTCP':'Modbus TCP client with automatic retry after connection or transaction failure. Call every scan. Endpoint or timeout/retry configuration changes close the current context and reopen it on subsequent scans. Requests share this connection and execute sequentially.',
|
||||
'ClientRTU':'Modbus RTU client using a Linux serial device. Call every scan. Supports 8 data bits, parity 0 none/1 odd/2 even, and 1 or 2 stop bits. Uses unit IDs 1..247; broadcast 0 is unsupported. Serial-setting changes recreate the context. Automatic recovery proceeds while enabled and scanned.',
|
||||
'RequestBase':'Abstract request binding, scheduling and diagnostics. Derived requests register automatically when called with a valid initialized client. Call each instance every scan with stable array storage. Quantity zero disables this request. Actual length is MIN(in_quantity, remaining array capacity).',
|
||||
'RequestStats':'Request counters, completion properties, online/offline tracking and 100 ms indicator lights. Normally maintained by RequestBase. Online means a valid response was received, including a Modbus exception; it does not guarantee successful data. Several fields are declared VAR_INPUT for compatibility but are maintained internally.',
|
||||
'To_IPAddress':'Returns the four supplied bytes as ARRAY[0..3] OF BYTE. Example: atMb.To_IPAddress(192,168,50,10). This is the ClientTCP address format; the slave bind address instead uses a packed DWORD.',
|
||||
'ModbusSlave':'Native Modbus TCP server. Same implementation and interface as SlaveTCP. Call once per scan with all four memory arrays. Array bounds are protocol addresses; each table is limited to 2000 elements and addresses 0..65535. Pushes application memory, performs bounded network processing, then copies native memory back. This is TCP only, not an RTU slave.',
|
||||
'SlaveTCP':'Native Modbus TCP server with four separate memory tables. Same implementation and interface as ModbusSlave. FC 1/2/3/4 read the tables; FC 5/6/15/16 write coils/holding registers. Array lower bounds define Modbus addresses. Call every scan and retain array storage. This is TCP only.',
|
||||
'ClientStatus':'Aggregate request status used by the RTU monitoring property: noneOk=0, someOk=1, allOk=2. This describes request online flags, not the physical connection. The current toString getter compares onlineCnt with all registered requests and reports allOk for an empty registry.',
|
||||
'Error':'IEC error classification. TCP-named socket/timeout members are also used for RTU failures. For the precise native reason inspect out_nativeError/out_lastNativeError; for an OS failure inspect out_socketError.',
|
||||
'ExceptionCodes':'Modbus exception response values plus local TIMEOUT=-1. A valid Modbus exception proves communication, but the requested operation failed. In current statistics, non-exception errors can also be classified as TIMEOUT; use native diagnostics to distinguish their actual cause.',
|
||||
'atMbCall_cext':'External C ABI entry point used by the wrappers. Application code should normally call the function blocks. Do not change parameter order/types or external implementation settings. Pointers are accessed synchronously by C; provide distinct valid data/status buffers and truthful capacities. Client status requires 24 UDINT elements. All calls must be owned by one IEC task.',
|
||||
'addRequest':'Register a non-null RequestBase pointer once. Duplicate pointers are ignored. Capacity is 100 requests per client; overflow sets out_registrationFull. No removal API is provided. Normally called automatically by the request FB.',
|
||||
'nativeCall':'Internal adapter to atMbCall_cext. Command 20 packs the ASCII serial path into WORD storage and passes serial settings in ABI fields; other commands use normal client arguments and status buffers. Return is the native command result, 0 for success.',
|
||||
'nativeCycle':'Internal per-scan engine: acquire context, tick nonblocking I/O, update diagnostics, copy completed read data, process registered requests, acknowledge completion, select the next request, and publish statistics. Invalid native handles are discarded and reopened on a later scan.',
|
||||
'nativeEnsureContext':'Acquire/configure/enable a native context if absent. Returns TRUE when a handle exists or initialization succeeds. A handle does not mean connected; check out_connected. Failure records a native error and closes partially initialized contexts.',
|
||||
'nativeRelease':'Close the native context, complete any active request as cancelled, clear selection, set registered requests offline and invalidate cached read data. Returns TRUE. It does not set in_enable FALSE; an enabled scanned client can reopen.',
|
||||
'nativeSelectNextRequest':'Select an active, unheld request that meets in_minDelay and was not just completed this scan. Lowest in_priorityCurrent wins; oldest sinceLastExec breaks ties. Positive priorities are decremented when the best selected priority is positive. The selected request is submitted immediately; zero priority is not a prerequisite for execution.',
|
||||
'nativeUpdateDiagnostics':'Publish native connection state, last error, errno and reconnect count. States 3..5 count as connected. A connected-to-disconnected transition marks registered requests offline.',
|
||||
'nativeUpdateStatistics':'Publish IEC send/reply counters, registered statistics pointers and active-request aggregate online flags. Every elapsed window of at least 1000 ms publishes submitted-request count and successful transaction-duration sum, then restarts the window.',
|
||||
'processAllRequests':'Internal loop over registered non-null requests, dispatching processRequest. Returns immediately for an empty or invalid registry. Usually invoked by nativeCycle rather than application code.',
|
||||
'recreateClient':'Close/release the current context so the next enabled scan creates a fresh one with current settings. Cancels an active transaction and invalidates cached read data. Abstract on ClientBase; implemented by both concrete clients.',
|
||||
'release':'Stop this client by setting in_enable FALSE, then release its native context. Returns the release result. To resume, set in_enable TRUE and keep calling the client. A call that supplies in_enable := TRUE every scan will re-enable it.',
|
||||
'setAllRequestsOffline':'Internal helper marking every registered request offline via its statistics FB. Returns TRUE after processing a nonempty valid registry; early return does not explicitly assign the return value.',
|
||||
'setupClient':'Returns whether nativeHandle is nonzero. It does not create a context or guarantee transport connectivity. Normal FB scanning performs initialization.',
|
||||
'activeIndex':'Get the current active request registry index, or -1 when idle. Setting a valid index sets that request stats.execute TRUE; it does not bypass native scheduler selection or directly submit a transaction.',
|
||||
'activeReqCnt':'Private count of registered requests whose stats.active is TRUE. Zero quantity/usable length requests are inactive.',
|
||||
'allRequestsComplete':'Private legacy completion check. TRUE only if none of the registered requests has execute, reqDone, reqBusy or reqError set. Empty registry returns FALSE. This is not a network completion pulse.',
|
||||
'onlineCnt':'Count registered requests with stats.online TRUE. Includes any registered request with that flag; used for aggregate reporting.',
|
||||
'requestExecuting':'Private property returning activeIndex <> -1. Represents selected/active request state maintained by the scheduler.',
|
||||
'toString':'ClientRTU monitoring property returning ClientStatus based on onlineCnt versus requestCnt. Reports allOk for an empty registry. RequestStats also has an internal STRING field of this name; these are different values.',
|
||||
'busy':'Assign stats.reqBusy from input in. Internal request helper. Although declared BOOL, its implementation does not assign a return value; use the side effect rather than the return.',
|
||||
'done':'Set stats.reqDone. When in is TRUE, restore in_priorityCurrent to in_prioritySetpoint for the next scheduler cycle. Successful completion counters are maintained by the stats property setter.',
|
||||
'err':'Set stats.reqError and, on TRUE, record errorId/exceptionCode. Set next priority to 10 * MIN(consecutive errors,3)^2. Lower numeric priority wins, so this backs off repeated failures relative to lower-priority-number requests.',
|
||||
'invalidateNativeData':'Mark cached data invalid and stale. Does not erase the application array. Called during client release; applications should gate use of cached values with validity and age flags.',
|
||||
'nativeFinish':'Complete a request from native result. Map errors, copy errno/write uncertainty, clear busy, update response/counters and successful-read freshness. Modbus exceptions count as replies and communication-online evidence. If enabled, uncertain writes are held pending writeNow().',
|
||||
'nativeProcess':'Shared request processing called by derived processRequest. Completes native results or submits a selected request, sets its timeout and records busy/send counters. Updates data age and stale flags during ordinary processing. Completion branches can return before age refresh, so allow a subsequent scan for freshness diagnostics.',
|
||||
'replyTimeout':'Protected timeout property expressed internally in microseconds. Request in_timeout is milliseconds and takes precedence, then client TIME in_timeout, then a dataRate formula, then 1 second fallback. Submission rounds up to milliseconds and clamps to at least 1 ms.',
|
||||
'setClientOffline':'Clear nativeResponseSeen and consecutive-success count, force online FALSE/offline TRUE. Does not clear data arrays. Declared BOOL but does not assign a return value.',
|
||||
'setToString':'Private statistics helper formatting OK:<doneCnt>/Er:<errorCnt> into STRING(20). Declared BOOL but does not assign a return value.',
|
||||
'execute':'Statistics execution property. Setting TRUE starts the execution state only when reqDone and reqError are FALSE; otherwise the setter clears execution. Usually controlled by the scheduler.',
|
||||
'reqBusy':'Get/set the underlying request-busy flag. Normally managed by the native wrapper. The scan-held reqBusyLight provides a more visible monitoring indicator.',
|
||||
'reqDone':'Get/set completion flag. When TRUE during execution, record duration, increment total/consecutive successes, clear consecutive errors and last error, clear execution and update the monitor string. Do not repeatedly drive this manually.',
|
||||
'reqError':'Get/set error flag. When TRUE during execution, increment total/consecutive errors, clear consecutive successes and execution. If lastExceptionCode is success, increment timeoutCnt and set TIMEOUT even for some non-timeout native errors.',
|
||||
'recreateRequest':'Clear this request nativeInFlight. If it is the client active request, recreate the whole client connection first. Returns TRUE. May affect other requests sharing the same connection; this is not an independent transport.',
|
||||
'writeNow':'Clear uncertain-write hold and out_writeUncertain, then set in_priorityCurrent to 0. Returns TRUE. Requests a prompt opportunity at the next scheduler selection; does not guarantee transmission in this call and does not bypass connection/minimum-delay requirements.',
|
||||
'serverCall':'Private adapter for native server commands. Uses separate server status and WORD staging buffers. Returns the command result; 0 means command accepted, not necessarily that the listener has bound.',
|
||||
'FB_exit':'CODESYS lifecycle hook releasing owned native resources. Applications should use release/recreateClient rather than call lifecycle hooks manually. Server hooks close the server context; the client hook calls nativeRelease.',
|
||||
'FB_reinit':'CODESYS lifecycle hook clearing native handles and in-flight state. ClientBase also clears the request registry for re-registration; RequestBase clears execution/completion flags; servers clear their handle. These hooks alone do not establish qualified online-change support.'}
|
||||
for name,fc,kind,limit in [('ReqReadCoil',1,'BOOL coils',2000),('ReqReadDiscrete',2,'BOOL discrete inputs',2000),('ReqReadHolding',3,'UINT holding registers',125),('ReqReadInput',4,'UINT input registers',125),('ReqWriteCoil',15,'BOOL coils',1968),('ReqWriteHolding',16,'UINT holding registers',123)]:
|
||||
summary[name]=f'Modbus FC {fc}: '+('write ' if fc>=15 else 'read ')+f'{kind}. Call every scan with a client reference and data array. Protocol start address is zero-based; in_dataOffset is the actual array index. Maximum transaction length is {limit}. Usable length is clamped to array capacity. '+('Values are copied into native storage at submission. This FB uses the multiple-write FC even for one element. Use writeNow() to release a held uncertain write.' if fc>=15 else 'Only a successful validated reply updates the destination slice. Old values remain after failures; check out_validData and out_stale.')
|
||||
fields={
|
||||
'in_enable':'Enable processing; TRUE by default. FALSE releases transport. Continue scanning to refresh state.',
|
||||
'in_timeout':'Reply timeout override. ClientBase: TIME; RequestBase: SYSTIME numeric milliseconds (not microseconds). Zero selects inherited/calculated timeout.',
|
||||
'in_resetStats':'Reset request consumed and cleared by nativeCycle. Supplying TRUE on every call resets statistics every scan; pulse once.',
|
||||
'in_ipAddress':'ClientTCP: four IPv4 bytes. Slave: packed numeric DWORD, 0 binds all interfaces; e.g. DWORD#16#C0A8321F for 192.168.50.31.',
|
||||
'in_port':'Remote TCP port, default 502.', 'in_baudrate':'TCP gateway serial baud-rate hint for calculated reply timeout; 0 uses internal dataRate 105200. Does not configure a serial port.',
|
||||
'in_connectTimeout':'TCP connection-attempt deadline, default 2 s; clamped to at least 1 ms.',
|
||||
'in_reconnectDelay':'Delay before native recovery attempt, default 100 ms; clamped to at least 1 ms. Effective timing also depends on scan interval and failure detection.',
|
||||
'in_holdUncertainWrites':'FALSE allows normal retries after uncertain writes. TRUE holds affected write requests until writeNow(); read requests can continue.',
|
||||
'in_device':'Linux serial device path, default /dev/ttyUSB0, at most 127 ASCII characters. Runtime account needs access.',
|
||||
'in_baudRate':'Serial baud rate, default 9600. Supplied C source accepts 1200,2400,4800,9600,19200,38400,57600,115200.',
|
||||
'in_parity':'0 none, 1 odd, 2 even. Must match the slave.', 'in_dataBits':'Only 8 supported by supplied native implementation.', 'in_stopBits':'1 or 2; must match the slave.',
|
||||
'in_rs485':'Request Linux kernel RS485 direction control. Leave FALSE for USB adapters handling direction automatically. Requires driver support.',
|
||||
'client':'Reference to the owning ClientBase concrete instance. Initialize/call client before first registration; retain a stable instance.',
|
||||
'in_startAddr':'Zero-based Modbus protocol address, not a 40001/30001 display reference. Must fit the transaction into address space.',
|
||||
'in_quantity':'Requested item count; actual stats.reqLength is MIN(this, available destination/source capacity). Zero makes the request inactive.',
|
||||
'in_dataOffset':'Actual first index in the application array. For ARRAY[1..10], use 1 to start at the first element; not a relative offset.',
|
||||
'in_unitId':'Modbus unit/slave ID, default 0. RTU requires explicit 1..247; TCP/gateway values depend on the target.',
|
||||
'in_minDelay':'Minimum start-to-start time before scheduler eligibility, default 10 ms. Not a fixed polling period.',
|
||||
'in_prioritySetpoint':'Priority restored on success, default 50. Lower values win. Error backoff can replace current priority.',
|
||||
'in_priorityCurrent':'Mutable scheduler priority. Lower values win, equal values use age. WriteNow sets 0. Application need not maintain it each scan.',
|
||||
'in_staleAfter':'Read data age threshold, default 2 s. out_stale becomes TRUE when age is strictly greater; does not stop polling.',
|
||||
'data':'Open array: BOOL for coils/discretes; UINT for registers. Stable VAR_IN_OUT storage. Writes snapshot values at submission; reads replace only their successful slice.',
|
||||
'stats':'Per-request RequestStats instance maintained by request processing; inspect rather than overwrite.',
|
||||
'out_connected':'Transport is in ready/sending/receiving state. Does not prove a request succeeded or data is fresh.',
|
||||
'out_connectionState':'0 disabled, 1 retry wait, 2 TCP connecting, 3 ready, 4 sending, 5 receiving.',
|
||||
'out_lastNativeError':'Native last transport error; may persist after recovery. Check current connection and request diagnostics too.',
|
||||
'out_nativeError':'Result of the last completed/failed request; 0 success. Precise native code, distinct from IEC Error enum.',
|
||||
'out_socketError':'Native Linux errno snapshot as UDINT; may be historical. Used for both serial and sockets.',
|
||||
'out_reconnectCount':'Native successful reconnections after the first connection. Context recreation resets its native lifetime counters.',
|
||||
'out_registrationFull':'Sticky request registry overflow indicator; max 100 requests/client. Client reinitialization clears it.',
|
||||
'out_writeUncertain':'Write may have reached target without a confirmed response. It is not proof of failure or success. Relevant for write requests.',
|
||||
'out_validData':'TRUE after a successful read; subsequent read failures do not necessarily clear it. Client release or invalidateNativeData clears it.',
|
||||
'out_dataAgeMs':'Age since last successful read in milliseconds. Updated by normal processing; completion early returns can defer refresh by one scan.',
|
||||
'out_stale':'TRUE before valid read data or when age exceeds in_staleAfter. Array contents are retained.',
|
||||
'out_parameterError':'Array-offset validation for requests; full transaction validation can instead appear in out_nativeError/stats.lastErrorId. Slave: invalid port/interfaceName/table bounds or capacity.',
|
||||
'msgLastSecondCnt':'Submitted request count in the last completed window of at least 1000 ms, not necessarily an exact sliding second.',
|
||||
'msgLastSecondTime':'Sum of successful transaction durations in that completed window (ms); excludes failed durations.',
|
||||
'numMsgSent':'IEC accepted submission count since resetStats; not a count of physical packets.',
|
||||
'numMsgReply':'IEC successful or Modbus-exception reply count since resetStats.',
|
||||
'allRequestsOnline':'TRUE when at least one active request exists and onlineCnt equals activeReqCnt. Online can include exception replies.',
|
||||
'reqStats':'Pointers to registered request statistics by registry index, populated on client scans. Null entries are unused; validate before dereference.',
|
||||
'in_reset':'Slave one-shot recreation input. FB clears it internally; supplying TRUE every scan recreates every scan.',
|
||||
'interfaceName':'Legacy slave field. Must be empty; nonempty is rejected. Use packed in_ipAddress instead.',
|
||||
'port':'Slave listening port, default 502, must be nonzero.',
|
||||
'aCoilsMemory':'Read/write BOOL coil table. Array bounds are protocol addresses; FC1 reads, FC5/15 write.',
|
||||
'aDiscreteInputsMemory':'Read-only from network BOOL input table; FC2 reads. Application updates it.',
|
||||
'aHoldingRegistersMemory':'Read/write UINT holding table; FC3 reads, FC6/16 write.',
|
||||
'aInputRegistersMemory':'Read-only from network UINT input table; FC4 reads. Application updates it.',
|
||||
'out_running':'Native listener is bound and enabled. Does not mean a peer is connected.',
|
||||
'out_error':'Current server error flag when outputs are refreshed; some early returns leave other diagnostics unchanged.',
|
||||
'out_errorId':'IEC server error classification: parameter/internal/native socket class. Consult errno for OS detail.',
|
||||
'out_connectedClients':'Number of native TCP peers connected (max 8 per server in supplied C source).',
|
||||
'out_requestsReceived':'Native request count for current server context.',
|
||||
'out_repliesSent':'Native fully transmitted reply count for current context, including exception replies.',
|
||||
'out_exceptionReplies':'Number of Modbus exception replies for current server context.',
|
||||
'lastErrorId':'Last mapped IEC error; successful completion resets to OK.', 'lastExceptionCode':'Last Modbus exception, success=0; stats may substitute TIMEOUT=-1 for non-exception failures.',
|
||||
'lastValidDuration':'Last successful request duration (ms).','longestValidDuration':'Maximum successful duration recorded; resetStats does not clear it.',
|
||||
'interval':'Start-to-start execution interval (ms).','offlineCnt':'Count of NOT online rising edges; can include initial not-online state, not only a physical disconnect.',
|
||||
'doneCnt':'Total successful completions; resetStats clears.','errorCnt':'Total failed completions; resetStats clears.',
|
||||
'doneCntRst':'Consecutive successes; reset on error/offline/resetStats.','errorCntRst':'Consecutive errors; reset on successful completion/resetStats.',
|
||||
'sinceLastExec':'Age since last execution start (ms), capped around 60000 by the statistics clock logic.',
|
||||
'timeoutCnt':'Statistics non-exception error count; can include other native failures. Not exclusively reply timeouts; resetStats does not clear it.',
|
||||
'reqLength':'Effective transaction length; zero means inactive.', 'online':'Communication status; valid normal/exception response sets evidence. Distinct from valid data.',
|
||||
'offline':'Offline status; initialized FALSE, explicitly set on client-offline or repeated failures.', 'active':'TRUE when reqLength is nonzero.',
|
||||
'executeLight':'Execution indicator with 100 ms off-delay.','reqDoneLight':'Completion indicator with 100 ms off-delay.','reqErrorLight':'Error indicator with 100 ms off-delay.','reqBusyLight':'Busy indicator with 100 ms off-delay.',
|
||||
'executeMbFb':'Legacy retained output; no assignment in current native implementation. Do not use as a trigger.',
|
||||
'nativeResponseSeen':'Internal communication-response evidence, maintained by nativeFinish and offline handling.', 'nativeLastSuccessMs':'Internal successful-read timestamp in monotonic milliseconds.',
|
||||
'command':'Native command selector. Use wrappers in application code.', 'handle':'Generation-checked native context handle; 0 for open.',
|
||||
'ipAddress':'Packed IPv4 for TCP commands; command 20 overloads this field with RTU baud rate.',
|
||||
'unitId':'Transaction unit ID; command 20 overloads it with serial parity.', 'functionCode':'Modbus FC; command 20 uses data bits, server push/pull use table ID.',
|
||||
'startAddress':'Protocol start; command 20 uses stop bits.', 'quantity':'Item count; command 20 uses serial path byte count.',
|
||||
'pData':'WORD buffer; client reads/writes, serial ASCII path, or server table staging.', 'dataCapacity':'Available WORD elements, not bytes.',
|
||||
'pStatus':'UDINT status buffer; requires at least 24 elements.', 'statusCapacity':'Available UDINT elements, not bytes.',
|
||||
'nowMs':'Monotonic time in milliseconds; wrappers use SysTimeGetUs / 1000.', 'connectTimeoutMs':'TCP connection timeout; command 20 uses kernel RS485 flag.',
|
||||
'responseTimeoutMs':'Reply deadline in milliseconds.', 'retryDelayMs':'Native recovery wait in milliseconds.',
|
||||
'byte1':'First IPv4 octet.','byte2':'Second IPv4 octet.','byte3':'Third IPv4 octet.','byte4':'Fourth IPv4 octet.',
|
||||
'bInCopyCode':'Runtime lifecycle copy-code indicator; current implementation does not branch on it.',
|
||||
'in':'New state to assign. Normally used internally by completion helpers.', 'errorId':'Mapped IEC error supplied to the error helper.', 'exceptionCode':'Modbus exception classification supplied to the error helper.', 'result':'Native completion result; 0 success.'}
|
||||
original={}
|
||||
sections=[];count=0
|
||||
for e in entries:
|
||||
parent,name=path(e);t=blob(e,'Interface');impl=blob(e,'Implementation')
|
||||
if t is None:continue
|
||||
old=t.text or '';original[meta(e,'Guid').text]=old
|
||||
if name=='resetStats':
|
||||
desc=('Reset IEC send/reply/window counters and call each registered stats.resetStats. Does not clear native errno/reconnect counters or all per-request history. No transport recreation.' if parent=='ClientBase' else 'Reset only doneCnt, doneCntRst, errorCnt and errorCntRst. Retains durations, offlineCnt, timeoutCnt and timestamps.')
|
||||
elif name=='processRequest':desc=('Derived request dispatch: performs '+parent+' transaction using shared nativeProcess, validates copy bounds and transfers the configured slice. Called by client scheduler; application calls the request FB body to register/bind its data.' if parent!='RequestBase' else 'Abstract internal request dispatch implemented by each concrete read/write block.')
|
||||
elif name in ['Get','Set']:desc=f'{name} accessor for {parent}; see the owning property reference for side effects. Runtime/property syntax normally invokes this accessor.'
|
||||
else:desc=summary.get(name)
|
||||
assert desc,(parent,name)
|
||||
header=f'(* atMb API reference: {parent+"." if parent else ""}{name}\n{desc}\n*)\n'
|
||||
# Add comments only to declaration lines, never change code or types.
|
||||
lines=[];scope=''
|
||||
for line in old.splitlines(keepends=True):
|
||||
sm=re.match(r'\s*(VAR(?:_INPUT|_OUTPUT|_IN_OUT|_TEMP)?)(?:\s+CONSTANT)?\s*$',line)
|
||||
if sm:scope=sm.group(1)
|
||||
if re.match(r'\s*END_VAR',line):scope=''
|
||||
m=re.match(r'\s*(\w+)\s*:',line)
|
||||
if m and m.group(1) in fields:
|
||||
lines.append(' // '+fields[m.group(1)]+'\n')
|
||||
lines.append(line)
|
||||
t.text=header+''.join(lines);count+=1
|
||||
sections.append('## '+(parent+'.' if parent else '')+name+'\n\n'+desc+'\n\n```iecst\n'+old.strip()+'\n```\n\n')
|
||||
# Parameter table scoped to input/output/open-array sections.
|
||||
rows=[];scope=''
|
||||
for line in old.splitlines():
|
||||
sm=re.match(r'\s*(VAR(?:_INPUT|_OUTPUT|_IN_OUT)?)(?:\s+CONSTANT)?\s*$',line)
|
||||
if sm:scope=sm.group(1)
|
||||
if re.match(r'\s*END_VAR',line):scope=''
|
||||
m=re.match(r'\s*(\w+)\s*:\s*(.*?);',line)
|
||||
if m and scope in ['VAR_INPUT','VAR_OUTPUT','VAR_IN_OUT']:
|
||||
fn=m.group(1); explanation=fields.get(fn,'Internal wrapper state exposed for compatibility; normally leave under library control.')
|
||||
rows.append('| `'+fn+'` | '+scope+' | `'+m.group(2).replace('|','\\|')+'` | '+explanation+' |')
|
||||
if rows:sections.append('| Member | Direction | Declaration | Meaning |\n|---|---|---|---|\n'+'\n'.join(rows)+'\n\n')
|
||||
# Verify executable tokens and all non-Interface XML content unchanged.
|
||||
def without_comments(s):return re.sub(r'\s+','',re.sub(r'//[^\n]*','',re.sub(r'\(\*.*?\*\)','',s,flags=re.S)))
|
||||
for e in entries:
|
||||
guid=meta(e,'Guid').text
|
||||
if guid in original:assert without_comments(original[guid])==without_comments(blob(e,'Interface').text),path(e)
|
||||
output=B/'atMb-Documented.export';E.ElementTree(r).write(output,encoding='utf-8',xml_declaration=True)
|
||||
check=E.parse(output).getroot();orig=E.parse(src).getroot()
|
||||
for root in [check,orig]:
|
||||
for e in root.find('.//List2[@Name="EntryList"]'):
|
||||
t=blob(e,'Interface')
|
||||
if t is not None:t.text='DOCUMENTATION_ONLY'
|
||||
assert E.tostring(check)==E.tostring(orig),'Non-documentation content changed'
|
||||
(B/'API-REFERENCE.md').write_text('# atMb API reference\n\nGenerated from your working `CmpAtomModbus(1).export`. Documents every code object, including internal methods and property accessors. Runtime implementations, identifiers, declarations and embedded native payloads are preserved in the documented export; additions are comments only.\n\n'+''.join(sections))
|
||||
(B/'VALIDATION.md').write_text(f'# Documentation validation\n\nDocumented {count} code objects.\n\n- All interface executable tokens unchanged after removing comments/whitespace.\n- All XML content outside Interface text identical after parse/serialization.\n- Implementations, GUIDs, native payloads, project information and references unchanged.\n- Documented XML parses successfully.\n- No CODESYS IDE compile performed; this is a comments-only change.\n\nInput SHA256: `{hashlib.sha256(src.read_bytes()).hexdigest()}`\n')
|
||||
print('Documented',count,'objects; comment-only and native-payload preservation checks passed')
|
||||
Reference in New Issue
Block a user