Threaded version 0.7
This commit is contained in:
+504
-524
File diff suppressed because it is too large
Load Diff
+45
-52
@@ -1,68 +1,61 @@
|
||||
# Build and deployment
|
||||
# Build and upgrade to atMb 0.6
|
||||
|
||||
## Build native C
|
||||
## Build on ARM64 Linux
|
||||
|
||||
From the repository root on the target Linux machine:
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
sudo apt install build-essential m4 dos2unix python3
|
||||
make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" clean
|
||||
make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" all
|
||||
file component-source/component/out/libCmpAtomModbus.so
|
||||
make -C component-source test
|
||||
mkdir -p artifacts/arm64
|
||||
cp component-source/component/out/libCmpAtomModbus.so artifacts/arm64/
|
||||
```
|
||||
|
||||
`component-source/component/` is the active SDK build directory. Its
|
||||
`CmpAtomModbus.c` includes the protocol source files as one translation unit.
|
||||
The removed `generated/` directory contained original unimplemented stubs;
|
||||
it was not used by this makefile. Preserve the active `CmpAtomModbusItf.m4` and
|
||||
`CmpFrame.c`; they define ABI registration and cleanup.
|
||||
Run these commands on an ARM64 build machine. On an x86 laptop, ordinary gcc
|
||||
builds x86 output; use the appropriate cross-compiler instead.
|
||||
|
||||
The makefile registers component version 0.3.0.0. The supplied external
|
||||
`atMbCall_cext` API retains signature 0x709EE65B and API version 0.2.0.0.
|
||||
Do not change the external parameter layout or regenerate it just because the
|
||||
library title/version was edited. Full SDK/M4 builds must be checked on the target.
|
||||
## Cross-compile ARMv7 hard-float on the laptop
|
||||
|
||||
## Update the CODESYS library
|
||||
```sh
|
||||
sudo apt install gcc-arm-linux-gnueabihf binutils-arm-linux-gnueabihf make m4 dos2unix
|
||||
make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" clean
|
||||
make -C component-source/component SDKDIR="$(realpath ExtensionSDK)" CC="arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard" all
|
||||
mkdir -p artifacts/armv7hf
|
||||
cp component-source/component/out/libCmpAtomModbus.so artifacts/armv7hf/
|
||||
arm-linux-gnueabihf-readelf --version-info artifacts/armv7hf/libCmpAtomModbus.so
|
||||
```
|
||||
|
||||
1. Back up `library/CmpAtomModbus.library` and open it in CODESYS.
|
||||
2. Retain Project Information title `CmpAtomModbus` and namespace `atMb`.
|
||||
3. Retain the Standard and SysTime references required by the IEC code.
|
||||
4. In its C Implemented Library object, update the native binary for the exact
|
||||
device target with the newly built `libCmpAtomModbus.so`. Use the component
|
||||
naming/settings already present in the working project.
|
||||
5. Compile the library, save/install it in Library Repository and update application
|
||||
references. Test with a full download for native component changes.
|
||||
The makefile supplies `-pthread` during compilation/linking. Always clean between
|
||||
architectures. Check `file`, ELF attributes, NEEDED libraries and GLIBC symbol
|
||||
requirements against the target. Adding pthread can change the required glibc
|
||||
version; the previously checked binaries do not establish compatibility of this build.
|
||||
The WL2000 runtime was ARMv7/VFPv3-D16 hard-float. The UC20 OS was ARM64/glibc 2.39;
|
||||
its runtime architecture/component support still needs target verification.
|
||||
|
||||
The working `.library` is preserved unchanged in this cleanup. Updating a separate
|
||||
`.so` file does not automatically update the binary embedded inside the library.
|
||||
The standalone artifact is retained for deployment/build comparison; rebuilding it
|
||||
does not imply it is identical to the currently embedded implementation.
|
||||
## CODESYS
|
||||
|
||||
## Runtime deployment
|
||||
1. Keep the prior working `.library` and PLC application for rollback.
|
||||
2. Create a fresh library project named `CmpAtomModbus.library` and import
|
||||
`library/atMb-Threaded.export`. Importing into an old project does not delete
|
||||
renamed/removed objects; a fresh project avoids duplicate old methods.
|
||||
3. Preserve Project Information title `CmpAtomModbus`, namespace `atMb`, and
|
||||
Standard/SysTime references. Verify library version 0.6.0.0 in Project Information.
|
||||
4. Add a C Implemented Library object and attach the **new 0.4.0.0** component for
|
||||
each exact target device. Do not attach files from `backup/`.
|
||||
5. Compile, install the library and update application references. Rename application
|
||||
diagnostic accesses using `RENAMED-MEMBERS.json`. `ClientTCP`/`ClientRTU` and
|
||||
request function-block names remain the same.
|
||||
6. Use a full download and verify the runtime loads component 0.4.0.0. If manually
|
||||
preloading an old component, remove/replace that preload during maintenance so
|
||||
it does not conflict with the embedded component.
|
||||
7. Start on a bench with the default worker settings; verify unplug/replug, device
|
||||
reboot, disabled client and PLC stop/start behavior before deployment.
|
||||
|
||||
Use the established embedded-library deployment or a manual bench installation;
|
||||
avoid loading two versions of the same component simultaneously. The earlier Pi
|
||||
setup manually loaded `/usr/lib/libCmpAtomModbus.so` with
|
||||
`Component.1=CmpAtomModbus` under `[ComponentManager]` in
|
||||
`/etc/CODESYSControl_User.cfg`. When switching to embedded deployment, remove only
|
||||
that manual preload entry during maintenance and restart the runtime as required.
|
||||
The C ABI layout/signature is unchanged (0x709EE65B, API 0.2.0.0). Component version
|
||||
is 0.4.0.0. Command 41 capability checking prevents this IEC export operating with
|
||||
an old component; it does not eliminate target/compiler compatibility checks.
|
||||
|
||||
For a manual installation, stop the runtime before replacing its component and
|
||||
restart afterwards. Check `/var/log/codesyscontrol.log` for the loaded component
|
||||
and version; `/var/opt/codesys/codesyscontrol.log` was stale on the development Pi.
|
||||
No deployment or runtime restart is performed by this cleanup.
|
||||
|
||||
## Different Linux targets
|
||||
|
||||
Build natively on the desired CPU or use a matching cross-toolchain. ARM64,
|
||||
ARM32 and x86-64 outputs are distinct. SDK, CODESYS runtime interface and libc
|
||||
compatibility also matter. A successful host protocol test does not validate native
|
||||
component loading into every runtime version. RTU support uses Linux termios/ioctl;
|
||||
verify serial permissions, driver behavior and physical timing on the target.
|
||||
|
||||
## Further documentation
|
||||
|
||||
- [User guide](USER-GUIDE.md): examples, polling, RTU, slave, timeout and recovery.
|
||||
- [API reference](API-REFERENCE.md): all objects from the last supplied working export.
|
||||
- [Validation](VALIDATION.md): exact scope of checks performed.
|
||||
Host validation: `make -C component-source test`. Host frame/function compilation
|
||||
used the supplied SDK and generated headers; a fresh SDK/M4 build and the IDE
|
||||
compile were not available in the execution environment.
|
||||
|
||||
+9
-23
@@ -1,25 +1,11 @@
|
||||
# Repository cleanup
|
||||
# Migration housekeeping
|
||||
|
||||
Removed unused files:
|
||||
The prior working library, export and ARM binaries are retained under `backup/`.
|
||||
They are not the threaded release. New source is imported from `atMb-Threaded.export`
|
||||
and rebuilt in the IDE with a newly built 0.4 C component. Previous IDE caches,
|
||||
editor lock/options files and C build outputs were omitted from this package.
|
||||
Git metadata is retained; no commit or push was made.
|
||||
|
||||
- `component-source/generated/`: original empty C stubs; active makefile builds
|
||||
from `component-source/component/`.
|
||||
- Both `CmpAtomModbus_library.precompilecache` files: local CODESYS caches,
|
||||
regenerated by the IDE; older duplicate was under artifacts.
|
||||
- `docs/document_export.py`: depended on an export already absent from this ZIP.
|
||||
- `docs/DOCUMENTATION-VALIDATION.md`: report for an export absent from this ZIP;
|
||||
current validation is consolidated in `VALIDATION.md`.
|
||||
|
||||
Rewrote README/build instructions to use the actual working `.library`, removing
|
||||
references to deleted exports, migration scripts and first-time Git initialization.
|
||||
Retained API/usage details and stated that their baseline is the last supplied export.
|
||||
Updated ignore rules and file checksums.
|
||||
|
||||
Preserved exact bytes of the `.library`, ARM64 `.so`, active C sources, SDK, tests
|
||||
and all existing Git metadata. Git changes are not staged, committed or pushed.
|
||||
Old files remain recoverable from Git history where previously committed.
|
||||
|
||||
To apply to an existing checkout, replacing files alone will not remove obsolete
|
||||
files. Prefer extracting this full snapshot into a separate directory and reviewing
|
||||
its existing Git status; alternatively remove the listed obsolete paths explicitly
|
||||
while applying updated files, keeping your checkout's own `.git` directory.
|
||||
`RENAMED-MEMBERS.json` is the exact old-to-new mapping for application changes.
|
||||
The previous transport prefix is removed from active IEC code and comments.
|
||||
Public request output is now out_errorCode; client output is out_lastErrorCode.
|
||||
|
||||
+146
-176
@@ -1,220 +1,190 @@
|
||||
# atMb usage and behavior
|
||||
# atMb 0.4 user guide
|
||||
|
||||
This guide describes the last supplied working IEC export, including `ClientTCP`.
|
||||
The current repository stores the working file at `library/CmpAtomModbus.library`.
|
||||
Use that file in CODESYS. No IEC or C implementation is changed by repository cleanup.
|
||||
The binary library format was not decoded to reverify its member declarations;
|
||||
when changing IEC code, update this guide against a fresh CODESYS export.
|
||||
## Thread and PLC calls
|
||||
|
||||
## Choosing the block
|
||||
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.
|
||||
|
||||
| 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 |
|
||||
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 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:
|
||||
## Optional worker configuration and a separate core
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
mbClient : atMb.ClientTCP;
|
||||
readRegisters : atMb.ReqReadHolding;
|
||||
registers : ARRAY[0..9] OF UINT;
|
||||
dataUsable : BOOL;
|
||||
commWorker : atMb.Worker;
|
||||
END_VAR
|
||||
|
||||
// Example only: CPU 2 must be online and permitted on this PLC.
|
||||
commWorker(in_cpu := 2, in_interval := T#1MS, in_watchdog := T#2S);
|
||||
```
|
||||
|
||||
Cyclic body:
|
||||
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.
|
||||
|
||||
```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);
|
||||
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.
|
||||
|
||||
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);
|
||||
## Scan-loss watchdog
|
||||
|
||||
dataUsable := readRegisters.out_validData AND NOT readRegisters.out_stale;
|
||||
```
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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:
|
||||
## TCP read
|
||||
|
||||
```iecst
|
||||
VAR
|
||||
serialClient : atMb.ClientRTU;
|
||||
readRegisters : atMb.ReqReadHolding;
|
||||
registers : ARRAY[0..9] OF UINT;
|
||||
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);
|
||||
```
|
||||
|
||||
```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);
|
||||
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.
|
||||
|
||||
readRegisters(client := serialClient, in_unitId := 1,
|
||||
in_startAddr := 0, in_quantity := 10, in_dataOffset := 0,
|
||||
data := registers);
|
||||
```
|
||||
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.
|
||||
|
||||
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.
|
||||
## Requests, data and priorities
|
||||
|
||||
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.
|
||||
| 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 |
|
||||
|
||||
## Polling and priority
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
`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.
|
||||
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 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.
|
||||
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
|
||||
|
||||
Reply timeout precedence:
|
||||
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.
|
||||
|
||||
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.
|
||||
`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.
|
||||
|
||||
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.
|
||||
`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.
|
||||
|
||||
`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.
|
||||
## Client statistics
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Data quality and statistics
|
||||
|
||||
| Diagnostic | Meaning |
|
||||
| Client member | Aggregation |
|
||||
|---|---|
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
`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.
|
||||
`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.
|
||||
|
||||
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.
|
||||
`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 example
|
||||
## TCP slave memory exchange
|
||||
|
||||
```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
|
||||
```
|
||||
`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.
|
||||
|
||||
```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.
|
||||
```
|
||||
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.
|
||||
|
||||
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 cleanup preserves the supplied working library and standalone native binary. 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.
|
||||
Online-change pointer ownership and physical hardware certification remain future
|
||||
work. This release needs a full download and on-target validation.
|
||||
|
||||
+37
-12
@@ -1,14 +1,39 @@
|
||||
# Cleanup validation
|
||||
# Validation and remaining qualification
|
||||
|
||||
- Working `.library`, standalone ARM64 `.so`, active C/component sources, tests,
|
||||
SDK and all Git metadata compared byte-for-byte with the uploaded ZIP: unchanged.
|
||||
- Removed precompile caches, unused generated stubs and broken generator script.
|
||||
- README/build instructions no longer refer to absent migration exports/scripts.
|
||||
- Strict host compilation and bridge tests passed after cleanup.
|
||||
- Nine direct TCP and nine bridge TCP scenarios passed.
|
||||
- RTU frame/write/error/recovery tests passed.
|
||||
- TCP slave FC1/2/3/4/5/6/15/16, exception/address and recovery tests passed.
|
||||
IEC library version: 0.6.0.0. C component version: 0.4.0.0.
|
||||
External C ABI signature/API version are unchanged.
|
||||
|
||||
No CODESYS compile or full ARM64 SDK build was run. Physical RTU operation and
|
||||
online-change behavior were not qualified by these checks. API documentation is
|
||||
based on the last supplied working IEC export, not a decoded binary `.library`.
|
||||
## Passed on the Linux build host
|
||||
|
||||
- Strict C compilation with warnings treated as errors for the test targets.
|
||||
- Bridge capacity, context pool, handle generation and shutdown tests.
|
||||
- Nine direct TCP and nine bridge TCP protocol/error/recovery scenarios.
|
||||
- RTU tests over real pseudo-terminals: fragmented frames, CRC errors, timeouts,
|
||||
exceptions, register/coil writes, invalid write echoes and recovery.
|
||||
- TCP slave FC1/2/3/4/5/6/15/16, address/exception handling and bind recovery.
|
||||
- Worker TCP and RTU responses complete with a 100 ms deadline while PLC calls
|
||||
are paused for 250 ms; results remain available for the next call.
|
||||
- Worker duration/age reporting and rejection of an unacknowledged result overwrite.
|
||||
- Slave service between scans and remote writes preserved against stale PLC pushes.
|
||||
- Scan watchdog transport shutdown and resumption, including uncertain sent writes
|
||||
without automatic worker replay; server watchdog listener restart.
|
||||
- CPU affinity configuration/restoration, invalid configuration rejection,
|
||||
concurrent status calls, and 20 worker shutdown/restart cycles.
|
||||
- Worker tests repeated after priority-inheritance mutex/error-reporting changes.
|
||||
- Full component host shared-object compile using the uploaded generated SDK headers;
|
||||
ComponentEntry and atmbcall_cext exported. This is not a fresh M4/ARM SDK build.
|
||||
- Updated IEC export parsed, object identifiers/references checked, basic structured
|
||||
text block balance checked, common client toString verified, and old embedded
|
||||
component removed. These checks do not substitute for the CODESYS compiler.
|
||||
|
||||
## Required before deployment
|
||||
|
||||
Rebuild the C component for each target with its compatible SDK/toolchain, check
|
||||
its ELF architecture and GLIBC dependencies, import the export in CODESYS, compile,
|
||||
embed/deploy the rebuilt component and perform a full download. No updated compiled
|
||||
.library or ARM binary is supplied. Previous binaries are backups only.
|
||||
|
||||
CODESYS IDE compilation, physical target/serial hardware testing, prolonged load
|
||||
and disconnect testing, and online-change behavior were not qualified here.
|
||||
Online change remains future work. Confirm watchdog and reconnect settings under
|
||||
actual PLC task jitter and network failures before production use.
|
||||
|
||||
Reference in New Issue
Block a user