Seems to be working great
This commit is contained in:
@@ -1,43 +1,13 @@
|
||||
# atMb 0.6 — threaded communication and client statistics
|
||||
# atMb 0.8 — online-change-focused synchronous release
|
||||
|
||||
This update moves TCP/RTU transaction progress and TCP server I/O into one shared
|
||||
C worker thread. The worker also handles connection recovery between PLC scans.
|
||||
The IEC task still selects/submits the next client request and exchanges slave
|
||||
memory. One C worker serves the fixed pools; it is not a separate thread per device.
|
||||
This release removes the native worker thread and restores TCP, RTU and server
|
||||
transport progress to the owning PLC call. The C layer retains no IEC pointers
|
||||
and uses fixed storage only.
|
||||
|
||||
Start with [upgrade/build instructions](docs/BUILD-AND-USAGE.md), then read the
|
||||
[user guide](docs/USER-GUIDE.md) and [API reference](docs/API-REFERENCE.md).
|
||||
Import `library/atMb-OnlineChange.export` into a fresh CODESYS library project.
|
||||
The IEC library version is **0.8.0.0** and the C component is **0.5.0.0**.
|
||||
The previous threaded release remains under `backup/` for rollback/reference.
|
||||
|
||||
## Required upgrade
|
||||
|
||||
Rebuild the C component as **0.4.0.0**, import `library/atMb-Threaded.export` into
|
||||
a fresh CODESYS library project and embed the new `.so` for the device target.
|
||||
The external function signature is unchanged, but new commands are required.
|
||||
The new IEC wrapper checks capability version and rejects an older component.
|
||||
This package contains updated source/export, not an IDE-compiled updated `.library`
|
||||
or rebuilt ARM binaries. The previous files are retained under `backup/` only.
|
||||
|
||||
## Changes
|
||||
|
||||
- Worker defaults: 1 ms sleep, normal OS scheduling, 2 s per-context scan watchdog.
|
||||
- Optional `atMb.Worker` FB sets CPU affinity, sleep interval and watchdog.
|
||||
- Completion and read data stay latched until acknowledged; failed writes are not
|
||||
replayed by the worker. IEC cyclic retries remain configurable via write holds.
|
||||
- Server dirty tracking preserves remote writes until the PLC pulls them.
|
||||
- Public diagnostics now use `out_errorCode` and `out_lastErrorCode`.
|
||||
- `client.stats` aggregates every registered request's counters and timings.
|
||||
- Both TCP and RTU clients show `OK:<successes>/Er:<errors>` like requests.
|
||||
- Accurate worker transaction durations; timeout counts classify actual timeouts.
|
||||
|
||||
## Files
|
||||
|
||||
- `component-source/`: protocol implementation, worker, component frame and tests.
|
||||
- `ExtensionSDK/`: required SDK headers/macros/build support.
|
||||
- `library/atMb-Threaded.export`: importable IEC source; no old embedded binary.
|
||||
- `library/atMb-Threaded-Review.st`: readable export source.
|
||||
- `docs/`: usage, ABI/worker notes, migration, validation and reference.
|
||||
- `backup/`: original library/export/binaries for rollback, not the new release.
|
||||
|
||||
Host tests run with `make -C component-source test`. Physical PLC/RTU testing and
|
||||
CODESYS IDE compilation remain required. Online-change qualification is future work;
|
||||
use a full download for this structural update. Git changes are not committed/pushed.
|
||||
`FB_reinit` closes native contexts before IEC instance memory is replaced,
|
||||
clears request pointers and active-request state, and allows contexts to reopen
|
||||
on subsequent scans.
|
||||
|
||||
+17
-20
@@ -10,40 +10,37 @@ a8de018bfde8d10d3f281bb45de03c4b4f3cd28466eb0fa0981086bc4033a69c ExtensionSDK/i
|
||||
814d06ca83d775f3bcb93f80859602b6c1debe9511b3d42ef337948bd2e2b5d0 ExtensionSDK/makefile
|
||||
4fc8013238169e924ee2eb5dd6c2b39c9d274f4c7c2219dc0b9a530687097519 ExtensionSDK/makefile_template
|
||||
aa7ccad11508ccbf4f9b24aa8db8f22392f9d723ae3af5cbfa67e38271cfbac3 ExtensionSDK/src/CmpFrame.c
|
||||
3a171751b48b339c731611044bdcb94a409d89568807026f6122ecba06e458fe README.md
|
||||
bf5b5518674a427f0a5c08f4484b07fc5fb92e2581469420f236358b5c2b6a07 README.md
|
||||
2a58de4b824ac7c41e5a192c05f299a08f8a59c95d7c8046722498a073e2f48e artifacts/README.md
|
||||
62dd7f0b5d7efdb0e8ca6a13257581c9d2231162e3e6acc311d34f12948acff7 backup/README.md
|
||||
07527262a8efcf9add5554cfcaed22cf927af63b985e431ab5341078a6864667 backup/artifacts/arm64/libCmpAtomModbus.so
|
||||
bb39f2b586454b47ccd64f158e0a08f6f5d3947e98e667d9ce240b2ab472f8fa backup/artifacts/armv7hf/libCmpAtomModbus.so
|
||||
fb1dbd96af6bb0a39e760d0e3ef5eab5a3f5c461de66d5d96d3301adce3d821d backup/library/CmpAtomModbus.export
|
||||
c475ae30bc96965bff86a956d133ebdc7d95f38f6f2326c70ea1ecfbc80d6e4b backup/library/CmpAtomModbus.library
|
||||
ec596402e5fc7a0bdc9fb3994143695a7457bda1077d0b6eee92944ba5798400 component-source/Makefile
|
||||
7b36f997e84cfea38246a3d197a72cd76f6c1b94902b934659e9b66a092aadb3 component-source/component/CmpAtomModbus.c
|
||||
f50ee94893d4282e6ff125c1830cbea05c0d1f3680bd7ddadd2b4c79aa1edf14 component-source/Makefile
|
||||
d91cffa9751bca79f13ae80552d654519333536c894bb13b41a1182c179c53b7 component-source/component/CmpAtomModbus.c
|
||||
30a8d7d5dfd372d171d4acdafd0b77cedca684e0b6e661ae1ab67e2627d35708 component-source/component/CmpAtomModbusItf.m4
|
||||
513586eed183bcc53cdf008c483da03feff6b5cb0f3ed3eff17de1462def12c3 component-source/component/CmpFrame.c
|
||||
cac2eeaad2fba34a0f5dddae57da11205fe98f52d2bc126125e5efe2c085220c component-source/component/makefile
|
||||
bd63f5153b80f5a6ea3000879b83e54dae430f09f1253a2fb3f828c5ccfff2b0 component-source/src/atom_bridge.c
|
||||
c0542adac77f60d5f3e7c14e755f07b341ba815b9c0243af4cec09c4844e4ab4 component-source/src/atom_bridge.h
|
||||
d891cd5dbaa6e7d891c42de74368ac482461986472b9d1f92af00d611289b1b7 component-source/component/makefile
|
||||
ffb81617656709acb3b046a240c03df5ea0d8de9001fa631444b23b714f05036 component-source/src/atom_bridge.c
|
||||
7994895beac2337d1f81216d5971eafa335c950c2050aa6e7cdfc83984955155 component-source/src/atom_bridge.h
|
||||
115271ae5673cd834fc196e594ff4c1ad0f2a71bb01dc36f713809adeda6efbf component-source/src/atom_mb.c
|
||||
366c879743b51daed51bac909439fc91b908a39ce63ec1d5ef0a8de78cb80bb1 component-source/src/atom_mb.h
|
||||
a2afdfd2f6f607f41464c3483b58d76452164019446986a9430b54cc0b9f6b5d component-source/src/atom_rtu.c
|
||||
565a5122de679b2e60990b1252f356e69316b51979485c3cf0101c6e32ac5a4a component-source/src/atom_rtu.h
|
||||
b7fb2772d7b2967014af4651d7a75570aeda69d7c06c515c3548a8a8d2d671e3 component-source/src/atom_server.c
|
||||
5e9e8e1d92f702af5901751da056e756fe9f988fc618360c42d9039619df183c component-source/src/atom_server.h
|
||||
853f95e1acf7dac8b75a63398ba139ae143744ba42ce211ca339c393637dafb7 component-source/src/atom_worker.c
|
||||
2555b12b0f1b8dc84847d2a34155f5d496fbb47804a7f27bf094458bed9e3949 component-source/src/atom_server.c
|
||||
51f3b4a5779347e58b9993b3c2d8f180a73e0a70cb2a8b81bf9350aca8b42bad component-source/src/atom_server.h
|
||||
d770393632f9d3d1acddf229f562148648792398a39d90851aa529d2923f8382 component-source/tests/bridge_driver.c
|
||||
50bb7854c3a966c46a583fba951c1d3a605bf9d1739ac83685322d9a7b740c4d component-source/tests/driver.c
|
||||
3af1a5f71d69bae2abda3de9dba31740ad254d7157d702847fb921fc2b64df3d component-source/tests/test_bridge.c
|
||||
0cf8f8fefda6376cacf0c56729d24aaa76241e807b0dee0b36001b3ad3cea509 component-source/tests/test_tcp.py
|
||||
c79f2e6d005dac7fb62669c274ff0c82b78e0ac80ef83ee19a596e699fdad7b8 component-source/tests/test_transport.py
|
||||
76d04a651b2d487cffbd778ea0b0ab8a9efda4b964044615efcacd0233448da4 component-source/tests/test_worker.py
|
||||
92617cdf8d973b65e662d25ad1986275f770ff01d25823e82b9624b3b9fb7fd5 docs/API-REFERENCE.md
|
||||
b014c5caec618cb2863c5a8e3ee98041e0cc5d1da0b5c2933809b471c6170e74 docs/BUILD-AND-USAGE.md
|
||||
27c9d27501196d9d4bbc28dd3da9f1ce3b4e16bd4fd0830d7cc1c85c681357b2 docs/CLEANUP.md
|
||||
997ce9d0fe59de7a214c9692fc9341f85d83ada909bad399532697e2943f88e7 docs/IMPORT-FIX.md
|
||||
575adda751a14ef74dc515190da8f93c8c2b085a7e4388a9b5363cc442e7bdd8 component-source/tests/test_transport.py
|
||||
e409f8f786c5d4fc6f91230dc2ad6baa1af3388ec74e1a13a6011634f36d430e docs/API-REFERENCE.md
|
||||
91e5a0a3597d214832ca2acf02a72bfe8678268fc7b88aba1cca4a9336087f56 docs/BUILD-AND-USAGE.md
|
||||
dc4b312826bba713d389d86cd35fd01d4ea223c3f084556f9ff2064532a4dcee docs/CLEANUP.md
|
||||
d60f5881289ac66cf5f6e4a90c5c32beaded3426a385825fb3d1750ec758dee1 docs/RENAMED-MEMBERS.json
|
||||
c44551b2bb75ae7e98fc33a95dad306f419e328ba0571c7277b238705356efe5 docs/USER-GUIDE.md
|
||||
6bd8db7b034dfd48e52a1af5845c769ca6cbc72236522ddfdd905ae56b5b1f66 docs/VALIDATION.md
|
||||
fb39a21af08a33d070d186366eb9ccbabdf645c001bd2a91b24e12400775db72 docs/WORKER-ABI.md
|
||||
c5c5363714b6a8ebb3778efb69f4c4213aec7aeecf2b60a88637d1bd4d8ceeb3 library/atMb-Threaded-Review.st
|
||||
24efab5b7fb402e84ad2e375c513e2ccf2ca809d9754b67374bd26c787ccd42e library/atMb-Threaded.export
|
||||
56fa44b409bb90c3271d162ef36f52644d17f49c2badf32f08c224ff26ced796 docs/USER-GUIDE.md
|
||||
ba57f003d81114f7b20dedbb0837259cacc8d6e0211308927a477a8609d9f031 docs/VALIDATION.md
|
||||
7c566ea69d86ff9654f7eb100e3086b9139bb006a8323a5272adf1dd4a6e57d2 docs/WORKER-ABI.md
|
||||
b0b0e76734802224de21cb985bf9554edd45fcf5ced4b9b911f9eecc99089002 library/atMb-OnlineChange-Review.st
|
||||
fac79d3281a13570e134a599953584a399a884d63fdb2ea32f801548fa3c3c64 library/atMb-OnlineChange.export
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,16 +1,15 @@
|
||||
CC ?= cc
|
||||
CFLAGS = -std=c11 -Wall -Wextra -Werror -O2 -pthread
|
||||
CFLAGS = -std=c11 -Wall -Wextra -Werror -O2
|
||||
.PHONY: test clean
|
||||
test: tests/driver tests/test_bridge tests/bridge_driver
|
||||
./tests/test_bridge
|
||||
python3 tests/test_tcp.py
|
||||
python3 tests/test_tcp.py bridge_driver
|
||||
python3 tests/test_transport.py
|
||||
python3 tests/test_worker.py
|
||||
tests/bridge_driver: tests/bridge_driver.c src/atom_bridge.c src/atom_bridge.h src/atom_worker.c src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h
|
||||
$(CC) $(CFLAGS) -Isrc tests/bridge_driver.c src/atom_bridge.c src/atom_worker.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@
|
||||
tests/test_bridge: tests/test_bridge.c src/atom_bridge.c src/atom_bridge.h src/atom_worker.c src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h
|
||||
$(CC) $(CFLAGS) -Isrc tests/test_bridge.c src/atom_bridge.c src/atom_worker.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@
|
||||
tests/bridge_driver: tests/bridge_driver.c src/atom_bridge.c src/atom_bridge.h src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h
|
||||
$(CC) $(CFLAGS) -Isrc tests/bridge_driver.c src/atom_bridge.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@
|
||||
tests/test_bridge: tests/test_bridge.c src/atom_bridge.c src/atom_bridge.h src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h
|
||||
$(CC) $(CFLAGS) -Isrc tests/test_bridge.c src/atom_bridge.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@
|
||||
tests/driver: tests/driver.c src/atom_mb.c src/atom_mb.h src/atom_rtu.c src/atom_rtu.h src/atom_server.c src/atom_server.h
|
||||
$(CC) $(CFLAGS) -Isrc tests/driver.c src/atom_mb.c src/atom_rtu.c src/atom_server.c -o $@
|
||||
clean:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#define _GNU_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#include "../src/atom_bridge.h"
|
||||
#include "CmpStd.h"
|
||||
@@ -32,5 +31,3 @@ void CDECL CDECL_EXT atmbcall_cext(atmbcall_cext_struct *p)
|
||||
#include "../src/atom_rtu.c"
|
||||
#include "../src/atom_server.c"
|
||||
#include "../src/atom_bridge.c"
|
||||
|
||||
#include "../src/atom_worker.c"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Generated from supplied ExtensionSDK; build with target Linux toolchain.
|
||||
SDKDIR ?= ../../ExtensionSDK
|
||||
MAJORVER=0
|
||||
MINORVER=7
|
||||
CMPVERSION=0x00040000
|
||||
MINORVER=4
|
||||
CMPVERSION=0x00050000
|
||||
# Local development ID; check no other component uses this ID.
|
||||
CMPID=0x2001
|
||||
DOS2UNIX=dos2unix
|
||||
@@ -10,6 +10,4 @@ M4=m4
|
||||
CC ?= gcc
|
||||
# The SDK otherwise includes CmpFrame in its component identity wildcard.
|
||||
override CSRC := CmpAtomModbus.c
|
||||
CFLAGS += -pthread
|
||||
LDLIBS += -pthread
|
||||
include $(SDKDIR)/makefile
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "atom_server.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired, suspended; uint64_t heartbeat; } slot;
|
||||
typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired; } slot;
|
||||
static slot slots[AM_POOL_SIZE];
|
||||
static uint32_t token(unsigned i) { return (slots[i].generation<<8)|(i+1u); }
|
||||
static slot *lookup(uint32_t h) {
|
||||
@@ -47,7 +47,7 @@ uint32_t ab_dispatch(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
text[q]=0;
|
||||
if(!ar_init(&s->c,&s->rtu,text,ip,unit,f,(uint8_t)addr,ct))return AM_ARGUMENT;
|
||||
} else if(!am_init(&s->c,text,port)) return AM_ARGUMENT;
|
||||
s->generation++; s->used=1; s->suspended=0; s->heartbeat=now; h=token(i);
|
||||
s->generation++; s->used=1; h=token(i);
|
||||
snapshot(&s->c,h,status); return 0;
|
||||
}
|
||||
s=lookup(h);
|
||||
@@ -58,12 +58,9 @@ uint32_t ab_dispatch(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
/* Never recycle a generation after wrap: stale handles stay invalid. */
|
||||
if(s->generation==0xffffffu) s->retired=1;
|
||||
snapshot(&s->c,0,status); return 0;
|
||||
case AB_ENABLE: s->heartbeat=now; s->suspended=0; am_enable(&s->c,1,now); break;
|
||||
case AB_DISABLE: s->suspended=0; if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break;
|
||||
case AB_TICK:
|
||||
s->heartbeat=now;
|
||||
if(s->suspended) { s->suspended=0; am_enable(&s->c,1,now); }
|
||||
break;
|
||||
case AB_ENABLE: am_enable(&s->c,1,now); break;
|
||||
case AB_DISABLE: if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break;
|
||||
case AB_TICK: if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now); break;
|
||||
case AB_RECONNECT: if(s->serial)ar_reconnect(&s->c,&s->rtu,now);else am_reconnect(&s->c,now); break;
|
||||
case AB_CONFIGURE:
|
||||
if(s->c.enabled) result=AM_BUSY;
|
||||
@@ -106,15 +103,14 @@ void ab_close_all(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Called only by the worker while holding the shared engine mutex. */
|
||||
void ab_pump(uint64_t now,uint32_t watchdog_ms) {
|
||||
for(unsigned i=0;i<AM_POOL_SIZE;i++) if(slots[i].used) {
|
||||
slot *s=&slots[i];
|
||||
if(s->c.enabled && now-s->heartbeat >= watchdog_ms) {
|
||||
if(s->serial)ar_close(&s->c);else am_close(&s->c);
|
||||
s->suspended=1;
|
||||
}
|
||||
if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now);
|
||||
}
|
||||
as_pump(now,watchdog_ms);
|
||||
/* Synchronous ABI entry point. All transport progress occurs on the caller's
|
||||
* PLC task; no native worker thread or retained IEC pointer is used. */
|
||||
uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t unit,uint8_t f,
|
||||
uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,uint32_t *status,uint32_t sc,
|
||||
uint64_t now,uint32_t ct,uint32_t rt,uint32_t retry) {
|
||||
return ab_dispatch(cmd,h,ip,port,unit,f,addr,q,data,cap,status,sc,now,ct,rt,retry);
|
||||
}
|
||||
|
||||
void ab_shutdown(void) { ab_close_all(); }
|
||||
|
||||
/* Called only by the worker while holding the shared engine mutex. */
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef ATOM_BRIDGE_H
|
||||
#define ATOM_BRIDGE_H
|
||||
#include <stdint.h>
|
||||
/* Fixed pool; serialized calls and worker I/O. IEC instances still belong to one task.
|
||||
/* Fixed pool; transport progress is synchronous in the owning PLC task. IEC instances still belong to one task.
|
||||
* Only synchronous pointer access; generation-checked handles, no heap. */
|
||||
#define AM_STATUS_WORDS 24
|
||||
#define AM_POOL_SIZE 16
|
||||
enum { AB_OPEN=1, AB_CLOSE, AB_ENABLE, AB_DISABLE, AB_TICK, AB_SUBMIT,
|
||||
AB_COPY_READ, AB_RECONNECT, AB_CONFIGURE, AB_ACK, AB_REQUEST_TIMEOUT };
|
||||
enum { AB_OPEN_RTU=20 };
|
||||
enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND, AB_WORKER_ERROR };
|
||||
enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND };
|
||||
uint32_t ab_call(uint32_t command,uint32_t handle,uint32_t ip,uint16_t port,
|
||||
uint8_t unit,uint8_t function,uint16_t address,uint16_t quantity,
|
||||
uint16_t *data,uint32_t capacity,uint32_t *status,uint32_t status_capacity,
|
||||
|
||||
@@ -114,7 +114,11 @@ uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table
|
||||
case AS_CLOSE:as_stop(s);s->used=0;as_snapshot(s,0,out);return 0;
|
||||
case AS_ENABLE:s->enabled=1;s->suspended=0;s->heartbeat=now;break;
|
||||
case AS_DISABLE:s->suspended=0;s->enabled=0;as_stop(s);break;
|
||||
case AS_TICK:s->heartbeat=now;if(s->suspended){s->suspended=0;s->enabled=1;s->retry_at=now;}break;
|
||||
case AS_TICK:
|
||||
s->heartbeat=now;
|
||||
if(s->suspended){s->suspended=0;s->enabled=1;s->retry_at=now;}
|
||||
as_tick(s,now);
|
||||
break;
|
||||
case AS_PUSH:case AS_PULL:
|
||||
if(table>3 || !q || q>AS_ITEMS || (uint32_t)addr+q>65536u)result=AM_ARGUMENT;
|
||||
else if(!data || cap<q)result=258;
|
||||
@@ -133,11 +137,3 @@ uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table
|
||||
as_snapshot(s,h,out);return result;
|
||||
}
|
||||
void as_shutdown(void){for(unsigned i=0;i<AS_POOL;i++)if(as_slots[i].used){as_stop(&as_slots[i]);as_slots[i].used=0;}}
|
||||
|
||||
void as_pump(uint64_t now,uint32_t watchdog_ms){
|
||||
for(unsigned i=0;i<AS_POOL;i++)if(as_slots[i].used){
|
||||
as_server *s=&as_slots[i];
|
||||
if(s->enabled && now-s->heartbeat>=watchdog_ms){as_stop(s);s->enabled=0;s->suspended=1;}
|
||||
as_tick(s,now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,3 @@ enum { AS_OPEN=30,AS_CLOSE,AS_ENABLE,AS_DISABLE,AS_TICK,AS_PUSH,AS_PULL };
|
||||
uint32_t as_call(uint32_t,uint32_t,uint32_t,uint16_t,uint8_t,uint16_t,uint16_t,uint16_t *,uint32_t,uint32_t *,uint64_t,uint32_t);
|
||||
void as_shutdown(void);
|
||||
#endif
|
||||
|
||||
void as_pump(uint64_t now,uint32_t watchdog_ms);
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include "atom_bridge.h"
|
||||
#include "atom_mb.h"
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
/* All engine access is serialized. The worker never holds an IEC pointer.
|
||||
* Network I/O is nonblocking; no mutex is held across sleep or thread join.
|
||||
* Serial open/ioctl latency is driver dependent: this is not a hard RT API. */
|
||||
uint32_t ab_dispatch(uint32_t,uint32_t,uint32_t,uint16_t,uint8_t,uint8_t,uint16_t,uint16_t,
|
||||
uint16_t *,uint32_t,uint32_t *,uint32_t,uint64_t,uint32_t,uint32_t,uint32_t);
|
||||
void ab_pump(uint64_t,uint32_t);
|
||||
void ab_close_all(void);
|
||||
static pthread_mutex_t aw_lifecycle=PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t aw_engine;
|
||||
static pthread_once_t aw_once=PTHREAD_ONCE_INIT;
|
||||
static int aw_init_error;
|
||||
static void aw_init_mutex(void){
|
||||
pthread_mutexattr_t a;
|
||||
int e=pthread_mutexattr_init(&a);
|
||||
if(e){aw_init_error=e;return;}
|
||||
/* Priority inheritance limits inversion if the PLC task has RT priority. */
|
||||
e=pthread_mutexattr_setprotocol(&a,PTHREAD_PRIO_INHERIT);
|
||||
if(e){pthread_mutexattr_destroy(&a);aw_init_error=e;return;}
|
||||
aw_init_error=pthread_mutex_init(&aw_engine,&a);
|
||||
pthread_mutexattr_destroy(&a);
|
||||
}
|
||||
static pthread_t aw_thread;
|
||||
static int aw_running,aw_stop,aw_error,aw_cpu=-1;
|
||||
static uint32_t aw_interval=1,aw_watchdog=2000;
|
||||
static uint64_t aw_cycles,aw_heartbeat;
|
||||
static cpu_set_t aw_original_mask;
|
||||
static uint64_t aw_now(void){
|
||||
struct timespec t;clock_gettime(CLOCK_MONOTONIC,&t);
|
||||
return (uint64_t)t.tv_sec*1000u+(uint64_t)t.tv_nsec/1000000u;
|
||||
}
|
||||
static void *aw_main(void *unused){
|
||||
(void)unused;
|
||||
for(;;){
|
||||
pthread_mutex_lock(&aw_engine);
|
||||
if(aw_stop){pthread_mutex_unlock(&aw_engine);break;}
|
||||
aw_heartbeat=aw_now();ab_pump(aw_heartbeat,aw_watchdog);aw_cycles++;
|
||||
uint32_t delay=aw_interval;
|
||||
pthread_mutex_unlock(&aw_engine);
|
||||
struct timespec t={delay/1000u,(long)(delay%1000u)*1000000L};
|
||||
while(nanosleep(&t,&t)<0 && errno==EINTR){}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
static int aw_start(void){
|
||||
if(aw_running)return 0;
|
||||
int e=pthread_getaffinity_np(pthread_self(),sizeof(aw_original_mask),&aw_original_mask);
|
||||
if(e)return e;
|
||||
pthread_attr_t attr;struct sched_param priority={0};e=pthread_attr_init(&attr);
|
||||
if(e)return e;
|
||||
/* Do not inherit a high-priority real-time PLC task's scheduling policy. */
|
||||
e=pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);
|
||||
if(!e)e=pthread_attr_setschedpolicy(&attr,SCHED_OTHER);
|
||||
if(!e)e=pthread_attr_setschedparam(&attr,&priority);
|
||||
aw_stop=0;
|
||||
if(!e)e=pthread_create(&aw_thread,&attr,aw_main,NULL);
|
||||
pthread_attr_destroy(&attr);
|
||||
if(!e){aw_running=1;aw_cpu=-1;}
|
||||
return e;
|
||||
}
|
||||
static void aw_snapshot(uint32_t *o){
|
||||
memset(o,0,24*sizeof(*o));o[0]=0x00040000u;o[1]=(uint32_t)aw_running;
|
||||
o[2]=(uint32_t)aw_error;o[3]=(uint32_t)(aw_cpu+1);o[4]=aw_interval;o[5]=aw_watchdog;
|
||||
o[6]=(uint32_t)aw_cycles;o[7]=(uint32_t)(aw_cycles>>32);
|
||||
o[8]=(uint32_t)aw_heartbeat;o[9]=(uint32_t)(aw_heartbeat>>32);
|
||||
}
|
||||
uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t unit,uint8_t f,
|
||||
uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,uint32_t *status,uint32_t sc,
|
||||
uint64_t ignored_now,uint32_t ct,uint32_t rt,uint32_t retry){
|
||||
(void)ignored_now;
|
||||
if(!status || sc<AM_STATUS_WORDS)return AB_BAD_CAPACITY;
|
||||
pthread_once(&aw_once,aw_init_mutex);
|
||||
if(aw_init_error){memset(status,0,24*sizeof(*status));status[2]=status[7]=(uint32_t)aw_init_error;return AB_WORKER_ERROR;}
|
||||
pthread_mutex_lock(&aw_lifecycle);pthread_mutex_lock(&aw_engine);
|
||||
uint32_t result=0;
|
||||
if(cmd==1 || cmd==20 || cmd==30 || cmd==40){
|
||||
aw_error=aw_start();
|
||||
if(aw_error){memset(status,0,24*sizeof(*status));status[2]=status[7]=(uint32_t)aw_error;result=AB_WORKER_ERROR;goto done;}
|
||||
}
|
||||
if(cmd==40){
|
||||
/* addr=CPU+1 (0 restores inherited mask), ct=pump interval, rt=scan watchdog. */
|
||||
if(addr>CPU_SETSIZE || ct<1 || ct>20 || rt<100 || rt>60000){result=AM_ARGUMENT;goto worker_status;}
|
||||
cpu_set_t mask=aw_original_mask;
|
||||
if(addr){CPU_ZERO(&mask);CPU_SET(addr-1,&mask);}
|
||||
aw_error=pthread_setaffinity_np(aw_thread,sizeof(mask),&mask);
|
||||
if(aw_error){result=AB_WORKER_ERROR;goto worker_status;}
|
||||
aw_cpu=(int)addr-1;aw_interval=ct;aw_watchdog=rt;
|
||||
worker_status:
|
||||
aw_snapshot(status);
|
||||
}else if(cmd==41){aw_snapshot(status);}
|
||||
else result=ab_dispatch(cmd,h,ip,port,unit,f,addr,q,data,cap,status,sc,aw_now(),ct,rt,retry);
|
||||
done:
|
||||
pthread_mutex_unlock(&aw_engine);pthread_mutex_unlock(&aw_lifecycle);
|
||||
return result;
|
||||
}
|
||||
void ab_shutdown(void){
|
||||
pthread_once(&aw_once,aw_init_mutex);
|
||||
if(aw_init_error)return;
|
||||
pthread_mutex_lock(&aw_lifecycle);pthread_mutex_lock(&aw_engine);
|
||||
int join=aw_running;aw_stop=1;pthread_mutex_unlock(&aw_engine);
|
||||
if(join)pthread_join(aw_thread,NULL);
|
||||
pthread_mutex_lock(&aw_engine);ab_close_all();aw_running=0;aw_cpu=-1;
|
||||
aw_error=0;aw_interval=1;aw_watchdog=2000;aw_cycles=0;aw_heartbeat=0;
|
||||
pthread_mutex_unlock(&aw_engine);pthread_mutex_unlock(&aw_lifecycle);
|
||||
}
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
root=Path(__file__).resolve().parents[1]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
so=Path(tmp)/'test-transport.so'
|
||||
subprocess.run(['cc','-shared','-fPIC','-std=c11','-Wall','-Wextra','-Werror','-O2','-pthread','-I'+str(root/'src'),*[str(root/'src'/f) for f in ['atom_mb.c','atom_rtu.c','atom_server.c','atom_bridge.c','atom_worker.c']],'-o',str(so)],check=True)
|
||||
subprocess.run(['cc','-shared','-fPIC','-std=c11','-Wall','-Wextra','-Werror','-O2','-I'+str(root/'src'),*[str(root/'src'/f) for f in ['atom_mb.c','atom_rtu.c','atom_server.c','atom_bridge.c']],'-o',str(so)],check=True)
|
||||
lib=C.CDLL(str(so));fn=lib.ab_call
|
||||
U=C.c_uint32;W=C.c_uint16;B=C.c_uint8
|
||||
fn.argtypes=[U,U,U,W,B,B,W,W,C.POINTER(W),U,C.POINTER(U),U,C.c_uint64,U,U,U];fn.restype=U
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise production worker with real sockets/PTYS and deliberately sparse PLC calls."""
|
||||
import ctypes as C
|
||||
import os,pty,select,socket,struct,subprocess,tempfile,time,threading
|
||||
from pathlib import Path
|
||||
root=Path(__file__).resolve().parents[1]
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
so=Path(td)/'worker.so'
|
||||
subprocess.run(['cc','-shared','-fPIC','-std=c11','-Wall','-Wextra','-Werror','-O2','-pthread',
|
||||
*[str(root/'src'/f) for f in ['atom_mb.c','atom_rtu.c','atom_server.c','atom_bridge.c','atom_worker.c']],'-o',str(so)],check=True)
|
||||
lib=C.CDLL(str(so));U=C.c_uint32;W=C.c_uint16;B=C.c_uint8
|
||||
lib.ab_call.argtypes=[U,U,U,W,B,B,W,W,C.POINTER(W),U,C.POINTER(U),U,C.c_uint64,U,U,U];lib.ab_call.restype=U
|
||||
def call(cmd,h=0,ip=0,port=0,f=3,addr=0,q=1,values=(),ct=100,rt=100,retry=10,unit=1):
|
||||
data=(W*2000)(*values);status=(U*24)()
|
||||
result=lib.ab_call(cmd,h,ip,port,unit,f,addr,q,data,2000,status,24,0,ct,rt,retry)
|
||||
return result,list(status),list(data)
|
||||
def until(fn,seconds=1):
|
||||
end=time.monotonic()+seconds
|
||||
while time.monotonic()<end:
|
||||
v=fn()
|
||||
if v:return v
|
||||
time.sleep(.002)
|
||||
raise AssertionError('condition timed out')
|
||||
def configure(watchdog=2000,cpu=-1):
|
||||
r,s,_=call(40,addr=cpu+1,ct=1,rt=watchdog);assert r==0,(r,s)
|
||||
def client(port):
|
||||
r,s,_=call(1,ip=0x7f000001,port=port);assert not r;h=s[0]
|
||||
assert not call(9,h,rt=100)[0];assert not call(3,h)[0]
|
||||
until(lambda:call(5,h)[1][1]==3)
|
||||
return h
|
||||
def recv_exact(peer,count):
|
||||
result=b''
|
||||
while len(result)<count:
|
||||
v=peer.recv(count-len(result));assert v;result+=v
|
||||
return result
|
||||
def server():
|
||||
r,s,_=call(30,ip=0x7f000001,port=port);assert not r;h=s[0]
|
||||
for t in range(4):assert not call(35,h,f=t,q=8,values=[0]*8)[0]
|
||||
assert not call(32,h)[0]
|
||||
until(lambda:call(34,h)[1][1])
|
||||
return h
|
||||
probe=socket.socket();probe.bind(('127.0.0.1',0));port=probe.getsockname()[1];probe.close()
|
||||
try:
|
||||
configure();sh=server();ch=client(port)
|
||||
assert not call(6,ch,q=2)[0]
|
||||
# The reply deadline is 100 ms; the PLC deliberately does nothing for 250 ms.
|
||||
time.sleep(.25)
|
||||
r,s,d=call(7,ch);assert not r and s[3] and not s[4] and d[:2]==[0,0],s
|
||||
assert call(6,ch)[0]==2 # Completion cannot be overwritten before acknowledgment.
|
||||
r,m,_=call(12,ch);assert not r and m[21]<100 and m[22]>=100,m
|
||||
assert call(10,ch)[0]==0
|
||||
print('PASS worker completes TCP before timeout with PLC absent for 250 ms; result retained and timed')
|
||||
# Remote write must survive a stale PLC push before the PLC pulls its result.
|
||||
peer=socket.create_connection(('127.0.0.1',port));peer.settimeout(1)
|
||||
pdu=struct.pack('>BHH',6,0,4321);peer.sendall(struct.pack('>HHHB',1,0,len(pdu)+1,1)+pdu)
|
||||
assert recv_exact(peer,12)[7:]==pdu
|
||||
assert call(35,sh,f=2,q=8,values=[0]*8)[0]==0
|
||||
assert call(36,sh,f=2,q=8)[2][0]==4321
|
||||
# After acknowledgment a deliberate application override is permitted.
|
||||
call(35,sh,f=2,q=8,values=[42]*8);assert call(36,sh,f=2,q=8)[2][0]==42
|
||||
assert call(35,sh,f=2,addr=1,q=8,values=[0]*8)[0]==1 # explicit recreate for remap
|
||||
peer.close();call(2,ch);call(31,sh)
|
||||
print('PASS autonomous TCP slave and lossless remote-write handoff')
|
||||
# Real serial PTY, again no PLC ticks after submission.
|
||||
master,slave=pty.openpty();path=os.ttyname(slave);os.close(slave)
|
||||
r,s,_=call(20,ip=9600,unit=0,f=8,addr=1,q=len(path),ct=0,values=list(path.encode()));assert not r;h=s[0]
|
||||
call(9,h,rt=100);call(3,h);until(lambda:call(5,h)[1][1]==3)
|
||||
call(6,h,unit=1,f=3,q=1)
|
||||
assert select.select([master],[],[],1)[0];request=os.read(master,256);assert len(request)==8
|
||||
def crc(p):
|
||||
c=65535
|
||||
for b in p:
|
||||
c^=b
|
||||
for _ in range(8):c=(c>>1)^(0xa001 if c&1 else 0)
|
||||
return c
|
||||
p=bytes([1,3,2,0,77]);os.write(master,p+struct.pack('<H',crc(p)))
|
||||
time.sleep(.25);r,s,d=call(7,h);assert not r and s[3] and d[0]==77,s
|
||||
call(2,h);os.close(master)
|
||||
print('PASS RTU completion while PLC does not scan for 250 ms')
|
||||
# Watchdog cancels an in-flight write without replaying it on resume.
|
||||
configure(100)
|
||||
listener=socket.socket();listener.bind(('127.0.0.1',0));listener.listen();listener.settimeout(1)
|
||||
h=client(listener.getsockname()[1]);peer,_=listener.accept();peer.settimeout(1)
|
||||
call(11,h,rt=1000);assert not call(6,h,f=16,q=1,values=[9])[0]
|
||||
packet=recv_exact(peer,15);assert packet[7]==16
|
||||
time.sleep(.16)
|
||||
# command 12 is read-only and does not renew the scan lease.
|
||||
r,s,_=call(12,h);assert not r and s[1]==0 and s[5]==10 and s[9]==1,s
|
||||
assert peer.recv(1)==b''
|
||||
call(5,h);peer2,_=listener.accept();peer2.settimeout(.03)
|
||||
try:assert not peer2.recv(1), 'write replayed'
|
||||
except socket.timeout:pass
|
||||
call(2,h);peer.close();peer2.close();listener.close()
|
||||
print('PASS scan watchdog closes transport, preserves uncertain write, resumes without replay')
|
||||
# Server closes its listener when scanning ceases and recovers on refresh.
|
||||
configure(100);sh=server();time.sleep(.16)
|
||||
probe=socket.socket();probe.settimeout(.1);assert probe.connect_ex(('127.0.0.1',port))!=0;probe.close()
|
||||
call(34,sh);until(lambda:call(34,sh)[1][1]);call(31,sh)
|
||||
print('PASS server watchdog and restart')
|
||||
# CPU selection, failure reporting, and restoring inherited mask.
|
||||
configure(cpu=min(os.sched_getaffinity(0)))
|
||||
r,s,_=call(41);assert not r and s[3]==min(os.sched_getaffinity(0))+1
|
||||
assert call(40,addr=65535,ct=1,rt=2000)[0]==1
|
||||
configure()
|
||||
failures=[]
|
||||
def reader():
|
||||
for _ in range(200):
|
||||
if call(41)[0]:failures.append('snapshot failure')
|
||||
threads=[threading.Thread(target=reader) for _ in range(4)]
|
||||
for t in threads:t.start()
|
||||
for t in threads:t.join()
|
||||
assert not failures
|
||||
print('PASS configurable affinity, invalid arguments, concurrent snapshot access')
|
||||
for _ in range(20):
|
||||
lib.ab_shutdown();assert call(41)[1][1]==0;configure();assert call(41)[1][1]==1
|
||||
print('PASS repeated joined shutdown/restart')
|
||||
finally:lib.ab_shutdown()
|
||||
+13
-42
@@ -1,6 +1,6 @@
|
||||
# atMb 0.4 API reference
|
||||
# atMb 0.8 API reference
|
||||
|
||||
Generated from `library/atMb-Threaded.export`. Declarations below are the current source. Use USER-GUIDE.md for lifecycle, units and examples. Internal fields remain exposed where required by existing IEC inheritance; do not overwrite engine state from application code.
|
||||
Generated from `library/atMb-OnlineChange.export`. Declarations below are the current source. Use USER-GUIDE.md for lifecycle, units and examples. Internal fields remain exposed where required by existing IEC inheritance; do not overwrite engine state from application code.
|
||||
|
||||
## atMbCall_cext
|
||||
|
||||
@@ -106,7 +106,7 @@ END_TYPE
|
||||
|
||||
## ModbusMaster.ClientBase
|
||||
|
||||
Shared abstract client scheduler and registry. Call concrete client every scan; worker handles transaction progress. Exposes aggregated stats and toString.
|
||||
Shared abstract client scheduler and registry. Call concrete client every scan; IEC task handles transaction progress. Exposes aggregated stats and toString.
|
||||
|
||||
```iecst
|
||||
{attribute 'monitoring_display' := 'toString'}
|
||||
@@ -564,7 +564,7 @@ VAR_INPUT
|
||||
sentCnt : UDINT;
|
||||
replyCnt : UDINT;
|
||||
exceptionCnt : UDINT;
|
||||
totalValidDuration : ULINT; // Sum of successful worker transaction durations, milliseconds.
|
||||
totalValidDuration : ULINT; // Sum of successful transport transaction durations, milliseconds.
|
||||
END_VAR
|
||||
VAR
|
||||
previousOnline : BOOL;
|
||||
@@ -774,7 +774,7 @@ END_VAR
|
||||
|
||||
## ClientBase.FB_exit
|
||||
|
||||
Lifecycle resource release. Worker is joined by component exit; context hooks close their owned handle. Use public release for application shutdown.
|
||||
Lifecycle resource release. context hooks close their owned handle. Use public release for application shutdown.
|
||||
|
||||
```iecst
|
||||
METHOD FB_exit : BOOL
|
||||
@@ -815,7 +815,7 @@ END_VAR
|
||||
|
||||
## ClientBase.processCycle
|
||||
|
||||
Refresh scan lease, read completed results, process request completions, acknowledge result and select next transaction. OS I/O progression is performed by the worker.
|
||||
Refresh scan lease, read completed results, process request completions, acknowledge result and select next transaction. Transport I/O progresses during the owning PLC call.
|
||||
|
||||
```iecst
|
||||
METHOD processCycle
|
||||
@@ -826,7 +826,7 @@ END_VAR
|
||||
|
||||
## ClientBase.ensureContext
|
||||
|
||||
Check 0.4 worker capability, allocate/configure/enable a context. A nonzero handle does not imply connectivity. Old components are rejected with error 259.
|
||||
Allocate/configure/enable a context. A nonzero handle does not imply connectivity. Old components are rejected with error 259.
|
||||
|
||||
```iecst
|
||||
METHOD PROTECTED ensureContext : BOOL
|
||||
@@ -1138,7 +1138,7 @@ METHOD PUBLIC invalidateData
|
||||
|
||||
## RequestBase.finishRequest
|
||||
|
||||
Map a completed result and write uncertainty, collect worker timing/read age, update reply/exception/timeout counters and completion flags. Successful reads become valid; errors retain old array values.
|
||||
Map a completed result and write uncertainty, collect transport timing/read age, update reply/exception/timeout counters and completion flags. Successful reads become valid; errors retain old array values.
|
||||
|
||||
```iecst
|
||||
METHOD finishRequest
|
||||
@@ -1239,7 +1239,7 @@ PROPERTY reqBusy : BOOL
|
||||
|
||||
## RequestStats.reqDone
|
||||
|
||||
On successful executing completion, increment saturated success counters, clear errors and execution, update display. Worker supplies duration.
|
||||
On successful executing completion, increment saturated success counters, clear errors and execution, update display. Transport supplies duration.
|
||||
|
||||
```iecst
|
||||
PROPERTY reqDone : BOOL
|
||||
@@ -1307,7 +1307,7 @@ END_VAR
|
||||
|
||||
## ModbusSlave.FB_exit
|
||||
|
||||
Lifecycle resource release. Worker is joined by component exit; context hooks close their owned handle. Use public release for application shutdown.
|
||||
Lifecycle resource release. context hooks close their owned handle. Use public release for application shutdown.
|
||||
|
||||
```iecst
|
||||
METHOD FB_exit : BOOL
|
||||
@@ -1327,7 +1327,7 @@ METHOD FB_reinit : BOOL
|
||||
|
||||
## ModbusSlave.serverCall
|
||||
|
||||
Private synchronous server/worker ABI adapter using server staging/status arrays.
|
||||
Private synchronous server ABI adapter using server staging/status arrays.
|
||||
|
||||
```iecst
|
||||
METHOD PRIVATE serverCall : UDINT
|
||||
@@ -1339,7 +1339,7 @@ END_VAR
|
||||
|
||||
## SlaveTCP.FB_exit
|
||||
|
||||
Lifecycle resource release. Worker is joined by component exit; context hooks close their owned handle. Use public release for application shutdown.
|
||||
Lifecycle resource release. context hooks close their owned handle. Use public release for application shutdown.
|
||||
|
||||
```iecst
|
||||
METHOD FB_exit : BOOL
|
||||
@@ -1359,7 +1359,7 @@ METHOD FB_reinit : BOOL
|
||||
|
||||
## SlaveTCP.serverCall
|
||||
|
||||
Private synchronous server/worker ABI adapter using server staging/status arrays.
|
||||
Private synchronous server ABI adapter using server staging/status arrays.
|
||||
|
||||
```iecst
|
||||
METHOD PRIVATE serverCall : UDINT
|
||||
@@ -1539,32 +1539,3 @@ STRUCT
|
||||
END_STRUCT
|
||||
END_TYPE
|
||||
```
|
||||
|
||||
## ModbusMaster.Worker
|
||||
|
||||
Optional single global worker controller. CPU -1 restores inherited mask; nonnegative selects an OS CPU. Affinity error is explicit. Configure watchdog longer than expected scan gaps.
|
||||
|
||||
```iecst
|
||||
FUNCTION_BLOCK Worker
|
||||
VAR_INPUT
|
||||
in_cpu : DINT := -1; // -1 restores inherited affinity; >=0 selects an OS CPU ID.
|
||||
in_interval : TIME := T#1MS; // Worker sleep 1..20 ms; not a hard real-time guarantee.
|
||||
in_watchdog : TIME := T#2S; // 100 ms..60 s; closes contexts whose FB stops scanning.
|
||||
END_VAR
|
||||
VAR_OUTPUT
|
||||
out_running : BOOL;
|
||||
out_error : BOOL;
|
||||
out_errorCode : UDINT;
|
||||
out_osError : UDINT;
|
||||
out_cpu : DINT := -1;
|
||||
out_cycles : ULINT;
|
||||
END_VAR
|
||||
VAR
|
||||
status : ARRAY[0..23] OF UDINT;
|
||||
configured : BOOL;
|
||||
lastCpu : DINT;
|
||||
lastInterval : TIME;
|
||||
lastWatchdog : TIME;
|
||||
command : UDINT;
|
||||
END_VAR
|
||||
```
|
||||
|
||||
+18
-46
@@ -1,61 +1,33 @@
|
||||
# Build and upgrade to atMb 0.6
|
||||
# Build and upgrade to atMb 0.8
|
||||
|
||||
## Build on ARM64 Linux
|
||||
|
||||
From the repository root:
|
||||
Build from the repository root and clean between architectures:
|
||||
|
||||
```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
|
||||
mkdir -p artifacts/arm64
|
||||
cp component-source/component/out/libCmpAtomModbus.so artifacts/arm64/
|
||||
```
|
||||
|
||||
Run these commands on an ARM64 build machine. On an x86 laptop, ordinary gcc
|
||||
builds x86 output; use the appropriate cross-compiler instead.
|
||||
|
||||
## Cross-compile ARMv7 hard-float on the laptop
|
||||
For the UR2000 ARMv7 hard-float target:
|
||||
|
||||
```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
|
||||
make -C component-source/component \
|
||||
SDKDIR="$(realpath ExtensionSDK)" \
|
||||
CC="arm-linux-gnueabihf-gcc -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard" all
|
||||
```
|
||||
|
||||
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.
|
||||
Use the matching ARM64 compiler for Raspberry Pi/UC20 and check ELF architecture
|
||||
and GLIBC requirements before deployment. The C component is version **0.5.0.0**.
|
||||
|
||||
## CODESYS
|
||||
## CODESYS and online change
|
||||
|
||||
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.
|
||||
1. Create a fresh library project and import `library/atMb-OnlineChange.export`.
|
||||
2. Verify Project Information version **0.8.0.0**, namespace `atMb`, and the
|
||||
existing Standard/SysTime references.
|
||||
3. Add a C Implemented Library object with the rebuilt **0.5.0.0** component.
|
||||
4. Compile the library and update the application reference.
|
||||
5. Test online change while idle, during an active read, during disconnect, and
|
||||
with an active write. Confirm clients reopen and data becomes valid.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
The external ABI signature/API version is unchanged. There is no worker FB,
|
||||
thread-affinity setting, or native worker thread in this release.
|
||||
|
||||
+6
-9
@@ -1,11 +1,8 @@
|
||||
# Migration housekeeping
|
||||
# Release housekeeping
|
||||
|
||||
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.
|
||||
The active release is `atMb-OnlineChange.export`, library version 0.8.0.0, with
|
||||
C component version 0.5.0.0. The old threaded export and ARM binaries are kept
|
||||
under `backup/` for rollback/reference and are not part of the active release.
|
||||
|
||||
`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.
|
||||
No Git commit or push was made. Build outputs and IDE cache files are omitted
|
||||
from the release archive.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Export import correction
|
||||
|
||||
The Worker object in the first threaded export contained an unnamed List element.
|
||||
The generator cleared its attributes as well as its children. This revision
|
||||
restores Name="ChildObjectGuids", preserves an empty child list, and matches the
|
||||
original export envelope (no XML declaration, CRLF line endings).
|
||||
|
||||
All collection elements now retain serialization attributes. XML parsing passes.
|
||||
CODESYS import has not been verified in an IDE. If import still fails, capture the
|
||||
exact error text and whether it occurs before or after the object selection dialog.
|
||||
The C sources and IEC program logic are unchanged by this correction.
|
||||
+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.
|
||||
|
||||
+23
-33
@@ -1,39 +1,29 @@
|
||||
# Validation and remaining qualification
|
||||
# Validation and online-change qualification
|
||||
|
||||
IEC library version: 0.6.0.0. C component version: 0.4.0.0.
|
||||
External C ABI signature/API version are unchanged.
|
||||
Release versions: IEC library **0.8.0.0**, C component **0.5.0.0**. The external
|
||||
ABI signature/API version is unchanged.
|
||||
|
||||
## Passed on the Linux build host
|
||||
Host validation completed:
|
||||
|
||||
- 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.
|
||||
- Strict synchronous C build and bridge capacity/pool/handle/shutdown tests.
|
||||
- Direct and bridge TCP normal, fragmented, timeout, protocol-error, exception,
|
||||
write and reconnect scenarios.
|
||||
- RTU pseudo-terminal frame, CRC, timeout, exception, register/coil write and
|
||||
recovery scenarios.
|
||||
- TCP slave function-code, exception, address and bind-recovery scenarios.
|
||||
- XML export parsing, object references, collection metadata and removal of the
|
||||
threaded Worker object.
|
||||
- C client `FB_reinit` closes its native context before clearing IEC pointers;
|
||||
slave `FB_reinit` closes its listener before clearing its handle.
|
||||
|
||||
## Required before deployment
|
||||
Required on each target before production:
|
||||
|
||||
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.
|
||||
- Rebuild for the exact CPU/toolchain and verify ELF/GLIBC compatibility.
|
||||
- Compile/import the export in CODESYS and embed the matching C component.
|
||||
- Test online change while idle, during an active read, during disconnect and
|
||||
during an active write.
|
||||
- Verify contexts reopen, request pointers are rebuilt, data becomes valid, and
|
||||
no stale socket/listener remains after repeated online changes.
|
||||
|
||||
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.
|
||||
Physical target, prolonged soak, and CODESYS IDE qualification were not available
|
||||
in the host environment. Keep the previous threaded release as rollback only.
|
||||
|
||||
+13
-33
@@ -1,37 +1,17 @@
|
||||
# Worker ABI and lifecycle
|
||||
# Synchronous ABI and online-change lifecycle
|
||||
|
||||
The 16-argument atMbCall_cext ABI and its signature are retained. Calls are serialized
|
||||
by the lifecycle mutex and priority-inheritance engine mutex. The worker accesses
|
||||
fixed C contexts only. Calls may wait briefly for the engine mutex; no claim of
|
||||
hard real-time maximum latency is made for OS/serial-driver operations.
|
||||
There is no native worker thread in atMb 0.8. The existing 16-argument
|
||||
`atMbCall_cext` ABI is retained, and every transport tick runs synchronously in
|
||||
the PLC task that owns the IEC client or slave instance.
|
||||
|
||||
New commands:
|
||||
The C component uses fixed context pools, generation-checked handles and no
|
||||
retained IEC pointers. Request values and results are copied through the ABI
|
||||
buffers during the owning PLC call.
|
||||
|
||||
| Command | Purpose | Parameters / result |
|
||||
|---|---|---|
|
||||
| 12 | Client transaction timing | Normal handle; status[21] duration ms, [22] successful-read age ms (saturated UDINT); other client status fields retained |
|
||||
| 40 | Configure/start global worker | startAddress=CPU+1 (0 inherited mask), connectTimeoutMs=interval 1..20, responseTimeoutMs=watchdog 100..60000 |
|
||||
| 41 | Worker capabilities/status | No start side effect; available even before worker starts |
|
||||
For online change, `FB_reinit` closes the native context first, clears the client
|
||||
handle and all IEC request pointers, and resets active-request state. The next
|
||||
PLC scan recreates the context using the current configuration. Slave blocks
|
||||
close their listener before clearing their native handle.
|
||||
|
||||
Worker status words: [0] capability 0x00040000; [1] running; [2] pthread error;
|
||||
[3] configured CPU+1, 0 inherited mask; [4] interval ms; [5] watchdog ms;
|
||||
[6:7] cycle counter low/high; [8:9] monotonic heartbeat low/high. Remaining words zero.
|
||||
Error 260 reports worker initialization or CPU-affinity failure. Invalid settings
|
||||
return 1. Invalid settings do not change current affinity/timing.
|
||||
|
||||
Client command 5 and server command 34 now refresh the context's scan lease and
|
||||
snapshot state; they do not perform socket reads/writes. All deadline times are
|
||||
sourced from CLOCK_MONOTONIC; the ABI nowMs argument is retained but ignored.
|
||||
Command 6 rejects a new transaction until completion is acknowledged with command
|
||||
10. Completion data cannot be overwritten by another request before acknowledgment.
|
||||
|
||||
Worker shutdown sets a stop flag, releases the mutex, joins the thread, then
|
||||
closes contexts. Generation-tagged handles prevent access to released slots.
|
||||
Watchdog expiry closes transport while preserving the slot and terminal result;
|
||||
refresh resumes it. No worker thread is restarted repeatedly on link failure;
|
||||
existing TCP/RTU state machines retry within it.
|
||||
|
||||
Socket progress is nonblocking. Socket close/configuration calls may also occur
|
||||
synchronously for explicit close/reconfigure commands. These and serial ioctls are
|
||||
OS calls, so target execution-time measurement remains necessary. The worker uses
|
||||
normal SCHED_OTHER, not a high-priority busy loop.
|
||||
The external ABI signature/API version remain unchanged. Component version is
|
||||
0.5.0.0; IEC library version is 0.8.0.0.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
atom
|
||||
ADAM-CODESYS
|
||||
9256
|
||||
639269490056168856
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user