Seems to be working great
This commit is contained in:
+27
-182
@@ -1,190 +1,35 @@
|
||||
# atMb 0.4 user guide
|
||||
# atMb 0.8 user guide
|
||||
|
||||
## Thread and PLC calls
|
||||
## Execution model
|
||||
|
||||
One C worker progresses all TCP/RTU client transactions and TCP slave connections.
|
||||
It owns no pointers into IEC memory: request values, replies and slave tables live
|
||||
in fixed C storage. The external call copies buffers synchronously and serializes
|
||||
state access using a priority-inheritance engine mutex. No network wait or sleep
|
||||
is performed while that mutex is held. Serial driver calls can still take time;
|
||||
this is not a hard real-time or lock-free PLC interface.
|
||||
TCP, RTU and slave transport progress synchronously when the owning client or
|
||||
slave FB is called. Keep the related client, request FBs and data arrays in one
|
||||
PLC task and call them every scan. This version intentionally has no native
|
||||
worker thread, CPU-affinity input, or scan watchdog.
|
||||
|
||||
Continue calling each client/request and slave every PLC scan. Client request
|
||||
selection is still performed by the IEC scheduler. A transaction can finish while
|
||||
the PLC is not scanning, and its result waits for the next scan; the worker does
|
||||
not independently cycle through the PLC request registry. TCP slaves respond from
|
||||
the last copied memory snapshot between scans.
|
||||
The C state machines use nonblocking sockets and fixed storage. Serial driver
|
||||
calls remain target-dependent, and a long PLC task interval can delay transport
|
||||
progress; choose request timeouts accordingly.
|
||||
|
||||
## Optional worker configuration and a separate core
|
||||
## Online change
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
commWorker : atMb.Worker;
|
||||
END_VAR
|
||||
`FB_reinit` closes the native context before CODESYS replaces IEC instance memory,
|
||||
then clears handles, active request pointers and registration arrays. Request FBs
|
||||
clear in-flight state. TCP/RTU clients and slave listeners reopen on a later PLC
|
||||
scan. Expect active requests to be cancelled and reads to remain stale until a
|
||||
validated reply arrives. Writes already transmitted are not replayed.
|
||||
|
||||
// Example only: CPU 2 must be online and permitted on this PLC.
|
||||
commWorker(in_cpu := 2, in_interval := T#1MS, in_watchdog := T#2S);
|
||||
```
|
||||
## Status and recovery
|
||||
|
||||
Use only one Worker configuration instance for the component. Clients/servers
|
||||
start the worker automatically with defaults even without this FB. Default CPU -1
|
||||
uses the thread creator's inherited affinity mask; it is not a promise of all CPUs.
|
||||
A nonnegative value pins the worker to that Linux logical CPU ID. CPU affinity does
|
||||
not reserve an exclusive core or move the PLC task: choose a CPU outside the PLC
|
||||
task's configured affinity where the runtime allows it. Changes affect this worker
|
||||
only. Failure is visible in `out_errorCode` and `out_osError`; no silent fallback.
|
||||
Client statistics aggregate all registered requests. `toString` uses
|
||||
`OK:<successful requests>/Er:<failed requests>`. Three consecutive failed request
|
||||
results are required before a request is exposed as offline; a short TCP-to-RTU
|
||||
converter reconnect therefore does not immediately mark every request offline.
|
||||
A valid response restores online immediately. Reconnect remains automatic
|
||||
through the synchronous state machine.
|
||||
|
||||
The worker uses normal OS scheduling and explicitly avoids inheriting a PLC task's
|
||||
real-time priority. Sleep interval is 1..20 ms, default 1 ms; scheduling jitter can
|
||||
make actual intervals longer. Very short reply deadlines must account for worker
|
||||
interval and transaction framing. `out_cycles` helps monitor worker progress.
|
||||
|
||||
## Scan-loss watchdog
|
||||
|
||||
Each client/server refresh call renews its own lease; another client's scans do
|
||||
not keep a stopped client alive. Default expiry is 2 s, configurable 100 ms..60 s.
|
||||
If scanning stops, the worker closes that context's connection/listener. In-flight
|
||||
requests finish as cancelled; potentially transmitted writes retain uncertainty.
|
||||
Handles/storage remain allocated for recovery. The next client/server refresh
|
||||
resumes connection attempts using the same context. Explicit release frees it.
|
||||
|
||||
Choose a watchdog longer than the worst expected PLC scan gap. This bounds how
|
||||
long a stopped PLC's server can respond with its last memory snapshot; it is not
|
||||
an immediate CODESYS application-stop hook. Component exit stops and joins the
|
||||
worker before freeing connections. Repeated thread shutdown/start is tested.
|
||||
|
||||
## TCP read
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
mb : atMb.ClientTCP;
|
||||
readHolding : atMb.ReqReadHolding;
|
||||
values : ARRAY[0..9] OF UINT;
|
||||
END_VAR
|
||||
|
||||
mb(in_ipAddress := atMb.To_IPAddress(192,168,50,10), in_port := 502,
|
||||
in_enable := TRUE, in_timeout := T#500MS, in_reconnectDelay := T#100MS);
|
||||
readHolding(client := mb, in_unitId := 1, in_startAddr := 0,
|
||||
in_quantity := 10, in_dataOffset := 0, data := values);
|
||||
```
|
||||
|
||||
Keep one IEC task as owner of the related FB instances and arrays. Call the client
|
||||
before request registration on startup; polling starts after registration. The C
|
||||
entry point is serialized but that does not make concurrent IEC FB access safe.
|
||||
TCP addresses are four BYTEs; slave bind addresses are packed DWORDs.
|
||||
|
||||
RTU uses `ClientRTU(in_device := '/dev/ttyUSB0', in_baudRate := 9600,
|
||||
in_parity := 0, in_dataBits := 8, in_stopBits := 1, in_rs485 := FALSE)` and the same
|
||||
request FBs. RTU IDs must be 1..247. The runtime account needs serial permissions.
|
||||
Kernel RS485 direction control requires a supported driver. USB automatic-direction
|
||||
adapters generally use FALSE. Supported baud rates: 1200,2400,4800,9600,19200,38400,
|
||||
57600,115200. Physical timing/driver validation remains necessary.
|
||||
|
||||
## Requests, data and priorities
|
||||
|
||||
| Block | Function | Array | Maximum quantity |
|
||||
|---|---|---|---|
|
||||
| ReqReadCoil | 1 | BOOL | 2000 |
|
||||
| ReqReadDiscrete | 2 | BOOL | 2000 |
|
||||
| ReqReadHolding | 3 | UINT | 125 |
|
||||
| ReqReadInput | 4 | UINT | 125 |
|
||||
| ReqWriteCoil | 15 | BOOL | 1968 |
|
||||
| ReqWriteHolding | 16 | UINT | 123 |
|
||||
|
||||
Protocol addresses are zero-based; `in_dataOffset` is the actual array index, not
|
||||
a relative offset. Effective length is MIN(quantity, remaining array capacity).
|
||||
Zero length disables a request and now retains its statistics history. Arrays and
|
||||
FB instances must stay valid. There are 16 shared TCP/RTU client slots, 100 IEC
|
||||
requests per client, one in-flight transaction per client and four TCP server slots.
|
||||
|
||||
Lower `in_priorityCurrent` wins; oldest start age breaks ties. A request need not
|
||||
reach zero to run. Success restores `in_prioritySetpoint`; errors use 10/40/90
|
||||
priority backoff. `in_minDelay` is start-to-start eligibility, not a fixed period.
|
||||
|
||||
Successful reads update the destination slice. Errors retain previous data; use
|
||||
`out_validData AND NOT out_stale`. Data age includes time the completed reply waited
|
||||
for a PLC scan. Client release invalidates cached data but does not erase arrays.
|
||||
An exception reply indicates communication, but does not produce valid read data.
|
||||
|
||||
Writes snapshot their source slice at submission. The worker never automatically
|
||||
replays a request. The IEC scheduler still polls write FBs cyclically. Set client
|
||||
`in_holdUncertainWrites := TRUE` to pause an uncertain write after a failure;
|
||||
`writeNow()` clears the hold and sets priority zero once the application decides
|
||||
retrying is appropriate. It does not bypass min-delay/connection conditions.
|
||||
|
||||
## Timeouts and reconnects
|
||||
|
||||
Request `in_timeout` is numeric milliseconds (SYSTIME); client `in_timeout` is TIME.
|
||||
Nonzero request timeout wins, then client timeout, then the existing baud/data-rate
|
||||
formula. TCP's calculated default is about 38 ms, so an explicit timeout is clearer.
|
||||
The C worker uses CLOCK_MONOTONIC for deadlines, independent of PLC-provided time.
|
||||
|
||||
`in_reconnectDelay` defaults to 100 ms. Recovery proceeds without PLC ticks while
|
||||
the scan lease is valid. Actual downtime includes failure detection, retry delay,
|
||||
worker scheduling and remote-device readiness. Idle TCP disconnects are detected;
|
||||
there is no zero-downtime guarantee. TCP_NODELAY reduces small-message coalescing.
|
||||
|
||||
`recreateClient()` closes/recreates a context and cancels any active request.
|
||||
`release()` also sets `in_enable := FALSE`; a subsequent call explicitly passing
|
||||
TRUE re-enables it. Closing a context clears C lifetime counters.
|
||||
|
||||
## Client statistics
|
||||
|
||||
Both clients expose `stats : ClientStats` and a public string property `toString`.
|
||||
The monitor display and `stats.toString` use `OK:<doneCnt>/Er:<errorCnt>`. For example,
|
||||
requests with 10/2 and 7/1 completions/errors show `OK:17/Er:3` on the client.
|
||||
|
||||
| Client member | Aggregation |
|
||||
|---|---|
|
||||
| doneCnt / errorCnt | Sum of registered request successes/failures |
|
||||
| sentCnt / replyCnt | Sum of accepted submissions / normal or exception replies |
|
||||
| exceptionCnt / timeoutCnt | Sum of exception replies / result 5 or 6 timeouts |
|
||||
| offlineCnt | Sum of request online-to-offline transitions |
|
||||
| requestCount | Registered requests, including disabled ones |
|
||||
| activeCount | Nonzero effective-length requests |
|
||||
| onlineCount / offlineCount / unknownCount | Active request communication state |
|
||||
| busyCount | Active requests executing |
|
||||
| staleCount | Active read requests with stale data |
|
||||
| heldWriteCount | Active write requests paused after uncertain delivery |
|
||||
| totalValidDuration | Sum of successful transaction duration totals, ms |
|
||||
| averageValidDuration | Weighted mean: totalValidDuration / doneCnt, integer ms |
|
||||
| longestValidDuration | Maximum successful duration across requests |
|
||||
| online / offline | Nonempty active set, all online / all explicitly offline |
|
||||
|
||||
Historical counters include registered inactive requests; state counts include
|
||||
only active requests. An empty client is neither online nor offline. Aggregate
|
||||
counters are ULINT. Per-request counters retain their existing types and saturate
|
||||
rather than wrap; request offline counts saturate at UINT max. Successful duration
|
||||
accumulation stops when the associated success counter saturates to keep the mean
|
||||
consistent. There is no misleading aggregate of consecutive-error streaks or
|
||||
unrelated start intervals.
|
||||
|
||||
`numMsgSent`/`numMsgReply` remain as saturated UDINT mirrors of aggregate totals.
|
||||
The one-second window fields remain scan-published counts/duration totals.
|
||||
Request `lastValidDuration` now measures C submit-to-completion, excluding the PLC
|
||||
pickup delay. `resetStats()` resets totals, duration history, timeout and offline
|
||||
counts consistently, while retaining connection state and data freshness. Resetting
|
||||
during a transaction allows its eventual completion to be counted after reset.
|
||||
|
||||
`out_errorCode` is the request result; `out_lastErrorCode` is the client's last
|
||||
engine error. OS errno is `out_socketError`. Historical last errors may persist
|
||||
after recovery. Result 260 means worker/affinity setup failed. A non-timeout failure
|
||||
no longer increments timeoutCnt. See the API reference for all declarations.
|
||||
|
||||
## TCP slave memory exchange
|
||||
|
||||
`SlaveTCP` and `ModbusSlave` provide the same TCP server behavior. Supply four arrays:
|
||||
`aCoilsMemory`, `aDiscreteInputsMemory`, `aHoldingRegistersMemory`, `aInputRegistersMemory`.
|
||||
Their lower/upper bounds are Modbus addresses within 0..65535; max 2000 per table.
|
||||
FC1/2/3/4 read; FC5/6/15/16 write coils/holding registers. Bind IP 0 means all
|
||||
interfaces; interfaceName must remain empty. Up to eight peers per server.
|
||||
|
||||
Remote changes are marked dirty in C. An old PLC push cannot overwrite dirty
|
||||
values before pull acknowledges them. Multiple remote writes before a scan expose
|
||||
the latest value, not an event history. After pull, a later application push may
|
||||
intentionally override those values. Other tasks must not race the array exchange.
|
||||
Table bounds/length changes require `in_reset := TRUE` to recreate the server;
|
||||
a changed mapping is rejected rather than silently discarding pending writes.
|
||||
|
||||
Online-change pointer ownership and physical hardware certification remain future
|
||||
work. This release needs a full download and on-target validation.
|
||||
For TCP-to-RTU converters, set `ClientTCP.in_baudrate` to the converter's
|
||||
serial baud rate. When no explicit request or client timeout is supplied, the
|
||||
library estimates timeout from the request/response frame size and baud rate,
|
||||
then adds turnaround and device-processing margin. Direct TCP clients should
|
||||
leave `in_baudrate` at zero and use the normal fixed TCP timeout.
|
||||
|
||||
Reference in New Issue
Block a user