first commit

This commit is contained in:
2026-09-30 21:17:41 -06:00
commit 07244fc761
57 changed files with 30094 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Changes
## Development bundle 0.5 (component/ABI 0.2.0.0)
- Latest compatibility export includes empty registry, bounds and null dispatch checks.
- Related registry loop subtraction uses signed counts.
- Shared mutable scheduler fields use CODESYS input fields; offline method protected.
- C TCP core, status bridge and per-request timeout command 11 included.
- Supplied SDK, original generated C/M4, original export and tests preserved.
- Linux build script, packaging instructions and build metadata included.
Earlier exports are retained for comparison; use AtomModbus-Compatible.export.
+15
View File
@@ -0,0 +1,15 @@
# Compatibility record
| Item | Known evidence |
|---|---|
| IDE | User reports CODESYS 3.5 SP22 |
| Pi | aarch64, 64-bit Linux |
| Installed packages | codesyscontrol 4.16.0.0; codesysedge 4.16.0.0 arm64 |
| Component | User-built ARM64 component 0.2.0.0 loaded successfully |
| Latest wrapper | Dispatch guards added; target verification outstanding |
| Native C tests | Passed on development Linux host |
| Other architectures | Build source provided; not validated |
| Embedded deployment | Official procedure documented; not validated here |
A previous /var/opt/codesys log was stale and is not a current version record.
Record actual OS/libc, runtime, SDK hashes and test results for each release.
+7
View File
@@ -0,0 +1,7 @@
# Ownership and third-party licensing
No license is assigned to Atom's source or original exports by this bundle.
Choose your desired distribution terms before publishing.
The supplied ExtensionSDK remains subject to its CODESYS copyright and applicable
license terms. Preserve notices; verify redistribution rights before distributing
the SDK outside your organization. This file is not a license grant.
+55
View File
@@ -0,0 +1,55 @@
badefa63129a93dfe5aa2fc25e8ff72fb256655d44141199e68a9b6db40a8a98 .gitignore
0bf50be5921642c5fded901891709b227c9f53e5f598023379b2fb0e7d3ec123 CHANGELOG.md
3325c8766682bccf1de680f5cd75c8e64670ed12f15ea2388f4926da567caaf9 COMPATIBILITY.md
7e78eb0048fb62a249151dac1d8d0cc37df5eba5f020f844ee2c9465c88764a8 LICENSE-NOTES.md
0b4a11e0fa640bc39f8352b45110ceaba951033dbdd8e53438d770081e62cc58 Makefile
293b1752acad04ae5e24b8a5e646ccf9f2e26c89b74cee8a46f163cb1e122051 README.md
0f705c0e574b205918d14b0346a4b1f359f92606c18fd3b3c4bd16d93bbf2fa7 build/README.md
650346532973f9a6b526d318a5de513e1c8c099c00b41238e0ce8cb5de6e0ffd codesys/BUILD_ON_PI.md
b39ded5506bfdcb1aea7c8c97161cb8ea23d10a1f734df82bcc4fb96f7d87319 codesys/INTEGRATION.md
098db0f8c4609bf3f09b44a0012459f0ff7933938c5c9b79a5e289a5cedc8377 codesys/PI_SETUP.md
f426ebafcdd8e19b24a90354049b6b8559d9be597b2f9cac9f7649b2cd022c8d codesys/PLC_PRG_declaration.st
661c39cf7c5cb1900394753a91f8e53f806cf7e1ed5244a1e3562fa14a70bfce codesys/PLC_PRG_implementation.st
c334109ec949d2342bdee60cc18c025377a0d32397a96dbc2b1ccd9a29f7b8a7 codesys/PROJECTS-TO-ADD.md
d46196b1e67d8318bee443afa7ea7f4820680775219c519e6d3600e78e70cf12 codesys/STATUS.md
69570458de6762e5b63908adec323619ea7ccfea2a33b45613ead860c06fc171 codesys/atMbCall_cext.st
19fb0ff04eb6742f5e21cbc2956fd1adce9601422846e2bed19704ffd090f79c compat/All_Source_Review.st
4cb458ed8376857dfe644c506046183c8d6ebaac8ee23414a54cdbf2e576917d compat/AtomModbus-Compatible-DispatchFix.export
c16529ebd161704feb6806a0262fdbbc0d2b2da6a1aaaf2fc87ec555460f40fd compat/AtomModbus-Compatible-Fixed.export
4cb458ed8376857dfe644c506046183c8d6ebaac8ee23414a54cdbf2e576917d compat/AtomModbus-Compatible.export
8f5e02ffbc24f38f28b4f4a5bb6b6cee6289bff03aecb0fe7ed76749f0ed93cf compat/Original_HomeCodesys.export
be920ad07708256ff86c7651e8558899c65e9ff2c4378a8fca7d3e4f56c17952 compat/README.md
7eb25c89d948a565a7a5d6941df07945f7d70239d64f5a4e3f2f76692b11b1a8 component/CmpAtomModbus.c
30a8d7d5dfd372d171d4acdafd0b77cedca684e0b6e661ae1ab67e2627d35708 component/CmpAtomModbusItf.m4
513586eed183bcc53cdf008c483da03feff6b5cb0f3ed3eff17de1462def12c3 component/CmpFrame.c
51ac804780cf85318d8d75b42d97838afcc24428f6ddcc9e68f554aca139d2a5 component/makefile
941271e770b5dfb4367b752aef1af6b6d7a7b7245ea82627c1151a8090c7099c dist/README.md
d46196b1e67d8318bee443afa7ea7f4820680775219c519e6d3600e78e70cf12 docs/API.md
1edd488b4480ebe7d320f4c09b945aff55ce58897783b31efd891635158c644a docs/BUILD.md
5b7ffb0075cd4eb663b9d30414dd92ed41b3da28e472a053930804227ddd5ccc docs/PACKAGING.md
aaa2d2335812fc15897b7d2ed861f87a75c0fd8420b89681aedc131310eaf3fd docs/TESTING.md
5630f6f9eca82ff55e3b9515f22b6a9a00d3df616fc0594d331892df01d09e27 docs/TROUBLESHOOTING.md
c362c04636cb2cfc00eec8800f0aa9631709cd9eba76b66332f61a988823a055 generated/CmpAtomModbus.c
fa9b00a24ff9f24b63c9a08b8eba4d585a23f4fd6e19f990ec36dcc70f745427 generated/CmpAtomModbusItf.m4
0ac6e4fe82a82d1bd27ff20dec2f18820c0e0a5057cd0cb1a8020f6dc506603f sdk/ExtensionSDK/README
1cc355e875e572de3829d28bff9c100a4eecbfd741d938654be97881023c84d5 sdk/ExtensionSDK/include/CmpErrors.h
2010310fbeb8b4734e638610d2f6dc01b600fe42b8b104155806ef0784e95f2d sdk/ExtensionSDK/include/CmpItf.h
a8de018bfde8d10d3f281bb45de03c4b4f3cd28466eb0fa0981086bc4033a69c sdk/ExtensionSDK/include/CmpStd.h
212fcd438a0ad38712d575521c34ed875ac86353e543c737aec3770386503b14 sdk/ExtensionSDK/m4/Exports.m4
06b55c74620a6247bf4ddcf69f7373255f4677b2c2b7ef1ed0ac02ba0407f94e sdk/ExtensionSDK/m4/Implementation.m4
8fb9f4e77e2f2c4df494b6199bfd16ab2ed4257d76c735e46460f837d6e22163 sdk/ExtensionSDK/m4/Util.m4
814d06ca83d775f3bcb93f80859602b6c1debe9511b3d42ef337948bd2e2b5d0 sdk/ExtensionSDK/makefile
4fc8013238169e924ee2eb5dd6c2b39c9d274f4c7c2219dc0b9a530687097519 sdk/ExtensionSDK/makefile_template
aa7ccad11508ccbf4f9b24aa8db8f22392f9d723ae3af5cbfa67e38271cfbac3 sdk/ExtensionSDK/src/CmpFrame.c
151c7eed44289b30dc60121937846172e54abda6af19c643cdbe5afc887904f4 sdk/ExtensionSDK-original.zip
4cb1f22eb90c8ed8485132b5273f8e189ed142a66d03e73b5b296d6a3f9fdebf src/atom_bridge.c
a65faab8dd4ca545df89d0a756637c8630d67a3a5ed3bc75a0de3b3b8fb08a43 src/atom_bridge.h
b90ed7d3a8c2b5b38654b68d07e3a5f0b02ed39f0cef2b45068d8c1f654b4e39 src/atom_mb.c
db2e5789ececf935a2a8968982792efaafebc8ab4bb53b6f9374138e528c2cec src/atom_mb.h
d770393632f9d3d1acddf229f562148648792398a39d90851aa529d2923f8382 tests/bridge_driver.c
50bb7854c3a966c46a583fba951c1d3a605bf9d1739ac83685322d9a7b740c4d tests/driver.c
3af1a5f71d69bae2abda3de9dba31740ad254d7157d702847fb921fc2b64df3d tests/test_bridge.c
0cf8f8fefda6376cacf0c56729d24aaa76241e807b0dee0b36001b3ad3cea509 tests/test_tcp.py
dac1df481f09c954e0ff7af793238f54d11156e8b5060605979d300dde47dcf4 tools/build-linux.sh
a38bcadbe1ca1cd047ce2c54cc8e52428196f6552e4b20c62eba90c2bc823309 tools/build_compat_export.py
691eb3acc3dd58d0114ae2f0cb9de611be50d364d312ab5383a797d3ecadbeab tools/prepare_component.py
+15
View File
@@ -0,0 +1,15 @@
CC ?= cc
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
tests/bridge_driver: tests/bridge_driver.c src/atom_bridge.c src/atom_bridge.h src/atom_mb.c src/atom_mb.h
$(CC) $(CFLAGS) -Isrc tests/bridge_driver.c src/atom_bridge.c src/atom_mb.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
$(CC) $(CFLAGS) -Isrc tests/test_bridge.c src/atom_bridge.c src/atom_mb.c -o $@
tests/driver: tests/driver.c src/atom_mb.c src/atom_mb.h
$(CC) $(CFLAGS) -Isrc tests/driver.c src/atom_mb.c -o $@
clean:
rm -f tests/driver tests/test_bridge tests/bridge_driver
+32
View File
@@ -0,0 +1,32 @@
# Atom Modbus Library development kit
Start here. This folder contains the C TCP engine, CODESYS runtime component,
original generated ABI files, compatibility export, supplied ExtensionSDK,
regression tests and build documentation.
## Build on the Pi
Install build-essential, make, m4, sed, dos2unix and python3. In this folder run:
```sh
./tools/build-linux.sh linux-aarch64
```
The output and build metadata are placed in dist/0.2.0/linux-aarch64/.
To run the native socket regression tests: `make test`.
## CODESYS
Import compat/AtomModbus-Compatible.export into a copy of your existing Atom
library. This is the latest dispatch-fixed export. Read compat/README.md for
interfaces, dependencies and ownership rules. Keep CmpAtomModbus as the external
function library and add it with namespace CmpAtomModbus.
No editable .library projects, compiled libraries or ARM64 .so were uploaded
in this session, so none are included. Save your actual CmpAtomModbus.library
and Atom library projects in codesys/ when you have them. Exports are backups,
not substitutes for the complete library projects and their metadata.
Read docs/BUILD.md, docs/PACKAGING.md and docs/TROUBLESHOOTING.md.
This bundle is development revision 0.5; ABI/component version remains 0.2.0.0.
The CODESYS changes still require compilation and target testing.
+1
View File
@@ -0,0 +1 @@
Temporary build workspace. Do not store authoritative source here.
+87
View File
@@ -0,0 +1,87 @@
# Ready-to-build component, generated interface validated
The supplied CmpAtomModbus.c and CmpAtomModbusItf.m4 were inspected and the C
stub implemented. The M4 file is unchanged. External registration metadata:
signature 0x709EE65B, version 0x00020000 (0.2.0.0).
This source bundle is revision 0.3; the IEC library/component version remains
0.2.0.0 to match your generated interface. Do not change it independently.
## Build
Copy/extract this source folder and your ExtensionSDK onto the Pi:
```text
~/modbus-build/atom-modbus/
~/modbus-build/ExtensionSDK/
```
Then:
```sh
sudo apt-get install build-essential m4 sed dos2unix make
cd ~/modbus-build/atom-modbus
make -C component SDKDIR="$(realpath sdk/ExtensionSDK)" all
file component/out/libCmpAtomModbus.so
nm -D component/out/libCmpAtomModbus.so | sed -n '/ComponentEntry/p; /atmbcall_cext/p'
```
Expected: AArch64 shared object; ComponentEntry and atmbcall_cext exported.
You do NOT need to run the preparation script: component/ is already prepared.
The original generated files are retained in generated/ for reference.
## Install during a bench-test maintenance window
Stop your PLC application safely first. Keep a backup of the configuration.
```sh
sudo cp /etc/CODESYSControl_User.cfg /etc/CODESYSControl_User.cfg.before-atom-modbus
sudo /etc/init.d/codesyscontrol stop
sudo cp component/out/libCmpAtomModbus.so /usr/lib/libCmpAtomModbus.so
sudo nano /etc/CODESYSControl_User.cfg
```
Under the existing [ComponentManager] section add:
```ini
Component.N=CmpAtomModbus
```
Replace N with an unused numeric component index. Do not add a duplicate section
or overwrite another component. Development component ID is 0xffff2001; ensure
it does not collide with another custom component.
```sh
sudo /etc/init.d/codesyscontrol start
sudo tail -n 80 /var/log/codesyscontrol.log
```
Verify the component loads with no interface/signature/version errors. Save and
install your CmpAtomModbus IEC library in CODESYS' Library Repository, then add
it to a separate Raspberry Pi test project's Library Manager.
## Read-only PLC test
Create an ST PROGRAM PLC_PRG. Add the SysTime library. Paste
PLC_PRG_declaration.st in its declaration pane and
PLC_PRG_implementation.st in its implementation pane. Ensure PLC_PRG is called
by one cyclic task, e.g. 10 ms. Namespace-qualify atMbCall_cext if required.
Set the IP, unit ID and zero-based start address for a device with at least ten
readable holding registers. Download, start and set run=TRUE in the watch window.
Inspect status[1] (state), callResult, registers, readDone, successfulReadCount,
dataStale and requestErrorCount. Unplug and restore Ethernet or restart the
device to exercise recovery. status[11] counts additional connection attempts.
Set run=FALSE and let the program scan until handle=0 before stopping,
downloading or making online changes. Automatic application lifecycle cleanup
and the original atMb scheduler migration are still pending; this is a bench
program, not yet a production replacement. RTU is not implemented.
## Verification scope
Native engine/bridge tests passed locally. The real generated parameter struct
and function implementation were compiled against the supplied SDK headers
on the x86-64 development host. Full SDK/M4 registration build and execution
on the ARM64 Pi remain to be verified. No precompiled Pi binary is included.
The ST example has not been compiled in the CODESYS IDE here.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
atom
ADAM-CODESYS
5068
639263998361642755
+88
View File
@@ -0,0 +1,88 @@
# Integration into atomLibrary
This package contains a working C TCP core and native C bridge, not a compiled
CODESYS library. Target supplied: Raspberry Pi ARM64, runtime 4.16.0.0,
development environment SP22. ExtensionSDK inspected. See PI_SETUP.md for
the original generated-interface step. Real generated C/M4 files are now
supplied and the stub is implemented; use BUILD_ON_PI.md for the prepared build.
Do not map `am_client` directly to an IEC STRUCT: C int, size_t, alignment and
pointer sizes vary by target. Use SDK-generated external function signatures.
## Implemented bridge API
The separate operations below are implemented as commands through ONE external
function, `atMbCall_cext`. See STATUS.md for command IDs and the status array.
The table describes operations rather than separate exported function names.
The bridge uses a fixed pool of contexts and returns generation-checked UDINT handles.
Zero is invalid. Use functions named with `_cext` for the Control SL Extension SDK:
| Function | Purpose |
|---|---|
| atMbOpen_cext | Allocate a fixed context slot; configure numeric IPv4 and port |
| atMbConfigure_cext | Set connect, response and reconnect times in milliseconds |
| atMbEnable_cext | Enable/disable recovery |
| atMbTick_cext | Advance one nonblocking step using monotonic time |
| atMbSubmit_cext | Validate and copy request/write data into the context |
| atMbGetStatus_cext | Copy explicitly sized status fields to IEC outputs |
| atMbCopyRead_cext | Copy validated read result with destination capacity check |
| atMbReconnect_cext | Cancel active transaction and create a fresh connection |
| atMbClose_cext | Close socket and release pool slot |
Only pass pointers during a synchronous call. Never store pointers into IEC
application memory in C across scans or online changes. All operations for one
handle must run from one task. Add target-specific lifecycle handling for stop,
reset, download and online change; close or invalidate old handles before reuse.
## Map existing function blocks
Keep the existing names and data interfaces, with these internal changes:
- `atMbClientTCP`: own the C handle and call Tick once per scan. Keep
`in_ipAddress`, `in_port`, request registration and priority scheduling.
Changing the endpoint cancels the transaction and opens the new endpoint.
- `atMbClient`: schedule only while Ready and no request is pending. Complete
the prior request and copy its read data BEFORE submitting the next request.
Preserve priority ageing/minimum delay, adding explicit enabled eligibility
and starvation checks. Enforce the existing 100-request registration limit.
- `atMbReqBase`: snapshot parameters when submitted; validate offset plus
quantity against destination capacity. Reject overflow rather than silently
truncate. Quantity zero means disabled at the wrapper level.
- `atMbReadCoil` / `atMbReadDiscrete`: FC01 / FC02; convert unpacked C uint16_t
values to the actual IEC BOOL array type when copying.
- `atMbReadHolding` / `atMbReadInput`: FC03 / FC04, WORD values.
- `atMbWriteCoil` / `atMbWriteHolding`: FC15 / FC16; preserve `writeNow` semantics.
Snapshot write values on submit. A failed write is not automatically replayed;
the application must explicitly decide whether to reissue it.
- `atMbRequestStats`: replace `ModbusFB.Error` with your own transport-error enum;
keep Modbus exception separately. Keep counts, intervals and HMI indicators.
- `atMbClientRTU`: retain as a separate future transport; this C core does not
implement serial RTU framing, CRC, timing or serial-port control.
## Status additions
Client: state, connection attempt count, reconnect count, last transport error,
OS socket error, active request index and last successful response time.
Request: Busy/Done/Error, result, exception, consecutive successes/failures,
last successful read time, hasValidData, dataAgeMs, stale and writeUncertain.
Use connection state for transport readiness. Use per-request successful read
timestamps for freshness; another successful request must not refresh stale
data. A valid exception proves the peer responded but does not validate data.
Keep startup/never-read distinct from stale. The C core exposes a CLIENT-wide
last_success timestamp; per-request timestamps belong in the scheduler wrapper.
## Deployment gate
1. Confirm PLC model, CODESYS runtime version, operating system and architecture.
2. Confirm vendor support for custom C components; Control SL SDK support must
not be assumed on a vendor-supplied PLC runtime.
3. Generate the external declarations/stubs in the matching SDK, implement the
bridge, compile for the actual target and package its component.
4. Compile the IEC wrapper in CODESYS and exercise stop/start and online change.
5. Bench-test unplug/replug, device restart, long outage, endpoint change, full
request list, priority fairness and uncertain writes before site deployment.
References:
- https://content.helpme-codesys.com/en/CODESYS%20Control/_rtsl_extension_extension_sdk.html
- https://www.modbus.org/modbus-specifications
+72
View File
@@ -0,0 +1,72 @@
# Raspberry Pi integration — known target
UPDATE: Real generated C/M4 files have been supplied and component/ is prepared.
Start with BUILD_ON_PI.md; the interface-generation steps below are historical
reference and do not need to be repeated.
User-reported: aarch64, 64-bit userland, codesyscontrol 4.16.0.0;
development environment CODESYS 3.5 SP22. Supplied ExtensionSDK inspected.
The runtime package reports `all`, so verify the actual component/runtime
binary architecture on the Pi after building. No ARM binary is included here.
## Generate the official CODESYS interface
1. Create an **Empty library** named `CmpAtomModbus.library`. In Project
Information set Title = `CmpAtomModbus`, Version = `0.2.0.0`.
2. Add one Function named `atMbCall_cext`, return type UDINT. Paste the matching
declaration from atMbCall_cext.st (the FUNCTION line may already exist in
the declaration editor; do not duplicate it).
3. Right-click the function > Properties > Build > **External implementation**.
4. Build > **Generate Runtime System Files**. Select **M4 interface file** and
**C stub file**. Component name must be `CmpAtomModbus`.
5. Send the generated `CmpAtomModbus.c` and `CmpAtomModbusItf.m4` back for actual
interface validation. The generated signature/checksum must be preserved;
a plain `.so` exported symbol alone is insufficient for the IEC call.
## Prepare and build on Pi after generating those files
Keep your ExtensionSDK directory outside this source folder. Install host build
tools on the Pi if missing:
```sh
sudo apt-get install build-essential m4 sed dos2unix make
```
From the atom-modbus source folder, with the generated files in `generated/`:
```sh
python3 tools/prepare_component.py generated/CmpAtomModbus.c generated/CmpAtomModbusItf.m4 --sdk ../ExtensionSDK
make -C component SDKDIR="$(realpath sdk/ExtensionSDK)" all
file component/out/libCmpAtomModbus.so
```
The last output must identify an AArch64 ELF shared object. Native compilation
on the Pi avoids guessing a cross-toolchain or glibc version. The preparation
script validates field names from the real generated M4 struct and preserves
the generated interface. Its parser has only been exercised with a synthetic
fixture until the real generated files are supplied.
The generated component adds cleanup at `CH_EXIT`, after runtime tasks have
stopped. This does not yet handle application-only lifecycle changes.
## Install only after final review/bench test
The SDK's documented method is to place the component in `/usr/lib`, stop the
runtime and add `Component.N=CmpAtomModbus` under `[ComponentManager]` in
`/etc/CODESYSControl_User.cfg`, choosing an UNUSED component index. Restart the
runtime and inspect `/var/log/codesyscontrol.log`. Use a maintenance
window: a runtime restart stops the PLC application.
Alternatively, package it using the C-Implemented Library object for the exact
Raspberry Pi device target. That packaging still requires CODESYS IDE work.
## First bench exercise
Use a separate test application/task. Open -> configure -> enable -> tick every
scan until Ready. Submit an FC03 read. Tick until Done/Error. On Done, CopyRead,
then Acknowledge. On Error, record diagnostics, Acknowledge and allow recovery.
Do not submit writes during initial connection testing. Use a monotonic
millisecond clock (e.g. SysTimeGetUs value divided by 1000).
Preserve your existing production atomLibrary until the IEC wrapper and
stop/start/download/online-change handling have passed target tests.
+30
View File
@@ -0,0 +1,30 @@
(* Bench test: create PROGRAM PLC_PRG, ST language. Add the SysTime library.
Add CmpAtomModbus as a library; qualify atMbCall_cext with its namespace if
the Library Manager requires it. Paste this in the DECLARATION pane. *)
PROGRAM PLC_PRG
VAR
run : BOOL := FALSE; (* Set TRUE in watch window to start. FALSE closes. *)
ipAddress : DWORD := 16#C0A80132; (* EDIT: 192.168.1.50 *)
tcpPort : UINT := 502;
unitId : BYTE := 1;
startAddress : UINT := 0; (* Zero-based wire address, not 40001. *)
registers : ARRAY[0..9] OF WORD;
status : ARRAY[0..23] OF UDINT;
handle : UDINT := 0; (* Do NOT retain/persist this. *)
command : UDINT := 0;
callResult : UDINT;
timeResult : UDINT;
nowUs : SYSTIME;
nowMs : ULINT;
lastReadMs : ULINT;
readDone : BOOL;
requestError : BOOL;
hasValidData : BOOL;
dataStale : BOOL := TRUE;
lastRequestResult : UDINT;
lastException : UDINT;
lastSocketError : UDINT;
requestErrorCount : UDINT;
successfulReadCount : UDINT;
pollDelay : TON;
END_VAR
+93
View File
@@ -0,0 +1,93 @@
(* Paste in IMPLEMENTATION pane. This only issues FC03 reads.
Leave run FALSE until the component is loaded and target is configured.
Set run FALSE and allow scans to execute before stopping/downloading.
This bench program does not implement automatic disposal on online change. *)
readDone := FALSE;
requestError := FALSE;
timeResult := SysTimeGetUs(nowUs);
IF timeResult <> 0 THEN
RETURN;
END_IF;
nowMs := nowUs / 1000;
dataStale := NOT hasValidData;
IF hasValidData THEN
dataStale := (nowMs - lastReadMs) > 2000;
END_IF;
pollDelay(IN := (command = 5) AND (status[1] = 3) AND (status[2] = 0),
PT := T#200MS);
IF NOT run THEN
IF handle <> 0 THEN
command := 2; (* Close *)
ELSE
command := 0;
END_IF;
ELSIF (handle = 0) AND (command = 0) THEN
command := 1; (* Open *)
END_IF;
IF command = 0 THEN
RETURN;
END_IF;
callResult := atMbCall_cext(
command := command,
handle := handle,
ipAddress := ipAddress,
port := tcpPort,
unitId := unitId,
functionCode := 3,
startAddress := startAddress,
quantity := 10,
pData := ADR(registers[0]),
dataCapacity := 10,
pStatus := ADR(status[0]),
statusCapacity := 24,
nowMs := nowMs,
connectTimeoutMs := 2000,
responseTimeoutMs := 1000,
retryDelayMs := 1000
);
IF callResult <> 0 THEN
IF callResult = 256 THEN (* Stale handle: obtain a fresh context. *)
handle := 0;
command := 0;
ELSIF (command = 6) AND ((callResult = 2) OR (callResult = 3)) THEN
command := 5; (* Busy/offline: return to tick. *)
ELSE
(* Preserve callResult for diagnostics. Turning run off closes context. *)
run := FALSE;
END_IF;
RETURN;
END_IF;
CASE command OF
1: handle := status[0]; command := 9; (* Configure *)
9: command := 3; (* Enable *)
3: command := 5; (* Tick *)
5:
IF status[3] <> 0 THEN
command := 7; (* Copy validated read result before acknowledge. *)
ELSIF status[4] <> 0 THEN
lastRequestResult := status[5];
lastException := status[8];
lastSocketError := status[7];
requestErrorCount := requestErrorCount + 1;
requestError := TRUE;
command := 10;
ELSIF (status[1] = 3) AND pollDelay.Q THEN
command := 6; (* Submit *)
END_IF;
6: command := 5;
7:
lastReadMs := nowMs;
hasValidData := TRUE;
dataStale := FALSE;
readDone := TRUE;
successfulReadCount := successfulReadCount + 1;
command := 10; (* Acknowledge *)
10: command := 5;
2: handle := 0; command := 0; hasValidData := FALSE; dataStale := TRUE;
END_CASE;
+1
View File
@@ -0,0 +1 @@
Save your editable CmpAtomModbus.library and Atom library projects here, including project information and references.
+54
View File
@@ -0,0 +1,54 @@
# Bridge contract
Return: 0 = accepted, 1..10 = am_error values in atom_mb.h.
256 = invalid/stale handle, 257 = pool full, 258 = insufficient/null buffer,
259 = unknown command. Always inspect return codes. A rejected call is not a
completed Modbus request.
Commands: 1 Open, 2 Close, 3 Enable, 4 Disable, 5 Tick, 6 Submit,
7 CopyRead, 8 Reconnect, 9 Configure, 10 Acknowledge,
11 Set request timeout (responseTimeoutMs only; permitted while not busy).
Configure only while disabled; all three timeouts must be nonzero.
Open receives an IPv4 DWORD in human octet order: 16#C0A80132 = 192.168.1.50.
Open returns its handle in status[0], NOT in the function return value.
Close returns status[0]=0; old handles fail even when their slot is reused.
Capacity values count WORD/UDINT elements, not bytes. Pass separate buffers.
CopyRead copies `quantity` WORDs; coils/discretes are unpacked WORD 0/1.
| Status index | Meaning |
|---|---|
| 0 | Handle |
| 1 | State: 0 Disabled, 1 Retry, 2 Connecting, 3 Ready, 4 Sending, 5 Receiving |
| 2 | Busy |
| 3 | Done |
| 4 | Error |
| 5 | Latest transaction result |
| 6 | Last failure, retained after success |
| 7 | OS socket error |
| 8 | Modbus exception |
| 9 | Write outcome uncertain |
| 10 | Connection attempts |
| 11 | Attempts after first attempt |
| 12 | Completed successful requests |
| 13 | Failed requests |
| 14 | Has successful transaction |
| 15 | Last success timestamp, low 32 bits |
| 16 | Last success timestamp, high 32 bits |
| 17 | Quantity of latest request |
| 18 | Function code |
| 19 | Unit ID |
| 20 | Enabled |
| 21 | Connect timeout milliseconds |
| 22 | Whole-request timeout milliseconds |
| 23 | Retry delay milliseconds |
The PLC wrapper consumes Done/Error then Acknowledge before submitting a new
request. For Done reads, call CopyRead BEFORE Acknowledge. Completed read data
is retained internally but CopyRead requires Done. For writes, do not replay
an uncertain result without an application decision.
All calls to the entire bridge pool must originate from ONE IEC task. Contexts
and the pool are not thread-safe. Handles must not be RETAIN/PERSISTENT. Closing
on program disposal and invalidating on application reset/download remain PLC
wrapper responsibilities. Component unload cleanup alone does not cover an IEC
application stop or download.
+23
View File
@@ -0,0 +1,23 @@
(* Create FUNCTION atMbCall_cext returning UDINT, paste this declaration.
Properties > Build > External implementation must be selected.
Library filename and Project Information title: CmpAtomModbus.
This declaration is the SINGLE external entry point for the C engine. *)
FUNCTION atMbCall_cext : UDINT
VAR_INPUT
command : UDINT;
handle : UDINT;
ipAddress : DWORD;
port : UINT;
unitId : BYTE;
functionCode : BYTE;
startAddress : UINT;
quantity : UINT;
pData : POINTER TO WORD;
dataCapacity : UDINT;
pStatus : POINTER TO UDINT;
statusCapacity : UDINT;
nowMs : ULINT;
connectTimeoutMs : UDINT;
responseTimeoutMs : UDINT;
retryDelayMs : UDINT;
END_VAR
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+137
View File
@@ -0,0 +1,137 @@
# Existing atMb interface backed by the C TCP engine
Import AtomModbus-Compatible.export into a BACKUP COPY of your existing
atomLibrary, accepting replacement of the matching ModbusMaster objects.
This preserves the other Atom helpers referenced by the original code.
Do not import it into your small CmpAtomModbus external-function library.
In the copied atomLibrary's Library Manager add your CmpAtomModbus 0.2.0.0
library. Ensure its namespace/alias is exactly `CmpAtomModbus`, as the wrapper
calls `CmpAtomModbus.atMbCall_cext`. Keep existing dependencies including
ModbusFB, SysTime, the standard timer library and your other Atom helpers.
ModbusFB remains for legacy enum types and RTU; TCP no longer creates or calls
its client/request objects.
The native export preserves all 78 original object IDs and adds 10 methods.
Review source is in All_Source_Review.st. Original_HomeCodesys.export is the
unchanged supplied original. The export's XML/schema structure and public
declaration preservation have been checked, but the revised IEC code has NOT
been compiled in CODESYS here. Compile the copied atomLibrary and test before
replacing the installed production version.
## Same application calls
Your export's exact request names include `Req`:
```iecst
VAR
mbClient : atMbClientTCP;
mbRead : atMbReqReadHolding;
mbWrite : atMbReqWriteHolding;
holding : ARRAY[0..9] OF UINT;
setpoints : ARRAY[0..1] OF UINT;
END_VAR
```
```iecst
mbClient(
in_ipAddress := To_IPAddress(192,168,1,50),
in_port := 502,
in_timeout := T#1S
);
mbRead(
client := mbClient,
in_unitId := 1,
in_startAddr := 0,
in_quantity := 10,
in_dataOffset := 0,
in_minDelay := T#200MS,
data := holding
);
mbWrite(
client := mbClient,
in_unitId := 1,
in_startAddr := 100,
in_quantity := 2,
in_dataOffset := 0,
in_minDelay := T#1S,
data := setpoints
);
// mbWrite.writeNow(); // Existing method boosts priority, respecting min delay.
```
Only use mapped register ranges for your device; the write example is an API
example, not part of the read-only bench test. Existing names, input defaults,
BOOL/UINT open arrays, client reference, stats properties and writeNow are kept.
Use your existing library namespace prefix if required by Library Manager.
`in_dataOffset` remains an ACTUAL ARRAY INDEX as in the old implementation,
not a relative offset from LOWER_BOUND. Native capacity clipping now considers
that starting index. Bad indices set out_parameterError and disable scheduling
until corrected. Quantity zero disables a request. Protocol-excessive quantities
are rejected with a request parameter error. Don't change the client reference
of a registered request while running; request registrations are persistent.
## Added diagnostics/options (old call sites need no new arguments)
Client: out_connected, out_connectionState, out_lastNativeError,
out_socketError, out_reconnectCount, out_registrationFull.
State: 0 disabled, 1 retry, 2 connecting, 3 ready, 4 sending, 5 receiving.
Request: out_nativeError, out_socketError, out_writeUncertain, out_validData,
out_dataAgeMs, out_stale, out_parameterError. Existing stats remain available.
Extra TCP inputs: in_enable (TRUE), in_connectTimeout (2s),
in_reconnectDelay (1s), in_holdUncertainWrites (FALSE).
Extra request input: in_staleAfter (2s).
Priorities/minimum polling delays and cyclic writes are preserved. The C engine
never automatically replays a failed packet, but the IEC scheduler submits a
fresh snapshot on the next eligible write cycle, as the original code does.
For command writes where that is inappropriate, set in_holdUncertainWrites=TRUE:
an uncertain write holds that request until its existing writeNow() is called.
A valid Modbus exception counts as responsive communication, but does not
refresh read-data freshness. A client endpoint change closes the old context,
cancels its transaction and invalidates its old read data.
The client processes its registered requests, as before. Request FB calls bind
their arrays/register the requests; they must still run every scan. After an
online change registrations are rebuilt by those calls. The native base's
FB_reinit resets in-flight state and TCP FB_exit closes the C context when
disposed. These lifecycle paths require target testing. An application STOP
alone does not guarantee FB_exit; set in_enable=FALSE and run one scan (or call
release()) before STOP/download. Re-enable afterwards.
All native clients/requests must be owned by ONE IEC task because the C pool
is not thread-safe. Up to 16 native clients and 100 registered requests/client.
Do not retain/persist client handles. Arrays/instances must live for the client
lifetime. No IEC data pointer is held in the C engine between calls.
## Rebuild the Pi component once
This wrapper uses new command 11 for per-request timeouts. Rebuild the included
component; do not use the previous .so. No external function declaration,
checksum or library interface regeneration is needed. Version stays 0.2.0.0.
```sh
cd ~/modbus-build/atom-modbus
make -C component SDKDIR="$(realpath sdk/ExtensionSDK)" clean
make -C component SDKDIR="$(realpath sdk/ExtensionSDK)" all
sudo /etc/init.d/codesyscontrol stop
sudo cp component/out/libCmpAtomModbus.so /usr/lib/
sudo /etc/init.d/codesyscontrol start
```
The stop/start requires the same safe bench maintenance conditions as before.
The existing ComponentManager entry stays valid. Add the copied/compiled Atom
library to a test application, use only the read block initially and verify
disconnect/reconnect, per-request timeouts, priorities and lifecycle handling.
## Validation
Public input/output/in-out declarations from the original export are retained.
The C engine and bridge regression tests cover TCP recovery, framing and
exceptions. The new per-request timeout command is exercised by the TCP bridge
driver. This does not substitute for CODESYS compile, target scheduler tests or
production commissioning.
+30
View File
@@ -0,0 +1,30 @@
#include "../src/atom_bridge.h"
#include "CmpStd.h"
#include "CmpErrors.h"
#include "CmpItf.h"
#include "CmpAtomModbusDep.h"
void CDECL CDECL_EXT atmbcall_cext(atmbcall_cext_struct *p)
{
p->atMbCall_cext = ab_call(
p->command,
p->handle,
p->ipAddress,
p->port,
p->unitId,
p->functionCode,
p->startAddress,
p->quantity,
p->pData,
p->dataCapacity,
p->pStatus,
p->statusCapacity,
p->nowMs,
p->connectTimeoutMs,
p->responseTimeoutMs,
p->retryDelayMs);
}
#include "../src/atom_mb.c"
#include "../src/atom_bridge.c"
+70
View File
@@ -0,0 +1,70 @@
/**
* <interfacename>CmpAtomModbus</interfacename>
* <description></description>
*
* <copyright></copyright>
*/
SET_INTERFACE_NAME(`CmpAtomModbus')
/** EXTERN LIB SECTION BEGIN **/
/* Comments are ignored for m4 compiler so restructured text can be used. changecom(`/*', `*/') */
#ifdef __cplusplus
extern "C" {
#endif
/**
* <description>
* Create FUNCTION atMbCall_cext returning UDINT, paste this declaration.
* Properties > Build > External implementation must be selected.
* Library filename and Project Information title: CmpAtomModbus.
* This declaration is the SINGLE external entry point for the C engine.
* </description>
* <element name="atMbCall_cext" type=OUT></element>
* <element name="command" type=IN></element>
* <element name="handle" type=IN></element>
* <element name="ipAddress" type=IN></element>
* <element name="port" type=IN></element>
* <element name="unitId" type=IN></element>
* <element name="functionCode" type=IN></element>
* <element name="startAddress" type=IN></element>
* <element name="quantity" type=IN></element>
* <element name="pData" type=IN></element>
* <element name="dataCapacity" type=IN></element>
* <element name="pStatus" type=IN></element>
* <element name="statusCapacity" type=IN></element>
* <element name="nowMs" type=IN></element>
* <element name="connectTimeoutMs" type=IN></element>
* <element name="responseTimeoutMs" type=IN></element>
* <element name="retryDelayMs" type=IN></element>
*/
typedef struct tagatmbcall_cext_struct
{
RTS_IEC_UDINT command;
RTS_IEC_UDINT handle;
RTS_IEC_DWORD ipAddress;
RTS_IEC_UINT port;
RTS_IEC_BYTE unitId;
RTS_IEC_BYTE functionCode;
RTS_IEC_UINT startAddress;
RTS_IEC_UINT quantity;
RTS_IEC_WORD *pData;
RTS_IEC_UDINT dataCapacity;
RTS_IEC_UDINT *pStatus;
RTS_IEC_UDINT statusCapacity;
RTS_IEC_ULINT nowMs;
RTS_IEC_UDINT connectTimeoutMs;
RTS_IEC_UDINT responseTimeoutMs;
RTS_IEC_UDINT retryDelayMs;
RTS_IEC_UDINT atMbCall_cext;
} atmbcall_cext_struct;
DEF_API(`void',`CDECL',`atmbcall_cext',`(atmbcall_cext_struct *p)',1,0x709EE65B,0x00020000)
#ifdef __cplusplus
}
#endif
/** EXTERN LIB SECTION END **/
+81
View File
@@ -0,0 +1,81 @@
#include "../src/atom_bridge.h"
/*****************************************************************************
* Copyright: Copyright CODESYS Development GmbH
* Program: Extension API for the Linux SL products
******************************************************************************/
#include "CmpStd.h"
#include DEP_H
USE_STMT
DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct)
/* Used to exchange function pointers between component manager and components.
Called at startup for each component.
pInitStruct: IN Pointer to structure with:
pfExportFunctions OUT Pointer to function that exports component functions
pfImportFunctions OUT Pointer to function that imports functions from other components
pfGetVersion OUT Pointer to function to get component version
pfRegisterAPI IN Pointer to component manager function to register a API function
pfGetAPI IN Pointer to component manager function to get a API function
pfCallHook IN Pointer to component manager function to call a hook function
Return ERR_OK if library could be initialized, else error code
*/
{
pInitStruct->CmpId = COMPONENT_ID;
pInitStruct->pfExportFunctions = ExportFunctions;
pInitStruct->pfImportFunctions = ImportFunctions;
pInitStruct->pfGetVersion = CmpGetVersion;
pInitStruct->pfHookFunction = HookFunction;
pInitStruct->pfCreateInstance = NULL;
pInitStruct->pfDeleteInstance = NULL;
s_pfCMRegisterAPI = pInitStruct->pfCMRegisterAPI;
s_pfCMRegisterAPI2 = pInitStruct->pfCMRegisterAPI2;
s_pfCMGetAPI = pInitStruct->pfCMGetAPI;
s_pfCMGetAPI2 = pInitStruct->pfCMGetAPI2;
s_pfCMCallHook = pInitStruct->pfCMCallHook;
s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass;
s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance;
return ERR_OK;
}
/**
* <description>Publish exported functions to the component manager, to make them available for other components.
* Use generated macro EXPORT_STMT.</description>
* <result>ERR_OK or error code in case of error</result>
*/
static int CDECL ExportFunctions(void)
{
/* Macro to export functions */
EXPORT_STMT;
return ERR_OK;
}
/**
* <description>Import function pointers from other components. Use generated macro IMPORT_STMT.</description>
* <result>ERR_OK or error code if function name could not be resolved</result>
*/
static int CDECL ImportFunctions(void)
{
/* Macro to import functions */
IMPORT_STMT;
return ERR_OK;
}
/**
* <description>Return the version of our component.</description>
* <result>Version as defined in dep file</result>
*/
static RTS_UI32 CDECL CmpGetVersion(void)
{
return CMP_VERSION;
}
static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2)
{
if (ulHook == CH_EXIT) ab_shutdown();
return ERR_OK;
}
+13
View File
@@ -0,0 +1,13 @@
# Generated from supplied ExtensionSDK; native build on ARM64 Pi.
SDKDIR ?= ../sdk/ExtensionSDK
MAJORVER=0
MINORVER=2
CMPVERSION=0x00020000
# Local development ID; check no other component uses this ID.
CMPID=0x2001
DOS2UNIX=dos2unix
M4=m4
CC ?= gcc
# The SDK otherwise includes CmpFrame in its component identity wildcard.
override CSRC := CmpAtomModbus.c
include $(SDKDIR)/makefile
+1
View File
@@ -0,0 +1 @@
Release outputs are created by tools/build-linux.sh. No precompiled .so is included.
+54
View File
@@ -0,0 +1,54 @@
# Bridge contract
Return: 0 = accepted, 1..10 = am_error values in atom_mb.h.
256 = invalid/stale handle, 257 = pool full, 258 = insufficient/null buffer,
259 = unknown command. Always inspect return codes. A rejected call is not a
completed Modbus request.
Commands: 1 Open, 2 Close, 3 Enable, 4 Disable, 5 Tick, 6 Submit,
7 CopyRead, 8 Reconnect, 9 Configure, 10 Acknowledge,
11 Set request timeout (responseTimeoutMs only; permitted while not busy).
Configure only while disabled; all three timeouts must be nonzero.
Open receives an IPv4 DWORD in human octet order: 16#C0A80132 = 192.168.1.50.
Open returns its handle in status[0], NOT in the function return value.
Close returns status[0]=0; old handles fail even when their slot is reused.
Capacity values count WORD/UDINT elements, not bytes. Pass separate buffers.
CopyRead copies `quantity` WORDs; coils/discretes are unpacked WORD 0/1.
| Status index | Meaning |
|---|---|
| 0 | Handle |
| 1 | State: 0 Disabled, 1 Retry, 2 Connecting, 3 Ready, 4 Sending, 5 Receiving |
| 2 | Busy |
| 3 | Done |
| 4 | Error |
| 5 | Latest transaction result |
| 6 | Last failure, retained after success |
| 7 | OS socket error |
| 8 | Modbus exception |
| 9 | Write outcome uncertain |
| 10 | Connection attempts |
| 11 | Attempts after first attempt |
| 12 | Completed successful requests |
| 13 | Failed requests |
| 14 | Has successful transaction |
| 15 | Last success timestamp, low 32 bits |
| 16 | Last success timestamp, high 32 bits |
| 17 | Quantity of latest request |
| 18 | Function code |
| 19 | Unit ID |
| 20 | Enabled |
| 21 | Connect timeout milliseconds |
| 22 | Whole-request timeout milliseconds |
| 23 | Retry delay milliseconds |
The PLC wrapper consumes Done/Error then Acknowledge before submitting a new
request. For Done reads, call CopyRead BEFORE Acknowledge. Completed read data
is retained internally but CopyRead requires Done. For writes, do not replay
an uncertain result without an application decision.
All calls to the entire bridge pool must originate from ONE IEC task. Contexts
and the pool are not thread-safe. Handles must not be RETAIN/PERSISTENT. Closing
on program disposal and invalidating on application reset/download remain PLC
wrapper responsibilities. Component unload cleanup alone does not cover an IEC
application stop or download.
+37
View File
@@ -0,0 +1,37 @@
# Linux builds
A binary must match CPU architecture, libc and the CODESYS component interface.
One .so cannot cover every Linux version. Build on the oldest supported target
OS within each architecture, or use its sysroot in a matching cross toolchain.
Native Pi build, from Library:
```sh
sudo apt-get install build-essential make m4 sed dos2unix python3
./tools/build-linux.sh linux-aarch64
```
Native x86-64 Linux build: `./tools/build-linux.sh linux-x86_64`.
32-bit ARM requires a matching ARM hard-float compiler/OS; do not use the ARM64
binary. The target argument labels output; it does not choose a compiler.
Cross-build example, provided a matching compiler and libc sysroot are installed:
```sh
CC=aarch64-linux-gnu-gcc ./tools/build-linux.sh linux-aarch64
```
For a custom compiler/sysroot use a compiler wrapper as CC. Cross builds have
not been validated in this bundle. The supplied SDK's makefile controls linking;
check its options before adding toolchain flags. CC must be one executable path.
Build targets sequentially because component/out is shared and cleaned each run.
The build script accepts an SDKDIR environment override. Preserve the supplied
SDK unchanged and use separate folders for replacement SDK versions. Record
and test compatibility before changing the generated external function ABI.
For ABI changes, regenerate C/M4 from the external CODESYS library and review
tools/prepare_component.py; do not overwrite component implementations blindly.
Build metadata includes compiler, host, SDK file hashes and output architecture.
Host OS/glibc describes the build host, not necessarily a cross-compiler sysroot.
Add the actual sysroot and tested target runtime to build-info.txt before release.
+19
View File
@@ -0,0 +1,19 @@
# Embed the shared object in the IEC library
1. Copy the built libCmpAtomModbus.so to the development PC.
2. Open the CmpAtomModbus library containing atMbCall_cext.
3. In POUs add a C Implemented Library object (C Code Integration functionality).
4. Add an Object file for devices: choose the .so and the exact Raspberry Pi
64 SL device/version. If a component name is requested, use CmpAtomModbus.
5. Save/install the library and update the application's library reference.
6. Perform a full download and verify loading and function resolution.
Add separate object files for other device/architecture targets. CODESYS chooses
the matching device entry; do not assign ARM64 binaries to 32-bit devices.
For automatic deployment testing, remove only the old manually added
CmpAtomModbus ComponentManager entry and restart the runtime before download.
Do this while the PLC application is stopped for maintenance.
Official instructions:
https://content.helpme-codesys.com/en/CODESYS%20Control/_rtsl_extension_extension_sdk.html
https://content.helpme-codesys.com/en/CODESYS%20Development%20System/_cds_obj_c_implemented_library.html
+14
View File
@@ -0,0 +1,14 @@
# Validation
Run `make test` on Linux with a C compiler and Python 3. Tests use a local
socket server and cover normal/fragmented replies, close/reconnect, timeout,
bad transaction IDs, bad MBAP length, Modbus exceptions, uncertain writes and
coils. They run through the direct engine and bridge. Pool/handle tests are
included. These tests do not compile or execute Structured Text.
In CODESYS compile both libraries and start a read-only bench application.
Verify first scan with zero registered requests, then registered requests,
reply timeouts, priorities, data freshness, disconnect/reconnect and disposal.
Test full download/reset and online-change lifecycle separately. Only then test
writes with appropriate device registers. Validate embedded deployment on a
target without the manually configured component.
+27
View File
@@ -0,0 +1,27 @@
# Troubleshooting
On the user's Pi the active runtime log is /var/log/codesyscontrol.log.
/var/opt/codesys/codesyscontrol.log contained old logs. Check timestamps.
```sh
sudo /etc/init.d/codesyscontrol status
sudo tail -n 100 /var/log/codesyscontrol.log
file component/out/libCmpAtomModbus.so
nm -D component/out/libCmpAtomModbus.so
```
Look for CmpAtomModbus, undefined symbol, signature mismatch or load errors.
ComponentEntry and atmbcall_cext must be exported. The observed manual load was
component ID 0xffff2001, version 0.2.0.0. Ensure custom component IDs do not collide.
External function signature: 0x709EE65B, API version: 0x00020000.
A valid component load does not prove the request scheduler or device I/O works.
AccessViolation in processAllRequests: use the current export with zero-count,
bounds and null checks and do a full download. Nonzero stale pointers still need
lifecycle investigation. Keep requests and their data arrays alive for the client
lifetime, and do not change a registered request's client while running.
Use one IEC task for all native clients. Handles must not be retained/persistent.
For STOP/download, disable the client and let a scan run, or call release().
See compat/README.md for lifecycle limitations and uncertain-write handling.
+10
View File
@@ -0,0 +1,10 @@
#include "CmpStd.h"
#include "CmpErrors.h"
#include "CmpItf.h"
#include "CmpAtomModbusDep.h"
void CDECL CDECL_EXT atmbcall_cext(atmbcall_cext_struct *p)
{
}
+70
View File
@@ -0,0 +1,70 @@
/**
* <interfacename>CmpAtomModbus</interfacename>
* <description></description>
*
* <copyright></copyright>
*/
SET_INTERFACE_NAME(`CmpAtomModbus')
/** EXTERN LIB SECTION BEGIN **/
/* Comments are ignored for m4 compiler so restructured text can be used. changecom(`/*', `*/') */
#ifdef __cplusplus
extern "C" {
#endif
/**
* <description>
* Create FUNCTION atMbCall_cext returning UDINT, paste this declaration.
* Properties > Build > External implementation must be selected.
* Library filename and Project Information title: CmpAtomModbus.
* This declaration is the SINGLE external entry point for the C engine.
* </description>
* <element name="atMbCall_cext" type=OUT></element>
* <element name="command" type=IN></element>
* <element name="handle" type=IN></element>
* <element name="ipAddress" type=IN></element>
* <element name="port" type=IN></element>
* <element name="unitId" type=IN></element>
* <element name="functionCode" type=IN></element>
* <element name="startAddress" type=IN></element>
* <element name="quantity" type=IN></element>
* <element name="pData" type=IN></element>
* <element name="dataCapacity" type=IN></element>
* <element name="pStatus" type=IN></element>
* <element name="statusCapacity" type=IN></element>
* <element name="nowMs" type=IN></element>
* <element name="connectTimeoutMs" type=IN></element>
* <element name="responseTimeoutMs" type=IN></element>
* <element name="retryDelayMs" type=IN></element>
*/
typedef struct tagatmbcall_cext_struct
{
RTS_IEC_UDINT command;
RTS_IEC_UDINT handle;
RTS_IEC_DWORD ipAddress;
RTS_IEC_UINT port;
RTS_IEC_BYTE unitId;
RTS_IEC_BYTE functionCode;
RTS_IEC_UINT startAddress;
RTS_IEC_UINT quantity;
RTS_IEC_WORD *pData;
RTS_IEC_UDINT dataCapacity;
RTS_IEC_UDINT *pStatus;
RTS_IEC_UDINT statusCapacity;
RTS_IEC_ULINT nowMs;
RTS_IEC_UDINT connectTimeoutMs;
RTS_IEC_UDINT responseTimeoutMs;
RTS_IEC_UDINT retryDelayMs;
RTS_IEC_UDINT atMbCall_cext;
} atmbcall_cext_struct;
DEF_API(`void',`CDECL',`atmbcall_cext',`(atmbcall_cext_struct *p)',1,0x709EE65B,0x00020000)
#ifdef __cplusplus
}
#endif
/** EXTERN LIB SECTION END **/
Binary file not shown.
+149
View File
@@ -0,0 +1,149 @@
##############################################################################
# Copyright: Copyright CODESYS Development GmbH
# Program: Extension API for the Linux SL products
##############################################################################
##########################
This is the SDK of the Linux SL products to create an own additional CODESYS runtime component
whose functions that can be called from a CODESYS IEC library.
##########################
##########################
Prerequisites:
- A CODESYS Installation to create an IEC library on a windows machine
- Some buildtools to create a shared object (linux dynamic library), that can be run with the CODESYS Control SL runtimes
- some kind of GCC (for X64 / ARM / AARCH64, depending on your target system)
- make
- m4
- sed
- dos2unix
##########################
##########################
If you want to start a new project, follow these steps:
On the linux system (where you want to build the component)
1) Install the required tools (see prerequisites), e.g. with
sudo apt-get install build-essential m4 sed dos2unix make
2) Copy the SDK to a place of your choice, e.g. in your home directory.
Later we call this directory the "<SDKDIR>", so please remember this location!
Hint: As we are using the tool "make": dont use paths with whitespaces. Make does not like that at all!
3) Create a folder next to the sdk where you want to start your project, e.g. "CmpFirstTest"
mkdir ~/CmpFirstTest
Later we call this directory the "<PROJECT DIR>", so please remember this location!
4) Now create a new project with the following command, and use the makefile from the <SDKDIR>:
make -f <SDKDIR>/makefile newproject
e.g.:
make -f ../sdk/makefile newproject
This creates a makefile within your folder, you might want to take a look at it later ;-)
Your workspace now might look like this:
|
|- SKD <SDKDIR>
| |- include (dir)
| |- m4 (dir)
| |- src (dir)
| |- makefile
| |- makefile_template
| |- README
|
|- CmpFirstTest <PROJECT DIR>
|- makefile
5) Now take a look at the generated makefile.
If you are compiling for X64, you dont need to change anything
If you are compiling for another architecture, you need to set a proper cross compiler via the variable CC (and probably more CFLAGS, INCLUDES and LDLIBS)
On the Windows system (where CODESYS is installed)
1) Open your CODESYS IDE and create a new library unsing the "Empty library" template (do not use the "External CODESYS library" template).
Set the title in the project information to e.g.: "CmpFirstTest"
and store it under the same filename, e.g. "CmpFirstTest.library".
These two must match, otherwise it wont be possible to generate the shared object!
2) Add "externally linked" functions to your library as you wish. You can make a Function "externally linked" by right clicking
on the new function ->Properties->Build and select "External implementation".
This function must include "_cext" in its name, otherwise it wont be loaded by the runtime!
E.g.: TestFunction_cext
3) Now create the runtime system files by selecting Build->"Generate runtime system files".
Choose a suitable path as output directory.
Enable the checkboxes for "M4 interface file" and "C stub file" as you need to generate both: C and M4 files.
Also check that the component name is equal to the name of your library, e.g. "CmpFirstTest"
4) Save or transfer these generated files (*.c and *Itf.m4 file) to the <PROJECT DIR> on your linux system
On the linux system (at <PROJECT DIR>)
1) Go to your <PROJECT DIR> and try to build your component with
make all
2) Now a shared object, with the same name as your IEC library should have been generated in the "out" folder
3) Now you can transfer, copy or symbolically link that to a location on your target system where libraries are normally found (e.g. /usr/lib )
e.g. if you are on the target system:
sudo cp out/libCmpFirstTest.so /usr/lib
4) Stop the runtime on your target e.g. with
sudo /etc/init.d/codesyscontrol stop
5) Open the configuration file CODESYSControl_User.cfg of the runtime, e.g. with nano
and add an entry at the section [ComponentManager], where the components are listed:
[ComponentManager]
Component.1=CmpFirstTest
Component.2=CmpSomeOtherComponent
Component.3=CmpSomeMoreComponent
...
hints:
- the number or index in this setting needs to be incrementing, it might not collide with already existing settings/component indices
- if the line starts with ; it is a comment and the setting is not active
6) Then restart your runtime again, e.g. with
sudo /etc/init.d/codesyscontrol start
7) and check that your component is properly loaded, e.g. check the logfile (via CODESYS IDE), or under codesyscontrol.log
cat /var/opt/codesys/codesyscontrol.log
you should see a message like this:
... Dynamic: <cmp>CmpFirstTest</cmp>, <id>0xffff2000</id> <ver>1.0.0.0</ver>
-> Congratulations! you made your first component and it runs from the CODESYS Control runtime
-> if not: check the FAQs section at the end of this file
On the Windows system (where CODESYS is installed)
1) go to the CODESYS project where you want to use your newly created IEC library, and use your library
2) make a download and check if your code is called (e.g. make a printf there)
##########################
##########################
FAQs:
1) My component does not build, what is going wrong?
- this might have very different reasons. Check what compiler (CC), flags (CFLAGS/LDFLAGS), include paths (INCLUDES) and libraries (LDLIBS) you are setting in your makefile
- check if the code you are writing does compile without this SDK stuff
- check if the functions in the component are named with "_cext"
2) My component is not loaded from the runtime?
- if you see a message in the log, like:
**** ERROR: libCmpFirstTest.so: cannot open shared object file: No such file or directory
or
**** ERROR: *** Error: Component <cmp>CmpFirstTest</cmp> NOT loaded
-> then your shared object is not found. Please double check that you put it somewhere on your linuxsystem, where it can be found (e.g. /usr/lib/)
3) Can I output some messages from my code? Where is this output?
- just use the normal printf() function in your code. This will be output at the "debug logger". To enable the debug logger, just change the file /etc/defaults/codesyscontrol:
set these variables:
DEBUGOUTPUT=1
and
DEBUGLOGFILE=/tmp/codesyscontrol_debug.log
then restart the runtime. Then you can see the output at this logfile
##########################
+16
View File
@@ -0,0 +1,16 @@
/*
* this is a special header for the SL extension API!!!
* <copyright>
* Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved.
* </copyright>
*/
#ifndef __CMPERRORS_H__
#define __CMPERRORS_H__
#define ERR_OK 0
#define ERR_NOTINITIALIZED 0x0003
#endif
+711
View File
@@ -0,0 +1,711 @@
/*
* this is a special header for the SL extension API!!!
* <copyright>
* Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved.
* </copyright>
*/
#ifndef __CMPITF_H__
#define __CMPITF_H__
#include <stdio.h>
#if defined(WIN32) || defined(_WIN32_WCE)
#ifdef _USRDLL
#define DLL_DECL _declspec(dllexport)
#endif
#endif
#if defined(LINUX) || defined(QNX)
#ifdef _USRDLL
#define DLL_DECL __attribute__ ((visibility ("default")))
#endif
#endif
#if !defined(DLL_DECL)
#define DLL_DECL
#endif
#ifndef CDECL
#define CDECL
#endif
#ifndef CDECL_EXT
#define CDECL_EXT
#endif
#ifndef STDCALL
#define STDCALL __stdcall
#endif
#if defined(__cplusplus)
extern "C" {
#endif
typedef RTS_UI32 CMPID; /* Component ID to specify a component. Has the vendor ID as a prefix! */
typedef RTS_UI32 CLASSID; /* Class ID to specify a specific class (C or C++) */
typedef RTS_UI32 OBJID; /* Object ID, typically the instance index starting with 0 */
typedef RTS_UI32 ITFID; /* Interface ID to specify a specific interface (C or C++) */
#define RTS_VENDORID_3S 0x0000
#define RTS_ADDVENDORID(VendorID, ID) (((RTS_UI32)(VendorID) << 16 & UINT32_C(0xFFFF0000)) + (ID))
#define ADDVENDORID RTS_ADDVENDORID
#define RTS_GETVENDORID(ID) ((RTS_UI32)(ID) >> 16 & UINT32_C(0x0000FFFF))
#ifdef TRG_64BIT
#define RTSITF_GET_SIGNATURE(sign32bit, sign64bit) sign64bit
#else
#define RTSITF_GET_SIGNATURE(sign32bit, sign64bit) sign32bit
#endif
/* Function that can be used for all CAL_ macros to return ERR_NOTIMPLEMENTED */
RTS_RESULT CDECL FUNCTION_NOTIMPLEMENTED(unsigned long dummy,...);
RTS_RESULT CDECL FUNCTION_NOTIMPLEMENTED2(void *dummy,...);
/* Structure to handle a mapping of an ID and a name */
typedef struct RTS_ID_TO_NAME_
{
RTS_UI32 id;
char *pszName;
} RTS_ID_TO_NAME;
struct tagIBase;
typedef struct
{
/*pointer to virtual function table*/
void* __VFTABLEPOINTER;
} instance_struct;
typedef struct
{
instance_struct *pInstance;
RTS_IEC_DWORD dwCount;
} addref_struct;
typedef struct
{
instance_struct *pInstance;
RTS_IEC_DWORD dwCount;
} release_struct;
typedef struct
{
instance_struct *pInstance;
RTS_IEC_DWORD iid;
RTS_IEC_RESULT *pResult;
instance_struct **ppInterface;
} queryinterface_struct;
typedef unsigned long (CDECL *PF_ADDREF)(struct tagIBase *pIBase);
typedef unsigned long (CDECL *PF_RELEASE)(struct tagIBase *pIBase);
typedef void *(CDECL *PF_QUERYINTERFACE)(struct tagIBase *pIBase, ITFID iid, RTS_RESULT *pResult);
typedef void (CDECL *PF_IEC_ADDREF)(void);
typedef void (CDECL *PF_IEC_RELEASE)(void);
typedef void (CDECL *PF_IEC_QUERYINTERFACE)(void);
/* <SIL2/> */
typedef struct tagIBase
{
int bIEC;
int iRefCount;
RTS_HANDLE hInstance;
PF_ADDREF AddRef;
PF_RELEASE Release;
PF_QUERYINTERFACE QueryInterface;
CLASSID ClassId;
} IBase_C;
typedef struct tagIBase_IEC
{
int bIEC;
int iRefCount;
RTS_HANDLE hInstance;
PF_IEC_ADDREF *AddRef;
PF_IEC_RELEASE *Release;
PF_IEC_QUERYINTERFACE *QueryInterface;
} IBase_IEC;
#ifndef STATICITF
#define STATICITF static
#endif
#ifndef STATICITF_DEF
#define STATICITF_DEF
#endif
#if defined(CPLUSPLUS) && defined(__cplusplus)
class IBase
{
public:
virtual unsigned long AddRef(IBase *pIBase = NULL) =0;
virtual unsigned long Release(IBase *pIBase = NULL) =0;
virtual void *QueryInterface(IBase *pIBase = NULL, ITFID iid = 0, RTS_RESULT *pResult = NULL) =0;
virtual ~IBase() { };
};
#define DECLARE_ADDREF()\
virtual unsigned long AddRef(IBase *pIBase = NULL);
#define IMPLEMENT_ADDREF()\
virtual unsigned long AddRef(IBase *pIBase = NULL)\
{\
iRefCount++;\
return iRefCount;\
}
#define DECLARE_RELEASE()\
virtual unsigned long Release(IBase *pIBase = NULL);
#define IMPLEMENT_RELEASE()\
virtual unsigned long Release(IBase *pIBase = NULL)\
{\
iRefCount--;\
if (iRefCount == 0)\
{\
delete this;\
return 0;\
}\
return iRefCount;\
}
#define IMPLEMENT_RELEASE_NODELETE()\
virtual unsigned long Release(IBase *pIBase = NULL)\
{\
iRefCount--;\
if (iRefCount == 0)\
{\
return 0;\
}\
return iRefCount;\
}
#else
#define DECLARE_ADDREF\
static unsigned long CDECL AddRef(struct tagIBase *pIBase);
#define IMPLEMENT_ADDREF\
static unsigned long CDECL AddRef(struct tagIBase *pIBase)\
{\
return (pIBase ? ++pIBase->iRefCount : 0);\
}
#define DECLARE_RELEASE\
static unsigned long CDECL Release(struct tagIBase *pIBase);
#define IMPLEMENT_RELEASE\
static unsigned long CDECL Release(struct tagIBase *pIBase)\
{\
return (pIBase ? --pIBase->iRefCount : 0);\
}
#define DECLARE_QUERYINTERFACE\
static void *CDECL QueryInterface(IBase *pBase, ITFID iid, RTS_RESULT *pResult);
typedef IBase_C IBase;
#endif
/* Global function pointer declarations */
typedef int (CDECL *PF_EXPORT_FUNCTIONS)(void);
typedef int (CDECL *PF_IMPORT_FUNCTIONS)(void);
typedef RTS_UI32 (CDECL *PF_GET_VERSION)(void);
typedef RTS_RESULT (CDECL *PF_HOOK_FUNCTION)(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2);
/* typedef RTS_RESULT (CDECL *PF_REGISTER_API)(const char *pszAPIName, RTS_VOID_FCTPTR pfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID); */
typedef RTS_RESULT (CDECL *PF_REGISTER_API)(const CMP_EXT_FUNCTION_REF *pExpTable, RTS_UINTPTR dummy, int bExternalLibrary, RTS_UI32 cmpId);
typedef RTS_RESULT (CDECL *PF_GET_API)(char *pszAPIName, RTS_VOID_FCTPTR *ppfAPIFunction, RTS_UI32 ulSignatureID);
typedef RTS_RESULT (CDECL *PF_REGISTER_API2)(const char *pszAPIName, RTS_VOID_FCTPTR pfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID, RTS_UI32 ulVersion);
typedef RTS_RESULT (CDECL *PF_GET_API2)(char *pszAPIName, RTS_VOID_FCTPTR *ppfAPIFunction, int bExternalLibrary, RTS_UI32 ulSignatureID, RTS_UI32 ulVersion);
typedef RTS_RESULT (CDECL *PF_CALL_HOOK)(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2, int bReverse);
typedef IBase* (CDECL *PF_CREATEINSTANCE)(CLASSID cid, RTS_RESULT *pResult);
typedef RTS_RESULT (CDECL *PF_DELETEINSTANCE)(IBase *pIBase);
typedef RTS_HANDLE (CDECL *PF_REGISTER_CLASS)(CMPID CmpId, CLASSID ClassId);
typedef RTS_RESULT (CDECL *PF_DELETEINSTANCE2)(CLASSID ClassId, IBase *pIBase);
typedef struct tagINIT_STRUCT
{
/* Exported by Component */
CMPID CmpId;
#ifdef TRG_64BIT
RTS_UI32 aligndummy;
#endif
PF_EXPORT_FUNCTIONS pfExportFunctions;
PF_IMPORT_FUNCTIONS pfImportFunctions;
PF_GET_VERSION pfGetVersion;
PF_CREATEINSTANCE pfCreateInstance;
PF_DELETEINSTANCE pfDeleteInstance;
PF_HOOK_FUNCTION pfHookFunction;
/* Import from CM */
PF_REGISTER_API pfCMRegisterAPI;
PF_GET_API pfCMGetAPI;
PF_CALL_HOOK pfCMCallHook;
PF_REGISTER_CLASS pfCMRegisterClass;
PF_CREATEINSTANCE pfCMCreateInstance;
PF_REGISTER_API2 pfCMRegisterAPI2;
PF_GET_API2 pfCMGetAPI2;
PF_DELETEINSTANCE2 pfCMDeleteInstance2;
} INIT_STRUCT;
#define COMPONENT_ENTRY_NAME "ComponentEntry"
#if defined(CPLUSPLUS) && defined(__cplusplus)
extern "C" {
#endif
#if !defined(STATIC_LINK) && !defined(MIXED_LINK) && !defined(DYNAMIC_LINK) && !defined(CPLUSPLUS_STATIC_LINK)
DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct);
#endif
typedef int (CDECL *PF_COMPONENT_ENTRY)(INIT_STRUCT *pInitStruct);
#if defined(CPLUSPLUS) && defined(__cplusplus)
}
#endif
/* Functions exported from main */
RTS_RESULT CDECL MainLoadComponent(char *pszName, char *pszPath, RTS_HANDLE *phModule, PF_COMPONENT_ENTRY* ppfComponentEntry);
RTS_RESULT CDECL MainUnloadComponent(char *pszName, RTS_HANDLE hModule);
/* The following functions are always offered by the component manager */
int CDECL MainExitLoop(RTS_UINTPTR ulPar);
typedef int (CDECL *PFMAINEXITLOOP)(RTS_UINTPTR ulPar);
#ifdef STATIC_LINK
#define USE_CMExitLoop extern int CDECL MainExitLoop(RTS_UINTPTR ulPar);
#define EXT_CMExitLoop extern int CDECL MainExitLoop(RTS_UINTPTR ulPar);
#define GET_CMExitLoop ERR_OK
#define CAL_CMExitLoop MainExitLoop
#define CHK_CMExitLoop TRUE
#define EXP_CMExitLoop TRUE
#else /* DYNAMIC_LINK */
#define USE_CMExitLoop PFMAINEXITLOOP pfMainExitLoop;
#define EXT_CMExitLoop extern PFMAINEXITLOOP pfMainExitLoop;
#define GET_CMExitLoop s_pfGetAPI("CMExitLoop", (void **)&pfMainExitLoop, 0)
#define CAL_CMExitLoop pfMainExitLoop
#define CHK_CMExitLoop (pfMainExitLoop != NULL)
#define EXP_CMExitLoop s_pfRegisterAPI("CMExitLoop", MainExitLoop, 1, 0)
#endif
/**
* <description>Hook definitions to inform all components with a broadcast via HookFunction() ([0..10000] reserved for core components):
* <ul>
* <li>CH_INIT_XXX Hooks: Used for initialization of all components.
* Future range, not yet defined! = [1..1000]</li>
* <li>CH_EXIT_XXX Hooks: Used for deinitialization of all components.
* Future range, not yet defined! = [2000..3000]</li>
* <li>CH_XXX Hooks: Used for broadcast commands (e.g. cyclic operations).
* Future range, not yet defined! = [4000..5000]</li>
* </ul>
* IMPLEMENTATION NOTE:
* Future hooks should be defined in the appropriate range above!
* </description>
*/
/*
CH_INIT_XXX Hooks: Used for initialization of all components.
*/
#define CH_INIT_SYSTEM 1
/* First call at system startup to initialize all system components.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_SYSTEM2 8
/* Second call at system startup to initialize all system components.
(used to resolve dependencies between core components)
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT 2
/* First call at system startup. Compontents should initialize all local variables.
ulParam1: Pointer to int. Parameter name=pbDontCallInitHooksFromCMInit. Content can be set as followed:
0 [Default]: CMInitEnd() is called at the end of CMInit().
1: CMInitEnd (calling hooks from CH_INIT2 to CH_INIT_COMM) can be called asynchronously by your own
component and is not called in the context of CMInit()!
ulParam2: Not used
*/
#define CH_POST_INIT 200
/* Called right after CH_INIT. Use this hook to run initialization between
highly related components (e.g. Register backends or providers to the corresponding components.).
ulParam1: Not used
ulParam2: Not used
*/
#define CH_PRE_INIT2 250
/* Called right before CH_INIT2. Use this hook to finish initialization of a component that is highly
related to an other components. E.g. finish initialization after the providers have registered.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT2 3
/* Called after CH_INIT. All components are initialized. Other components can be used.
NOTE:
Events must be created of the provider in this init step!
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_DONE CH_INIT2
/* Backward compatibility */
#define CH_PRE_INIT3 7
/* Called after CH_INIT2 and before CH_INIT3. All components are initialized. Other components can be used.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT201 CH_PRE_INIT3
/* Backward compatibility */
#define CH_INIT3 4
/* Called after CH_INIT201. All components are initialized. Other components can be used.
NOTE:
Event callbacks must be registered by the consumer in this init step! Events are created in CH_INIT2.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_SYSTEM_TASKS 500
/* Called after CH_INIT3. Tasks can be started that are relevant for the IEC applications.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_TASKS 5
/* Called after CH_INIT_SYSTEM_TASKS. All components should start their tasks.
NOTE:
The IEC bootprojects are loaded here!
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_COMM 6
/* Called after CH_INIT_TASKS. Communication can be started at this point, because all
components are ready and running.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_INIT_FINISHED 1000
/* Called at the end of the complete initialization sequence. This is called after CH_INIT_COMM and right before the runtime system
fall into the cyclic CH_COMM_CYCLE state.
NOTE:
The IEC bootprojects are started here!
ulParam1: Not used
ulParam2: Not used
*/
/*
CH_EXIT_XXX Hooks: Used for deinitialization of all components.
*/
#define CH_EXIT_COMM 10
/* Called at the beginning of the shutdown sequence to disable all communication channels!
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT_TASKS 11
/* Called before CH_EXIT_SYSTEM_TASKS to stop and remove all tasks that are not needed by the IEC application.
NOTE:
IEC applications are unloaded here.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT_SYSTEM_TASKS 2200
/* Called before CH_EXIT3 to stop and remove all running tasks, because at CH_EXIT3 all objects are released and
so no task must run after this point.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT3 12
/* Called before CH_EXIT2 to store data (e.g. retain data).
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT2 13
/* Called before CH_EXIT to store data (e.g. retain data).
ulParam1: Not used
ulParam2: Not used
*/
#define CH_PRE_EXIT CH_EXIT2
/* Backward compatibility */
#define CH_EXIT 14
/* Here all memory of the components should be released.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT_SYSTEM2 17
/* Counter part to CH_INIT_SYSTEM2.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT_SYSTEM 15
/* Called at the end of the shutdown process for the system components to release memory.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_EXIT_GARBAGEMEMORY 16
/* This is the final call for the garbage collector to check, if heap memory was not released.
ulParam1: Not used
ulParam2: Not used
*/
/*
CH_XXX Hooks: Used for broadcast commands (e.g. cyclic operations).
*/
#define CH_COMM_CYCLE 20
/* Called every main cycle period. Can be used for background executions.
ulParam1: Not used
ulParam2: Not used
*/
#define CH_ON_IMPORT 23
/* Called for components to import new functions from a new loaded component
ulParam1: char *: Pointer to component name
ulParam2: Not used
*/
#define CH_ON_IMPORT_RELEASE 24
/* Called for components to release imported functions from a component that will be unloaded
ulParam1: char *: Pointer to component name
ulParam2: Not used
*/
#define CH_ON_LOAD_COMPONENT 25
/* Called after loading a new component
ulParam1: char *: Pointer to component name
ulParam2: Not used
*/
#define CH_ON_UNLOAD_COMPONENT 26
/* Called before unloading a component
ulParam1: char *: Pointer to component name
ulParam2: Not used
*/
#define CH_ON_REGISTER_INSTANCE 27
/* Called after register a new instance
ulParam1: CLASSID: ClassId of instance
ulParam2: IBase *: Pointer to IBase
*/
#define CH_ON_UNREGISTER_INSTANCE 28
/* Called before unregister a new instance
ulParam1: CLASSID: ClassId of instance
ulParam2: IBase *: Pointer to IBase
*/
#define CH_ON_LICENSE_FCTS_REGISTERED 29
/*
Called after the functionpointers from the license-library
have been registered to the runtime.
*/
#define CH_ON_LICENSE_FCTS_REGISTERED2 290
/*
Called after CH_ON_LICENSE_FCTS_REGISTERED.
*/
#define CH_ON_LICENSE_FCTS_LEGACY_REGISTERED 30
/*
Called after the functionpointers from the license-library
have been registered to the runtime.
*/
#define CH_SAFEMODE 4000
/*
Called when a component fails to resolve its required import functions during startup of the runtime system. In this case, the system is not stable any more.
Every component should check safety.
After entering the safemode, at least the communication should work and so for example a service engineer can investigate the reason for this error.
The logger should work too.
IMPORTANT NOTE:
It is important, that the IEC-application(s) are not loaded or at least are not started after entering the safemode! This is still implemented in the CmpApp component.
ulParam1: Not used
ulParam2: Not used
*/
/**
* <category>Hook table</category>
* <description>
* This is the table of all hooks with its corresponding name (see CM_HOOK_TABLE).
*
* NOTE:
* The init and exit hooks are defined here in the order they are executed during startup and shutdown!
*
* </description>
*/
#define CM_INIT_HOOK_TABLE { CH_INIT_SYSTEM, "CH_INIT_SYSTEM" },\
{ CH_INIT_SYSTEM2, "CH_INIT_SYSTEM2" },\
{ CH_INIT, "CH_INIT" },\
{ CH_POST_INIT, "CH_POST_INIT" },\
{ CH_PRE_INIT2, "CH_PRE_INIT2" },\
{ CH_INIT2, "CH_INIT2" },\
{ CH_PRE_INIT3, "CH_PRE_INIT3" },\
{ CH_INIT3, "CH_INIT3" },\
{ CH_INIT_SYSTEM_TASKS, "CH_INIT_SYSTEM_TASKS" },\
{ CH_INIT_TASKS, "CH_INIT_TASKS" },\
{ CH_INIT_COMM, "CH_INIT_COMM" },\
{ CH_INIT_FINISHED, "CH_INIT_FINISHED" }
#define CM_COMM_CYCLE_HOOK { CH_COMM_CYCLE, "CH_COMM_CYCLE" }
#define CM_EXIT_HOOK_TABLE { CH_EXIT_COMM, "CH_EXIT_COMM" },\
{ CH_EXIT_TASKS, "CH_EXIT_TASKS" },\
{ CH_EXIT_SYSTEM_TASKS, "CH_EXIT_SYSTEM_TASKS" },\
{ CH_EXIT3, "CH_EXIT3" },\
{ CH_EXIT2, "CH_EXIT2" },\
{ CH_EXIT, "CH_EXIT" },\
{ CH_EXIT_SYSTEM2, "CH_EXIT_SYSTEM2" },\
{ CH_EXIT_SYSTEM, "CH_EXIT_SYSTEM" },\
{ CH_EXIT_GARBAGEMEMORY, "CH_EXIT_GARBAGEMEMORY" }
#define CM_EXTENDED_HOOK_TABLE { CH_ON_IMPORT, "CH_ON_IMPORT" }, \
{ CH_ON_IMPORT_RELEASE, "CH_ON_IMPORT_RELEASE" }, \
{ CH_ON_LOAD_COMPONENT, "CH_ON_LOAD_COMPONENT" }, \
{ CH_ON_UNLOAD_COMPONENT, "CH_ON_UNLOAD_COMPONENT" }, \
{ CH_ON_REGISTER_INSTANCE, "CH_ON_REGISTER_INSTANCE" }, \
{ CH_ON_UNREGISTER_INSTANCE, "CH_ON_UNREGISTER_INSTANCE" }, \
{ CH_ON_LICENSE_FCTS_REGISTERED, "CH_ON_LICENSE_FCTS_REGISTERED" }, \
{ CH_ON_LICENSE_FCTS_REGISTERED2, "CH_ON_LICENSE_FCTS_REGISTERED2" }, \
{ CH_ON_LICENSE_FCTS_LEGACY_REGISTERED, "CH_ON_LICENSE_FCTS_LEGACY_REGISTERED" }, \
{ CH_SAFEMODE, "CH_SAFEMODE" } \
#define CM_HOOK_TABLE {\
CM_INIT_HOOK_TABLE,\
CM_COMM_CYCLE_HOOK,\
CM_EXIT_HOOK_TABLE,\
CM_EXTENDED_HOOK_TABLE\
}
/**
* <category>Single import of interface functions</category>
* <description>
* These macros are used by the System-Components to import single interface functions without modifying
* the global SysXXXDep.m4 files.
*
* ATTENTION:
* With usage of these macros, the RtsConfiguration cannot resolve this dependency anymore!!!
* So use these macros only in Sys-Components and not in standard or customer components!
*
* Usage:
* #include "<Interface>Itf.h"
* USE_SINGLE_ITF(<Interface>) or USE_SINGLE_ITF(<Class>, <Interface>)
* USE_SINGLE_FUNCTION(<Function>)
*
* static int CDECL ImportFunctions(void)
* {
* IMPORT_SINGLE_ITF(<Interface>);
* IMPORT_SINGLE_FUNCTION(<Function>);
* return ERR_OK;
* }
* </description>
*/
#ifdef CPLUSPLUS
#define USE_SINGLE_ITF(Interface_) ITF_##Interface_
#define IMPORT_SINGLE_ITF(Interface_) \
{\
IBase *pIBase_;\
RTS_RESULT Result_;\
if (pI##Interface_ == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase_ = (IBase *)s_pfCMCreateInstance(CLASSID_C##Interface_, &Result_); \
if (pIBase_ != NULL) \
{\
pI##Interface_ = (I##Interface_ *)pIBase_->QueryInterface(pIBase_, ITFID_I##Interface_, &Result_); \
pIBase_->Release(pIBase_); \
}\
} \
}
#define EXIT_SINGLE_ITF(Interface_) \
{\
IBase *pIBase_;\
RTS_RESULT exitResult_;\
if (pI##Interface_ != NULL) \
{ \
pIBase_ = (IBase *)pI##Interface_->QueryInterface(pI##Interface_, ITFID_IBase, &exitResult_); \
if (pIBase_ != NULL && exitResult_ == ERR_OK) \
{ \
pIBase_->Release(pIBase_); \
if (pIBase_->Release(pIBase_) == 0) /* The object will be deleted here! */ \
pI##Interface_ = NULL; \
} \
} \
}
#define IMPORT_SINGLE_ITF2(Class_, Interface_) \
{\
IBase *pIBase_;\
RTS_RESULT Result_;\
if (pI##Interface_ == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase_ = (IBase *)s_pfCMCreateInstance(CLASSID_C##Class_, &Result_); \
if (pIBase_ != NULL) \
{\
pI##Interface_ = (I##Interface_ *)pIBase_->QueryInterface(pIBase_, ITFID_I##Interface_, &Result_); \
pIBase_->Release(pIBase_); \
}\
} \
}
#define EXIT_SINGLE_ITF2(Class_, Interface_) \
{\
IBase *pIBase_;\
RTS_RESULT exitResult_;\
if (pI##Interface_ != NULL) \
{ \
pIBase_ = (IBase *)pI##Interface_->QueryInterface(pI##Interface_, ITFID_IBase, &exitResult_); \
if (pIBase_ != NULL && exitResult_ == ERR_OK) \
{ \
pIBase_->Release(pIBase_); \
if (pIBase_->Release(pIBase_) == 0) /* The object will be deleted here! */ \
pI##Interface_ = NULL; \
} \
} \
}
#else
#define USE_SINGLE_ITF(Interface_)
#define IMPORT_SINGLE_ITF(Interface_)
#define EXIT_SINGLE_ITF(Interface_)
#define IMPORT_SINGLE_ITF2(Class_, Interface_)
#define EXIT_SINGLE_ITF2(Class_, Interface_)
#endif
#define USE_SINGLE_FUNCTION(Function) USE_##Function
#define IMPORT_SINGLE_FUNCTION(Function) GET_##Function(0)
#define EXIT_SINGLE_FUNCTION(Function)
#if defined(__cplusplus)
}
#endif
#endif /*__CMPITF_H__*/
+641
View File
@@ -0,0 +1,641 @@
/*
* this is a special header for the SL extension API!!!
* <copyright>
* Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved.
* </copyright>
*/
#ifndef __RTSSTD_H__
#define __RTSSTD_H__
#if defined(__amd64__)
#define TRG_X64
#define TRG_64BIT
#elif defined(__arm__)
#define TRG_ARM
#elif defined(__aarch64__)
#define TRG_ARM
#define TRG_64BIT
#elif defined(i386)
#error "32 bit X86 is not supported!"
#else
#error "Unknown architecture!"
#endif
#define LINUX
#define CDECL
#define __cdecl CDECL /*necessary for sqlite in CmpCAAStorage*/
#define STDCALL
#define HUGEPTR
/****** OS *****/
#define SYSTARGET_OS_LINUX
/*** types ***/
#ifndef STDINT_H_UINTPTR_T_DEFINED
#define STDINT_H_UINTPTR_T_DEFINED
#endif
typedef unsigned short RTS_WCHAR;
#define RTS_WCHAR_DEFINED
#define INTEL_BYTE_ORDER
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stddef.h>
#include <ctype.h>
#include <math.h>
/**
* set default register width
*/
#ifndef RTS_REGISTER_WIDTH
#if defined(TRG_C16X) || defined(TRG_DSP)
#define RTS_REGISTER_WIDTH 16
#elif defined(TRG_64BIT) || defined(TRG_IA64)
#define RTS_REGISTER_WIDTH 64
#else
#define RTS_REGISTER_WIDTH 32
#endif
#endif
#if RTS_REGISTER_WIDTH == 64 && !defined (SIZEOF_LONG)
#if defined (SYSTARGET_OS_WINDOWS) || defined (SYSTARGET_OS_WINDOWS_RTE) || defined (SYSTARGET_OS_WINDOWS_CE)
#define SIZEOF_LONG 4
#else
#define SIZEOF_LONG 8
#endif
#endif
#include <stdint.h>
/**
* Redefine UINT32_C(), INT16_C, ...,
* as many compilers are defining this in a bad way. They
* are using type-casts to get the correct data-width. This
* makes it impossible to use for some pedantic C-Compilers.
*/
#ifdef INT64_C
#undef INT64_C
#endif
#ifdef UINT64_C
#undef UINT64_C
#endif
#ifdef INT32_C
#undef INT32_C
#endif
#ifdef UINT32_C
#undef UINT32_C
#endif
#ifdef INT16_C
#undef INT16_C
#endif
#ifdef UINT16_C
#undef UINT16_C
#endif
#ifdef INT8_C
#undef INT8_C
#endif
#ifdef UINT8_C
#undef UINT8_C
#endif
#define INT64_C(x) (RTS_I64_CONST(x))
#define UINT64_C(x) (RTS_UI64_CONST(x))
#define INT32_C(x) (x ## L)
#define UINT32_C(x) (x ## UL)
#define INT16_C(x) (x)
#define UINT16_C(x) (x ## U)
#define INT8_C(x) (x)
#define UINT8_C(x) (x ## U)
#define RTS_IEC_BOOL_C(x) (x)
#define RTS_IEC_BYTE_C(x) (UINT8_C(x))
#define RTS_IEC_SINT_C(x) (INT8_C(x))
#define RTS_IEC_USINT_C(x) (UINT8_C(x))
#define RTS_IEC_INT_C(x) (INT16_C(x))
#define RTS_IEC_UINT_C(x) (UINT16_C(x))
#define RTS_IEC_DINT_C(x) (INT32_C(x))
#define RTS_IEC_UDINT_C(x) (UINT32_C(x))
#define RTS_IEC_LINT_C(x) (INT64_C(x))
#define RTS_IEC_ULINT_C(x) (UINT64_C(x))
#define RTS_IEC_WORD_C(x) (UINT16_C(x))
#define RTS_IEC_DWORD_C(x) (UINT32_C(x))
#define RTS_IEC_LWORD_C(x) (UINT64_C(x))
#define RTS_IEC_REAL_C(x) (x)
#define RTS_IEC_LREAL_C(x) (x)
#ifdef TRG_64BIT
#define RTS_IEC_XINT_C(x) (INT64_C(x))
#define RTS_IEC_UXINT_C(x) (UINT64_C(x))
#define RTS_IEC_XWORD_C(x) (UINT64_C(x))
#else
#define RTS_IEC_XINT_C(x) (INT32_C(x))
#define RTS_IEC_UXINT_C(x) (UINT32_C(x))
#define RTS_IEC_XWORD_C(x) (UINT32_C(x))
#endif
/**
* Define some more exotic types, which are missing
* in many versions of stdint.h.
*/
#if !defined(STDINT_H_UINTPTR_T_DEFINED)
/* Note: If your compiler complains about a "redefinition"
* or s.th. similar here, you may set the defines
* _UINTPTR_T, _PTRDIFF_T, ... in your sysdefines.h.
*/
#ifndef _UINTPTR_T
typedef uint32_t uintptr_t;
#define _UINTPTR_T
#endif
#ifndef _PTRDIFF_T
typedef int32_t ptrdiff_t;
#define _PTRDIFF_T
#endif
#ifndef _INTPTR_T
typedef ptrdiff_t intptr_t;
#define _INTPTR_T
#endif
#define STDINT_H_UINTPTR_T_DEFINED
#endif /*!defined(STDINT_H_UINTPTR_T_DEFINED)*/
/* Format modifiers for integral types */
#ifndef ULONG_MAX
# include <limits.h>
# ifndef ULONG_MAX
# error ULONG_MAX does not exist in limits.h!
# endif
#endif
/*
* Some basic definitions
*/
#ifndef NULL
# define NULL 0L
#endif
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
/*lint -e666 -e506 */
#ifndef MIN
# define MIN(x,y) ((x)<(y)?(x):(y))
#endif
#ifndef MAX
# define MAX(x,y) ((x)>(y)?(x):(y))
#endif
#ifndef RTS_SETRESULT
# define RTS_SETRESULT(p,r) do { if(p) *(p)=r; } while(0)
#endif
#ifndef RTS_GETRESULT
# define RTS_GETRESULT(Return,Result) (Result == ERR_OK ? (Return) : ((Return) ? Result : Result))
#endif
#ifndef LOWWORD
# define LOWWORD(p) ((uint16_t)((uint32_t)(p) & 0x0000FFFF))
#endif
#ifndef HIGHWORD
# define HIGHWORD(p) ((uint16_t)(((uint32_t)(p)>>16) & 0x0000FFFF))
#endif
/* Operations on flags */
#ifndef RTS_SETFLAGS
#define RTS_SETFLAGS(value, flags) (value |= (flags))
#endif
#ifndef RTS_SETFLAG
#define RTS_SETFLAG(value, flag) RTS_SETFLAGS(value, flag)
#endif
#ifndef RTS_RESETFLAGS
#define RTS_RESETFLAGS(value, flags) (value &= ~(flags))
#endif
#ifndef RTS_RESETFLAG
#define RTS_RESETFLAG(value, flag) RTS_RESETFLAGS(value, flag)
#endif
#ifndef RTS_HASFLAGS
#define RTS_HASFLAGS(value, flags) ((value & (flags)) == (flags))
#endif
#ifndef RTS_HASFLAG
#define RTS_HASFLAG(value, flag) (value & (flag))
#endif
#ifndef ALIGN_MEMORY
# define ALIGN_MEMORY(pMemory) ((void *)(((((uintptr_t)(pMemory) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*))))
#endif
#ifndef ALIGN_MEMORY_SIZE
# define ALIGN_MEMORY_SIZE(pMemory, iSize) ((iSize) - (((((uintptr_t)(pMemory) + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)) - (uintptr_t)(pMemory)))
#endif
#ifndef ALIGN_MEMORY2
# define ALIGN_MEMORY2(pMemory, Modulo) ((void *)((((uintptr_t)(pMemory) + (Modulo) - 1) / (uintptr_t)(Modulo)) * (uintptr_t)(Modulo)))
#endif
#ifndef ALIGN_MEMORY2_BACK
# define ALIGN_MEMORY2_BACK(pMemory, Modulo) ((void *)(((uintptr_t)(pMemory) / (uintptr_t)(Modulo)) * (uintptr_t)(Modulo)))
#endif
#ifndef ALIGN_MEMORY_SIZE2
# define ALIGN_MEMORY_SIZE2(pMemory, iSize, Modulo) ((iSize) - (((((uintptr_t)(pMemory) + (Modulo) - 1) / sizeof(void*)) * (Modulo)) - (uintptr_t)(pMemory)))
#endif
#ifndef ALIGN_SIZE
/* NOTE: it seems like this operation can be made without a comparison: */
/* #define ALIGN_SIZE(Size, Modulo) (( (Size) + ((Modulo) - 1) ) / (Modulo)) * (Modulo) */
# define ALIGN_SIZE(Size, Modulo) /*lint -e{506} */(((Size) % (Modulo)) ? ((Size) + ((Modulo) - ((Size) % (Modulo)))) : (Size))
#endif
#ifndef RTS_VSNPRINTF
#if defined(RTS_VSNPRINTF_NOT_AVAILABLE)
#define RTS_VSNPRINTF(s,len,format,arglist) vsprintf(s,format,arglist)
#else
#define RTS_VSNPRINTF(s,len,format,arglist) vsnprintf(s,len,format,arglist)
#endif
#endif
#ifndef va_copy
#define va_copy(destination, source) ((destination) = (source))
#endif
#define CONCAT(x,y) x##y
/* Some preprocessors do not allow to place such a macro within other macros */
/* and/or pragmas. */
#define ____QUOTE____(x) #x
#define QUOTE(x) ____QUOTE____(x)
#ifdef INTEL_BYTE_ORDER
# define SWAP16u(x) (x)
# define SWAP16s(x) (x)
# define SWAP32u(x) (x)
# define SWAP32s(x) (x)
#elif defined(MOTOROLA_BYTE_ORDER)
# define SWAP16u(x) ( (uint16_t)((((x) & 0xFF)<<8) | (((x) & 0xFF00) >> 8)) )
# define SWAP16s(x) ( (int16_t)((((x) & 0xFF)<<8) | (((x) & 0xFF00) >> 8)) )
# define SWAP32u(x) ( (uint32_t)((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) )
# define SWAP32s(x) ((int32_t)((((uint32_t)(x)) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
#else
#error "No byte order defined for the current target"
#endif
#include "CmpErrors.h"
#define CAL_CMGETAPI(name) ERR_OK
#define CAL_CMEXPAPI(name) ERR_OK
typedef void HUGEPTR(CDECL *RTS_VOID_FCTPTR)(void);
/*
* Runtime system basic typedefinitions.
* Defines some types with special meaning. Systems not using standard type sizes
* must redefine this types in sysdefines.h and set BASETYPES_DEFINED
*/
#ifndef RTS_I64_CONST
#define RTS_I64_CONST(a) (a##LL)
#endif
#ifndef RTS_UI64_CONST
#define RTS_UI64_CONST(a) (a##ULL)
#endif
#ifndef BASETYPES_DEFINED
# define BASETYPES_DEFINED
typedef int8_t RTS_I8; /* Signed 8 bit value */
typedef int16_t RTS_I16; /* Signed 16 bit value */
#ifndef RTS_I32_DEFINED
#define RTS_I32_DEFINED
typedef int32_t RTS_I32; /* Signed 32 bit value */
#endif
typedef uint8_t RTS_UI8; /* Unsigned 8 bit value */
# define RTS_UI8_MAX (RTS_UI8)(~((RTS_UI8)0))
typedef uint16_t RTS_UI16; /* Unsigned 16 bit value */
# define RTS_UI16_MAX (RTS_UI16)(~((RTS_UI16)0))
#ifndef RTS_UI32_DEFINED
#define RTS_UI32_DEFINED
typedef uint32_t RTS_UI32; /* Unsigned 32 bit value */
#endif
# define RTS_UI32_MAX (RTS_UI32)(~((RTS_UI32)0))
#ifndef IEEE754TYPES_DEFINED
# define IEEE754TYPES_DEFINED
typedef float RTS_REAL32; /* 32 bit floating point value */
typedef double RTS_REAL64; /* 64 bit floating point value */
#define RTS_REAL32_MAX 3.4028234e+38
#define RTS_REAL64_MAX 1.7976931348623157e+308
#endif
typedef void* RTS_HANDLE; /* Handle for files, semaphores, ... Usually the native integer type of the platform*/
#define RTS_INVALID_HANDLE ((RTS_HANDLE)(~((uintptr_t)0)))
typedef RTS_UI32 RTS_RESULT; /* Result type for most functions. At least 16 bit. Should be the natural integer of a platform. */
typedef uintptr_t RTS_UINTPTR; /* Unsigned integral type that can hold a pointer */
#define RTS_UINTPTR_MAX (RTS_UINTPTR)(~((RTS_UINTPTR)0))
typedef intptr_t RTS_INTPTR; /* Signed integral type that can hold a pointer */
#define RTS_INTPTR_MAX (RTS_INTPTR)(~((RTS_INTPTR)0))
typedef ptrdiff_t RTS_PTRDIFF; /* Signed integral type that can hold an array index */
#define RTS_PTRDIFF_MAX (RTS_PTRDIFF)(~((RTS_PTRDIFF)0))
#ifndef RTS_SIZE_DEFINED
#define RTS_SIZE_DEFINED
typedef size_t RTS_SIZE; /* Unsigned integral type that can hold a buffer offset */
#endif
#define RTS_SIZE_MAX (RTS_SIZE)(~((RTS_SIZE)0))
/* Type definition of RTS_SSIZE should be changed to standard datatype ssize_t, if available on any target! */
#ifndef RTS_SSIZE_DEFINED
#define RTS_SSIZE_DEFINED
typedef intptr_t RTS_SSIZE; /* integral type that can hold a buffer offset */
#endif
#define RTS_SSIZE_MAX (RTS_SSIZE)(~((RTS_SSIZE)0))
typedef RTS_UINTPTR RTS_TIME; /* Coordinated Universal Time (UTC) */
typedef int RTS_INT; /* Data type has no constant size, so be careful in sharing shuch datatypes with IEC! */
typedef int RTS_BOOL; /* Boolean value (TRUE or FALSE). For best performance use platform specific int.
Data type has no constant size, so be carful in sharing shuch datatypes with IEC! */
#if defined(RTS_DISABLE_64BIT_SUPPORT) && !defined(SYSINTERNAL_DISABLE_64BIT)
#define SYSINTERNAL_DISABLE_64BIT
#endif
#if !defined(BASE64BITTYPES_DEFINED) && !defined(SYSINTERNAL_DISABLE_64BIT)
# define BASE64BITTYPES_DEFINED
typedef uint64_t RTS_UI64;
typedef int64_t RTS_I64;
#define RTS_UI64_MAX (RTS_UI64)(~((RTS_UI64)0))
#define RTS_I64_MAX (RTS_I64)(~((RTS_I64)0))
#endif
typedef char RTS_CSTRING;
typedef char RTS_UTF8STRING;
# ifndef RTS_WCHAR_DEFINED
# define RTS_WCHAR_DEFINED
typedef wchar_t RTS_WCHAR; /* wide character value. We expect 2 bytes unicode here! */
# endif /*RTS_WCHAR_DEFINED*/
# ifndef RTS_CWCHAR_DEFINED
# define RTS_CWCHAR_DEFINED
# ifdef RTS_UNICODE
typedef RTS_WCHAR RTS_CWCHAR;
# else
typedef RTS_CSTRING RTS_CWCHAR;
# endif
# endif /*RTS_CWCHAR_DEFINED*/
#ifdef RTS_DEFINE_MODE_T
typedef unsigned short mode_t;
#endif
#ifdef RTS_DEFINE_GID_T
typedef short gid_t;
#endif
#ifdef RTS_DEFINE_UID_T
typedef short uid_t;
#endif
#ifdef RTS_DEFINE_ID_T
typedef intptr_t id_t;
#endif
#ifdef RTS_DEFINE_PID_T
typedef uintptr_t pid_t;
#endif
#ifdef RTS_DEFINE_OFF_T
typedef uintptr_t off_t;
#endif
#ifdef RTS_DEFINE_NLINK_T
typedef short nlink_t;
#endif
#endif /*BASETYPES_DEFINED*/
typedef enum
{
RTSTYPE_I8,
RTSTYPE_I16,
RTSTYPE_I32,
RTSTYPE_UI8,
RTSTYPE_UI16,
RTSTYPE_UI32,
RTSTYPE_REAL32,
RTSTYPE_REAL64,
RTSTYPE_HANDLE,
RTSTYPE_RESULT,
RTSTYPE_UINTPTR,
RTSTYPE_INTPTR,
RTSTYPE_SIZE,
RTSTYPE_PTRDIFF,
RTSTYPE_INT,
RTSTYPE_BOOL,
RTSTYPE_UI64,
RTSTYPE_I64,
RTSTYPE_CSTRING,
RTSTYPE_WCHAR,
RTSTYPE_CWCHAR
}RTS_TYPECLASS;
typedef union
{
RTS_I8 i8Value;
RTS_I16 i16Value;
RTS_I32 i32Value;
RTS_UI8 ui8Value;
RTS_UI16 ui16Value;
RTS_UI32 ui32Value;
RTS_REAL32 real32Value;
RTS_REAL64 real64Value;
RTS_HANDLE handleValue;
RTS_RESULT resultValue;
RTS_UINTPTR uintptrValue;
RTS_INTPTR intptrValue;
RTS_SIZE sizeValue;
RTS_PTRDIFF ptrdiffValue;
RTS_INT intValue;
RTS_BOOL boolValue;
RTS_UI64 ui64Value;
RTS_I64 i64Value;
RTS_CSTRING *cstringValue;
RTS_WCHAR *wstringValue;
RTS_CWCHAR *cwstringValue;
}RTS_VALUE;
typedef struct
{
RTS_TYPECLASS typeClass;
RTS_VALUE value;
}RTS_TYPEDVALUE;
/*
* IEC basic typedefinitions.
* Defines all datatypes used in IEC programs. Datatypes must match to CoDeSys compiler.
* Systems that overloads standard types must redefine this types in sysdefines.h and set IECBASETYPES_DEFINED
*/
typedef void HUGEPTR(CDECL *RTS_VOID_FCTPTR)(void);
#ifndef IECBASETYPES_DEFINED
#define IECBASETYPES_DEFINED
# define RTS_IEC_VOID_FCTPTR RTS_VOID_FCTPTR
typedef void* RTS_IEC_VOIDPTR;
typedef RTS_I8 RTS_IEC_BOOL;
typedef RTS_I8 RTS_IEC_SINT;
typedef RTS_UI8 RTS_IEC_USINT;
typedef RTS_UI8 RTS_IEC_BYTE;
typedef RTS_I16 RTS_IEC_INT;
typedef RTS_UI16 RTS_IEC_UINT;
typedef RTS_UI16 RTS_IEC_WORD;
typedef RTS_I32 RTS_IEC_DINT;
typedef RTS_UI32 RTS_IEC_UDINT;
typedef RTS_UI32 RTS_IEC_DWORD;
typedef RTS_UI32 RTS_IEC_TIME;
typedef RTS_UI32 RTS_IEC_TIME_OF_DAY;
typedef RTS_UI32 RTS_IEC_TOD;
typedef RTS_UI32 RTS_IEC_DATE_AND_TIME;
typedef RTS_UI32 RTS_IEC_DT;
typedef RTS_UI32 RTS_IEC_DATE;
typedef RTS_I64 RTS_IEC_LINT;
typedef RTS_UI64 RTS_IEC_ULINT;
typedef RTS_UI64 RTS_IEC_LWORD;
typedef RTS_UI64 RTS_IEC_LTIME;
typedef RTS_SIZE RTS_IEC_XWORD;
typedef RTS_UINTPTR RTS_IEC_UXINT;
typedef RTS_PTRDIFF RTS_IEC_XINT;
typedef RTS_REAL32 RTS_IEC_REAL;
typedef RTS_REAL64 RTS_IEC_LREAL;
typedef RTS_CSTRING RTS_IEC_STRING;
# ifdef RTS_UNICODE
typedef RTS_WCHAR RTS_IEC_WSTRING;
# else
typedef RTS_UI16 RTS_IEC_WSTRING;
# endif
typedef RTS_CWCHAR RTS_IEC_CWCHAR;
typedef RTS_SIZE RTS_IEC_SIZE;
typedef RTS_HANDLE RTS_IEC_HANDLE;
typedef RTS_IEC_UDINT RTS_IEC_RESULT;
#endif /*IECBASETYPES_DEFINED*/
#ifndef RTS_MEM_REGION_DEFINED
#define RTS_MEM_REGION_DEFINED
/**
* <description>
* Abstract Memory Region.
* </description>
*/
typedef struct tagRTS_MEM_REGION
{
/* base address of the region */
RTS_UINTPTR base;
/* region size */
RTS_SIZE size;
} RTS_MEM_REGION;
#endif
#ifndef EXT_FUNCT_REF_DEFINED
#define EXT_FUNCT_REF_DEFINED
/**
* Exported function descriptor
*/
typedef struct tagCMP_EXT_FUNCTION_REF
{
/* pointer to an exported function */
RTS_VOID_FCTPTR pfExtCall;
/* name of an exported function */
const char* pszExtCallName;
/* signature */
RTS_UI32 signature;
/* version */
RTS_UI32 version;
} CMP_EXT_FUNCTION_REF;
#endif
/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */
/* as a required function (error if it is unresolved) */
#define CM_IMPORT_REQUIRED_FUNCTION 0
/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */
/* as an external libray function */
#define CM_IMPORT_EXTERNAL_LIB_FUNCTION 1
/* The CMGetAPI2/CMGetAPI3 importOptions parameter flag which specifies an import */
/* as an optional function */
#define CM_IMPORT_OPTIONAL_FUNCTION 2
/* Imported function belongs to an external libray */
#define ImportIsExternalLibFunction(par) (0 != ((par) & CM_IMPORT_EXTERNAL_LIB_FUNCTION))
/* Imported function is optional */
#define ImportIsOptionalFunction(par) (0 != ((par) & CM_IMPORT_OPTIONAL_FUNCTION))
#define RTS_SIZEOF sizeof
#define RTS_GET_WORD_OFFSET(typesize) (typesize)
#define RTS_GET_WORD_SIZE RTS_GET_WORD_OFFSET
#define RTS_GET_BYTE_OFFSET(offset) (offset)
#define RTS_GET_BYTE_SIZE(size) (size)
#define RTS_MEMSET memset
#define RTS_ALIGN_4BYTES 4
#define RTS_BITS_IN_BYTE 8
#define RTS_EMPTY_STRING ""
#define RTS_IS_EMPTY_STRING(s) ((char *)s == NULL || strcmp((char *)s, RTS_EMPTY_STRING) == 0)
#define RTS_TIMEOUT_INFINITE (~(RTS_UI32)0)
#define RTS_TIMEOUT_DEFAULT (RTS_UI32)(-2)
#define RTS_TIMEOUT_NO_WAIT (0)
#define RTS_INVALID_FILETRANSFER (RTS_UI32)(-1)
#include "CmpItf.h"
#ifndef COMPONENT_NAME_POSTFIX
#define COMPONENT_NAME_POSTFIX
#endif
#endif /*__RTSSTD_H__*/
File diff suppressed because it is too large Load Diff
+897
View File
@@ -0,0 +1,897 @@
divert(-1)
% -----------------------------------
% Copyright: Copyright CODESYS Development GmbH
% -----------------------------------
# The following lines ("DO NOT MODIFY...") generate a message in the destination file.
# This comment is not aimed at this file.
divert`'dnl
/***********************************************************************
* DO NOT MODIFY!
* This is a generated file. Do not modify it's contents directly
***********************************************************************/
include(`Util.m4')
divert(-1)
changecom(`%')
% -----------------------------
% Define some diverts that gather information about some special features
% and initialize those diversions
% -----------------------------
define(DIVERT_USE,3)
define(DIVERT_USEIMPORT,4)
define(DIVERT_USEEXTERN,5)
define(DIVERT_INIT,6)
define(DIVERT_RENAME_ENTRYFUN,9)
define(DIVERT_BEGINCLASSDEF,10)
define(DIVERT_CLASSDEF,11)
define(DIVERT_CLASSMEMBERS,12)
define(DIVERT_ENDCLASSDEF,13)
define(DIVERT_ENDMETHODDEF,15)
define(DIVERT_URTS_DEFS, 20)
define(DIVERT_ENDFILE,100)
define(`REF_ITF', `GEN_INCLUDE($1)')
define(`COMPONENT_VERSION',`#define CMP_VERSION UINT32_C(`$1')
#define CMP_VERSION_STRING "_VERSION_MAJOR(`$1')._VERSION_SUB0(`$1')._VERSION_SUB1(`$1')._VERSION_SUB2(`$1')"
#define CMP_VERSION_RC _VERSION_MAJOR(`$1'),_VERSION_SUB0(`$1'),_VERSION_SUB1(`$1'),_VERSION_SUB2(`$1')')
define(`COMPONENT_VENDORID',`#define CMP_VENDORID $1
#ifndef WIN32_RESOURCES')
divert(DIVERT_USE)dnl
#define USE_STMT \
/*lint -save --e{528} --e{551} */ \
static volatile PF_REGISTER_API s_pfCMRegisterAPI; \
static volatile PF_REGISTER_API2 s_pfCMRegisterAPI2; \
static volatile PF_GET_API s_pfCMGetAPI; \
static volatile PF_GET_API2 s_pfCMGetAPI2; \
static volatile PF_REGISTER_CLASS s_pfCMRegisterClass; \
static volatile PF_CREATEINSTANCE s_pfCMCreateInstance; \
static volatile PF_CALL_HOOK s_pfCMCallHook; \
static const CMP_EXT_FUNCTION_REF s_ExternalsTable[] =\
{\
EXPORT_EXTREF_STMT\
EXPORT_EXTREF2_STMT\
{ ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\
};\
static const CMP_EXT_FUNCTION_REF s_ItfTable[] = EXPORT_CMPITF_STMT; \
/*lint -restore */ \
static int CDECL ExportFunctions(void); \
static int CDECL ImportFunctions(void); \
static IBase* CDECL CreateInstance(CLASSID cid, RTS_RESULT *pResult); \
static RTS_RESULT CDECL DeleteInstance(IBase *pIBase); \
static RTS_UI32 CDECL CmpGetVersion(void); \
static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2); \
dnl
divert(-1)
divert(DIVERT_USEIMPORT)
#define USEIMPORT_STMT \
/*lint -save --e{551} */ \
static volatile PF_REGISTER_API s_pfCMRegisterAPI; \
static volatile PF_REGISTER_API2 s_pfCMRegisterAPI2; \
static volatile PF_GET_API s_pfCMGetAPI; \
static volatile PF_GET_API2 s_pfCMGetAPI2; \
static volatile PF_REGISTER_CLASS s_pfCMRegisterClass; \
static volatile PF_CREATEINSTANCE s_pfCMCreateInstance; \
static volatile PF_CALL_HOOK s_pfCMCallHook; \
/*lint -restore */ \
dnl
divert(-1)
divert(DIVERT_USEEXTERN)
#define USEEXTERN_STMT \
dnl
divert(-1)
divert(DIVERT_RENAME_ENTRYFUN)
#ifndef COMPONENT_NAME
#error COMPONENT_NAME is not defined. This prevents the component from being linked statically. Use `SET_COMPONENT_NAME'(<name_of_your_component>) to set the name of the component in your .m4 component description.
#endif
divert(-1)
divert(DIVERT_BEGINCLASSDEF)
#ifdef CPLUSPLUS
divert(-1)
divert(DIVERT_ENDMETHODDEF)
#endif /*CPLUSPLUS*/
divert(-1)
divert(DIVERT_ENDFILE)
`#endif /*WIN32_RESOURCES*/'
`#endif /*_DEP_H_*/'
divert(-1)')
% ---------------------------
% The SET_COMPONENT_NAME directive should reside at the top of each "Itf" file.
% ---------------------------
define(`SET_COMPONENT_NAME',
`define(_COMPONENT_NAME,`$1')dnl
`#ifndef _'_UPPERCASE(_COMPONENT_NAME)`DEP_H_'
`#define _'_UPPERCASE(_COMPONENT_NAME)`DEP_H_'
#define COMPONENT_NAME "$1" COMPONENT_NAME_POSTFIX
#define COMPONENT_ID ADDVENDORID(CMP_VENDORID, CMPID_$1)
#define COMPONENT_NAME_UNQUOTED $1
divert(DIVERT_RENAME_ENTRYFUN)
divert(DIVERT_RENAME_ENTRYFUN)
#if defined(STATIC_LINK) || defined(MIXED_LINK) || defined(DYNAMIC_LINK) || defined(CPLUSPLUS_STATIC_LINK)
#define ComponentEntry $1`'__Entry
#endif
divert
divert(DIVERT_URTS_DEFS)dnl
divert
')
% -----------------------------
% The COMPONENT_SOURCES and COMPONENT_SYSSOURCES macros are to be ignored. They
% are used by the configuration tool only.
% -----------------------------
define(`COMPONENT_SOURCES',`dnl')
define(`EXTERNAL_SOURCES',`dnl')
define(`EXTERNAL_INCLUDES',`dnl')
define(`COMPONENT_INCLUDES',`dnl')
define(`COMPONENT_SYSSOURCES',`dnl')
define(`CONTRIB_SOURCES',`dnl')
define(`CONTRIB_INCLUDES',`dnl')
')
% -----------------------------
% The CATEGORY macro is to be ignored. It
% is used by the configuration tool only.
% -----------------------------
define(`CATEGORY',`dnl')
')
% -----------------------------
% After all input is read, some macros have to be created
% -----------------------------
define(`ADD_IMPORTS',`ifdef(`__IMPORTS',`__IMPORTS popdef(`__IMPORTS') ADD_IMPORTS')')
define(`CREATE_IMPORT_STMT', dnl
`ifdef(`__IMPORTS',`#if defined(STATIC_LINK)
#define IMPORT_STMT
#else
#define IMPORT_STMT \
{\
RTS_RESULT importResult = ERR_OK;\
RTS_RESULT TempResult = ERR_OK;\
INIT_STMT \
TempResult = GET_LogAdd(CM_IMPORT_OPTIONAL_FUNCTION); \
TempResult = GET_CMUtlMemCpy(CM_IMPORT_OPTIONAL_FUNCTION); \
ADD_IMPORTS \
/* To make LINT happy */\
TempResult = TempResult;\
if (ERR_OK != importResult) return importResult;\
}
#endif
',`#define IMPORT_STMT INIT_STMT')
')
% -----------------------------
% This function emits all export statements inside the macro EXPORT_STMT (one after another)
% -----------------------------
define(`ADD_EXPORTS',`ifdef(`__EXPORTS',`__EXPORTS popdef(`__EXPORTS') ADD_EXPORTS')')
define(`ADD_EXTREF_EXPORTS',`ifdef(`__EXPORTS_EXTREF',`__EXPORTS_EXTREF popdef(`__EXPORTS_EXTREF') ADD_EXTREF_EXPORTS')')
define(`ADD_EXTREF_EXPORTS2',`ifdef(`__EXPORTS_EXTREF2',`__EXPORTS_EXTREF2 popdef(`__EXPORTS_EXTREF2') ADD_EXTREF_EXPORTS2')')
define(`ADD_EXPORTS_CPLUSPLUS_ONLY',`ifdef(`__EXPORTS_CPLUSPLUS_ONLY',`__EXPORTS_CPLUSPLUS_ONLY popdef(`__EXPORTS_CPLUSPLUS_ONLY') ADD_EXPORTS_CPLUSPLUS_ONLY')')
% -----------------------------
% Define the EXPORT_STMT macro. the content is filled out by ADD_EXPORTS.
% -----------------------------
define(`CREATE_EXPORT_STMT',dnl
`ifdef(`__EXPORTS_EXTREF',dnl
`#ifndef _UPPERCASE(_COMPONENT_NAME)_DISABLE_EXTREF
#define EXPORT_EXTREF_STMT \
ADD_EXTREF_EXPORTS
#else
#define EXPORT_EXTREF_STMT
#endif',`#define EXPORT_EXTREF_STMT')
'dnl
`ifdef(`__EXPORTS_EXTREF2',dnl
`#ifndef _UPPERCASE(_COMPONENT_NAME)_DISABLE_EXTREF2
#define EXPORT_EXTREF2_STMT \
ADD_EXTREF_EXPORTS2
#else
#define EXPORT_EXTREF2_STMT
#endif',`#define EXPORT_EXTREF2_STMT')
'dnl
`ifdef(`__EXPORTS',dnl
`#if !defined(_UPPERCASE(_COMPONENT_NAME)_DISABLE_CMPITF) && !defined(STATIC_LINK) && !defined(CPLUSPLUS) && !defined(CPLUSPLUS_ONLY)
#define EXPORT_CMPITF_STMT \
{\
ADD_EXPORTS\
{ ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\
}
#else
#define EXPORT_CMPITF_STMT \
{\
{ ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }\
}
#endif',`#define EXPORT_CMPITF_STMT {{ ((RTS_VOID_FCTPTR)(void *)0), "", 0, 0 }}')
'dnl
`ifdef(`__EXPORTS_CPLUSPLUS_ONLY', `
#define EXPORT_CPP_STMT \
{\
ADD_EXPORTS_CPLUSPLUS_ONLY \
}
',`#define EXPORT_CPP_STMT')
'dnl
)
% -----------------------------
% This function emits all init statements inside the macro INIT_STMT (one after another)
% -----------------------------
define(`ADD_INITS',`ifdef(`__INITS',`__INITS popdef(`__INITS') ADD_INITS')')
define(`ADD_INIT_LOCALS',`ifdef(`__INIT_LOCALS',`__INIT_LOCALS popdef(`__INIT_LOCALS') ADD_INIT_LOCALS')')
define(`ADD_EXITS',`ifdef(`__EXITS',`__EXITS popdef(`__EXITS') ADD_EXITS')')
% -----------------------------
% Define the INIT_STMT macro.
% -----------------------------
define(`CREATE_INIT_STMT', dnl
`ifdef(`__INITS',``
#ifdef CPLUSPLUS
#define INIT_STMT' \
{\
IBase *pIBase;\
RTS_RESULT initResult;\
if (pICmpLog == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCmpLog, &initResult); \
if (pIBase != NULL) \
{ \
pICmpLog = (ICmpLog *)pIBase->QueryInterface(pIBase, ITFID_ICmpLog, &initResult); \
pIBase->Release(pIBase); \
} \
} \
if (pICMUtils == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCMUtils, &initResult); \
if (pIBase != NULL) \
{ \
pICMUtils = (ICMUtils *)pIBase->QueryInterface(pIBase, ITFID_ICMUtils, &initResult); \
pIBase->Release(pIBase); \
} \
} \
ADD_INITS \
}
#define INIT_LOCALS_STMT \
{\
pICmpLog = NULL; \
pICMUtils = NULL; \
ADD_INIT_LOCALS \
}
#define EXIT_STMT \
{\
IBase *pIBase;\
RTS_RESULT exitResult;\
if (pICmpLog != NULL) \
{ \
pIBase = (IBase *)pICmpLog->QueryInterface(pICmpLog, ITFID_IBase, &exitResult); \
if (pIBase != NULL) \
{ \
pIBase->Release(pIBase); \
if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \
pICmpLog = NULL; \
} \
} \
if (pICMUtils != NULL) \
{ \
pIBase = (IBase *)pICMUtils->QueryInterface(pICMUtils, ITFID_IBase, &exitResult); \
if (pIBase != NULL) \
{ \
pIBase->Release(pIBase); \
if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \
pICMUtils = NULL; \
} \
} \
ADD_EXITS \
}
#else
#define INIT_STMT
#define INIT_LOCALS_STMT
#define EXIT_STMT
#endif
',`#ifdef CPLUSPLUS
#define INIT_STMT \
{\
IBase *pIBase;\
RTS_RESULT initResult;\
if (pICmpLog == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCmpLog, &initResult); \
if (pIBase != NULL) \
{ \
pICmpLog = (ICmpLog *)pIBase->QueryInterface(pIBase, ITFID_ICmpLog, &initResult); \
pIBase->Release(pIBase); \
} \
} \
if (pICMUtils == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_CCMUtils, &initResult); \
if (pIBase != NULL) \
{ \
pICMUtils = (ICMUtils *)pIBase->QueryInterface(pIBase, ITFID_ICMUtils, &initResult); \
pIBase->Release(pIBase); \
} \
} \
}
#define INIT_LOCALS_STMT \
{\
pICmpLog = NULL; \
pICMUtils = NULL; \
ADD_INIT_LOCALS \
}
#define EXIT_STMT \
{\
IBase *pIBase;\
RTS_RESULT exitResult;\
if (pICmpLog != NULL) \
{ \
pIBase = (IBase *)pICmpLog->QueryInterface(pICmpLog, ITFID_IBase, &exitResult); \
if (pIBase != NULL) \
{ \
pIBase->Release(pIBase); \
if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \
pICmpLog = NULL; \
} \
} \
if (pICMUtils != NULL) \
{ \
pIBase = (IBase *)pICMUtils->QueryInterface(pICMUtils, ITFID_IBase, &exitResult); \
if (pIBase != NULL) \
{ \
pIBase->Release(pIBase); \
if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \
pICMUtils = NULL; \
} \
} \
}
#else
#define INIT_STMT
#define INIT_LOCALS_STMT
#define EXIT_STMT
#endif')
')
% -----------------------------
% The next lines causes the CREATE_IMPORT_STMT, CREATE_EXPORT_STMT
% and CREATE_INIT_STMT function to be called after all input has been read
% -----------------------------
m4wrap(`
CREATE_EXPORT_STMT
#if defined(STATIC_LINK)
#define EXPORT_STMT\
{\
RTS_RESULT ExpResult;\
if (NULL == s_pfCMRegisterAPI)\
return ERR_NOTINITIALIZED;\
ExpResult = s_pfCMRegisterAPI(s_ExternalsTable, 0, 1, COMPONENT_ID);\
if (ERR_OK != ExpResult)\
return ExpResult;\
}
#else
#define EXPORT_STMT\
{\
RTS_RESULT ExpResult;\
if (NULL == s_pfCMRegisterAPI)\
return ERR_NOTINITIALIZED;\
ExpResult = s_pfCMRegisterAPI(s_ExternalsTable, 0, 1, COMPONENT_ID);\
if (ERR_OK != ExpResult)\
return ExpResult;\
ExpResult = s_pfCMRegisterAPI(s_ItfTable, 0, 0, COMPONENT_ID);\
if (ERR_OK != ExpResult)\
return ExpResult;\
}
#endif
')
m4wrap(`
CREATE_IMPORT_STMT
')
m4wrap(`
CREATE_INIT_STMT
')
% -----------------------------
% Generate an "#include " directive,
% replacing the extension ".m4" with ".h"
% -----------------------------
define(`GET_INCLUDE_FILE', `ifelse(`-1',regexp(`$1',`\.m4$'),`error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file')',dnl
`/**
* \file 'patsubst(`$1',`\.m4$',`\.h')`
*/')')
define(`GEN_INCLUDE', dnl
`ifelse(`-1',regexp(`$1',`\.m4$'), dnl
`error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file
')',dnl
$1,`CmpLogItf.m4', `/*Obsolete include: CmpLogItf.m4*/',dnl
$1,`CMUtilsItf.m4', `/*Obsolete include: CMUtilsItf.m4*/',dnl
`#include "patsubst(`$1',`\.m4$',`\.h')"')')
define(`GEN_INIT_STMT', `pushdef(`__INITS',ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \
',
$1,`CmpLog',`/*Obsolete include CmpLog*/ \
',
`if (pI$1 == NULL && s_pfCMCreateInstance != NULL) \
{ \
pIBase = (IBase *)s_pfCMCreateInstance(CLASSID_C$1, &initResult); \
if (pIBase != NULL) \
{ \
pI$1 = (I$1 *)pIBase->QueryInterface(pIBase, ITFID_I$1, &initResult); \
pIBase->Release(pIBase); \
} \
} \
'))')
define(`GEN_INIT_LOCALS_STMT', `pushdef(`__INIT_LOCALS',`ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \
',
$1,`CmpLog',`/*Obsolete include CmpLog*/ \
',
`pI$1 = NULL; \
')')')
define(`GEN_EXIT_STMT', `pushdef(`__EXITS',`ifelse($1,`CMUtils',`/*Obsolete include CMUtils*/ \
',
$1,`CmpLog',`/*Obsolete include CmpLog*/ \
',
`if (pI$1 != NULL) \
{ \
pIBase = (IBase *)pI$1->QueryInterface(pI$1, ITFID_IBase, &exitResult); \
if (pIBase != NULL) \
{ \
pIBase->Release(pIBase); \
if (pIBase->Release(pIBase) == 0) /* The object will be deleted here! */ \
pI$1 = NULL; \
} \
} \
')')')
define(`GEN_USED_INTERFACES', dnl
`ifelse(`-1',regexp(`$1',`\.m4$'), dnl
`error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file
')',dnl
`GEN_INCLUDE($1)'dnl
`GEN_INIT_STMT(`'patsubst(`$1',`\Itf.m4$',`'))'dnl
`GEN_INIT_LOCALS_STMT(`'patsubst(`$1',`\Itf.m4$',`'))'dnl
`GEN_EXIT_STMT(`'patsubst(`$1',`\Itf.m4$',`'))')dnl
')
% -----------------------------
% All variations of DEF_*_API
% Depending on the Variable DEF_CPP,
% we are adding them to __EXPORTS or __EXPORTS_CPP.
% -----------------------------
define(`PUSH_ITF', dnl
`ifdef(`DEF_CPP', dnl
`pushdef(`__EXPORTS_CPLUSPLUS_ONLY',` \
if (ERR_OK != EXP_`'_COMPONENT_NAME`'$3) \
{\
if (CHK_LogAdd)\
(void)CAL_LogAdd(STD_LOGGER, COMPONENT_ID, LOG_ERROR, ERR_FAILED, 0, "Export of ApiFunction [$3] failed\n"); \
return ERR_FAILED;\
}')'
, dnl
`pushdef(`__EXPORTS_EXTREF',`ifelse(`$5',1,`ifelse(`$8',1,`', `{ (RTS_VOID_FCTPTR)$3, "$3", ifelse(eval($#>5),1,$6,0), ifelse(eval($#>6),1,$7,0) },\
')',`')')' dnl
`pushdef(`__EXPORTS_EXTREF2',`ifelse(`$8',1,`{ (RTS_VOID_FCTPTR)$3, "$3", $6, $7 }, \
',`')')' dnl
`pushdef(`__EXPORTS',`ifelse(`$5',1,`',`{ (RTS_VOID_FCTPTR)$3, "$3", 0, 0 },\
')')' dnl
)' dnl
)
define(`PUSH_CLASS_ITF', dnl
`ifdef(`DEF_CPP', dnl
`pushdef(`__EXPORTS_CPLUSPLUS_ONLY',` \
if (ERR_OK != EXP_`'_COMPONENT_NAME`'$3) \
{\
if (CHK_LogAdd)\
(void)CAL_LogAdd(STD_LOGGER, COMPONENT_ID, LOG_ERROR, ERR_FAILED, 0, "Export of ApiFunction [$3] failed\n"); \
return ERR_FAILED;\
}')'
, dnl
`pushdef(`__EXPORTS_EXTREF',`ifelse(`$5',1,`ifelse(`$8',1,`',`{ (RTS_VOID_FCTPTR)$3, "$3",ifelse(eval($#>5),1,$6,0),ifelse(eval($#>6),1,$7,0)},\
')',`')')' dnl
`pushdef(`__EXPORTS_EXTREF2',`ifelse(`$8',1,`{ (RTS_VOID_FCTPTR)$3, "$3", $6, $7 }, \
',`')')' dnl
`pushdef(`__EXPORTS',`')' dnl
)' dnl
)
define(`DEF_API', dnl
`PUSH_ITF($@)' dnl
)
define(`DEF_STATIC_API',
`PUSH_ITF($@)' dnl
)
define(`DEF_CREATEITF_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_DELETEITF_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_HANDLEITF_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_HANDLEITF_API2',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_HANDLEITF_API_OWNCPP',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_CREATECLASSITF_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_CREATECLASSITF2_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_PRODUCECLASSITF_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_PRODUCECLASSITF_API_OWNCPP',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_DELETECLASSITF_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_DELETECLASSITF2_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_CLASSITF_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_RETURNHANDLECLASSITF_API',
`PUSH_CLASS_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_STATICITF_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) static $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_ITF_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_ITF_API_OWNCPP',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_ITF_API2',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`DEF_ITF_GLOBAL_API',
`PUSH_ITF($@)' dnl
`divert(DIVERT_CLASSMEMBERS) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4')));
divert(-1)'
)
define(`SET_INTERFACE_NAME',`
ifdef(`_IN_IMPLEMENT',`
pushdef(`_INTERFACE_LIST', `$1')
pushdef(`_INTERFACECAST_LIST',`
else if (iid == ITFID_`I'$1)
pItf = dynamic_cast<`I'$1 *>(this);')',
`ifelse($1,`CMUtils',`divert(DIVERT_USE)\
/*obsolete entry ITF_$1*/ dnl divert(-1)',
$1,`CmpLog',`divert(DIVERT_USE)\
/*obsolete entry ITF_$1*/ dnl divert(-1)',
`divert(DIVERT_USE)\
`ITF_'`$1'dnl')
ifelse($1,`CMUtils',`divert(DIVERT_USEIMPORT)\
/*obsolete entry ITF_$1*/ dnl divert(-1)',
$1,`CmpLog',`divert(DIVERT_USEIMPORT)\
/*obsolete entry ITF_$1*/ dnl divert(-1)',
`divert(DIVERT_USEIMPORT)\
`ITF_'`$1'dnl')
ifelse($1,`CMUtils',`divert(DIVERT_USEEXTERN)\
/*obsolete entry EXTITF_$1*/ dnl divert(-1)',
$1,`CmpLog',`divert(DIVERT_USEEXTERN)\
/*obsolete entry EXTITF_$1*/ dnl divert(-1)',
`divert(DIVERT_USEEXTERN)\
`EXTITF_'`$1'dnl')
divert(-1)')
')
define(`WRITE_INTERFACE_LIST2',`ifdef(`_INTERFACE_LIST',`, public `I'_INTERFACE_LIST popdef(`_INTERFACE_LIST')WRITE_INTERFACE_LIST2')')
define(`WRITE_INTERFACE_LIST',`ifdef(`_INTERFACE_LIST',`define(`_FIRST_INTERFACE_NAME',_INTERFACE_LIST)public `I'_INTERFACE_LIST popdef(`_INTERFACE_LIST')WRITE_INTERFACE_LIST2')')
define(`WRITE_INTERFACE_CASTS',`ifdef(`_INTERFACECAST_LIST',`_INTERFACECAST_LIST popdef(`_INTERFACECAST_LIST')WRITE_INTERFACE_CASTS')')
define(`TASK',`#ifndef RTS_TASKNAME_`'_UPPERCASE($1)
#define RTS_TASKNAME_`'_UPPERCASE($1) "$1"
#endif
#ifndef RTS_TASKPRIO_`'_UPPERCASE($1)
#define RTS_TASKPRIO_`'_UPPERCASE($1) $2
#endif
ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1)
#define RTS_TASKGROUP_`'_UPPERCASE($1) $3
#endif')
')
define(`TASKPREFIX',`#ifndef RTS_TASKPREFIX_`'_UPPERCASE($1)
#define RTS_TASKPREFIX_`'_UPPERCASE($1) "$1"
#endif
#ifndef RTS_TASKPRIO_`'_UPPERCASE($1)
#define RTS_TASKPRIO_`'_UPPERCASE($1) $2
#endif
ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1)
#define RTS_TASKGROUP_`'_UPPERCASE($1) $3
#endif')
')
define(`TASKPLACEHOLDER',`#ifndef RTS_TASKPLACEHOLDER_`'_UPPERCASE($1)
#define RTS_TASKPLACEHOLDER_`'_UPPERCASE($1) ""
#endif
#ifndef RTS_TASKPRIO_`'_UPPERCASE($1)
#define RTS_TASKPRIO_`'_UPPERCASE($1) $2
#endif
ifelse($3,`',`', `#ifndef RTS_TASKGROUP_`'_UPPERCASE($1)
#define RTS_TASKGROUP_`'_UPPERCASE($1) $3
#endif')
')
% -----------------------------
% Get all interfaces, which are implemented by this component.
% Differentiate between C++ and C Interfaces.
% -----------------------------
define(`DO_IMPLEMENT_ITF',`
GET_INCLUDE_FILE($1)
GEN_INCLUDE($1)
ifelse(`$#',1,`',`DO_IMPLEMENT_ITF(shift($@))')
divert(-1)
include($1)
divert
')
define(`IMPLEMENT_QUERY_INTERFACE',`define(`QUERY_INTERFACE', `$1')')
define(`IMPLEMENT_ITF',dnl
`undefine(`DEF_CPP')'dnl
`IMPLEMENT_ITF_AS(``C'_COMPONENT_NAME', $@)'
)
define(`IMPLEMENT_CPP_ITF',
`define(`DEF_CPP')'
`undefine(`_INTERFACE_LIST')undefine(`_INTERFACECAST_LIST')undefine(`_FIRST_INTERFACE_NAME')define(`_IN_IMPLEMENT')define(`_INTERFACE_LIST_INIT')'
`DO_IMPLEMENT_ITF($@)'
)
define(`IMPLEMENT_ITF_AS',
`ifdef(`_INTERFACE_LIST_INIT', `',
`undefine(`_INTERFACE_LIST')undefine(`_INTERFACECAST_LIST')undefine(`_FIRST_INTERFACE_NAME')'
)'
`define(`_IN_IMPLEMENT')define(`_INTERFACE_LIST_INIT')'
`
DO_IMPLEMENT_ITF(shift($@))
divert(DIVERT_CLASSDEF)
class $1 : WRITE_INTERFACE_LIST
{
public:
$1`() : h'_COMPONENT_NAME`'(RTS_INVALID_HANDLE), iRefCount(0)
{
}
virtual ~$1`()'
{
}
virtual unsigned long AddRef(IBase *pIBase = NULL)
{
iRefCount++;
return iRefCount;
}
virtual unsigned long Release(IBase *pIBase = NULL)
{
iRefCount--;
if (iRefCount == 0)
{
delete this;
return 0;
}
return iRefCount;
}
ifdef(`QUERY_INTERFACE', `QUERY_INTERFACE', `
virtual void* QueryInterface(IBase *pIBase, ITFID iid, RTS_RESULT *pResult)
{
void *pItf;
if (iid == ITFID_IBase)
pItf = dynamic_cast<IBase *>((`I'_FIRST_INTERFACE_NAME *)this);dnl
WRITE_INTERFACE_CASTS
else
{
if (pResult != NULL)
*pResult = ERR_NOTIMPLEMENTED;
return NULL;
}
if (pResult != (RTS_RESULT *)1)
(reinterpret_cast<IBase *>(pItf))->AddRef();
if (pResult != NULL && pResult != (RTS_RESULT *)1)
*pResult = ERR_OK;
return pItf;
}')
divert(DIVERT_ENDCLASSDEF)
public:
RTS_HANDLE h`'_COMPONENT_NAME;
int iRefCount;
};
divert(-1)
define(`DIVERT_CLASSDEF',eval(DIVERT_CLASSDEF+3))
define(`DIVERT_CLASSMEMBERS',eval(DIVERT_CLASSMEMBERS+3))
define(`DIVERT_ENDCLASSDEF',eval(DIVERT_ENDCLASSDEF+3))
undefine(`_IN_IMPLEMENT')
divert
')
%define(`IMPLEMENT_ITF', `
%divert(-1)
%include($1)
%divert')
define(`GENERATE_ITF_MACRO', `divert(DIVERT_USE)\
`ITF_'`$1' dnl
divert(-1)')
define(`USE_ITF', `GEN_USED_INTERFACES($1)
divert(-1)
pushdef(`DEF_API',`')
pushdef(`DEF_STATIC_API',`')
pushdef(`DEF_CREATEITF_API',`')
pushdef(`DEF_DELETEITF_API',`')
pushdef(`DEF_HANDLEITF_API',`')
pushdef(`DEF_HANDLEITF_API2',`')
pushdef(`DEF_HANDLEITF_API_OWNCPP',`')
pushdef(`DEF_CREATECLASSITF_API',`')
pushdef(`DEF_CREATECLASSITF2_API',`')
pushdef(`DEF_PRODUCECLASSITF_API',`')
pushdef(`DEF_PRODUCECLASSITF_API_OWNCPP',`')
pushdef(`DEF_DELETECLASSITF_API',`')
pushdef(`DEF_DELETECLASSITF2_API',`')
pushdef(`DEF_CLASSITF_API',`')
pushdef(`DEF_RETURNHANDLECLASSITF_API',`')
pushdef(`DEF_STATICITF_API',`')
pushdef(`DEF_ITF_API',`')
pushdef(`DEF_ITF_API_OWNCPP',`')
pushdef(`DEF_ITF_API2',`')
pushdef(`DEF_ITF_GLOBAL_API',`')
include($1)
popdef(`DEF_API')
popdef(`DEF_STATIC_API')
popdef(`DEF_CREATEITF_API')
popdef(`DEF_DELETEITF_API')
popdef(`DEF_HANDLEITF_API')
popdef(`DEF_HANDLEITF_API_OWNCPP')
popdef(`DEF_HANDLEITF_API2')
popdef(`DEF_CREATECLASSITF_API')
popdef(`DEF_CREATECLASSITF2_API')
popdef(`DEF_PRODUCECLASSITF_API')
popdef(`DEF_PRODUCECLASSITF_API_OWNCPP')
popdef(`DEF_DELETECLASSITF_API')
popdef(`DEF_DELETECLASSITF2_API')
popdef(`DEF_RETURNHANDLECLASSITF_API')
popdef(`DEF_CLASSITF_API')
popdef(`DEF_STATICITF_API')
popdef(`DEF_ITF_API2')
popdef(`DEF_ITF_API')
popdef(`DEF_ITF_API_OWNCPP')
popdef(`DEF_ITF_GLOBAL_API')
divert
')
define(`DOUSE', `divert(DIVERT_USE) \
ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/',
$1,`LogAdd',`/*obsolete USE_$1*/',
USE_$1) dnl
divert(DIVERT_USEIMPORT) \
ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/',
$1,`LogAdd',`/*obsolete USE_$1*/',
USE_$1) dnl
divert(DIVERT_USEEXTERN) \
ifelse($1,`CMUtlMemCpy',`/*obsolete USE_$1*/',
$1,`LogAdd',`/*obsolete EXT_$1*/',
EXT_$1) dnl
divert')
define(`DOIMPORT_REQ', `pushdef(`__IMPORTS',`ifelse($1,`LogAdd', `/*Obsolete required include LogAdd*/ \
',dnl
$1,`CMUtlMemCpy', `/*Obsolete required include CMUtlMemCpy*/ \
',
`if (ERR_OK == importResult ) importResult = GET_$1(0);\
')')')
define(`DOIMPORT_OPT', `pushdef(`__IMPORTS',`ifelse($1,`LogAdd', `/*Obsolete optional include LogAdd*/ \
',dnl
$1,`CMUtlMemCpy', `/*Obsolete optional include CMUtlMemCpy*/ \
',
`if (ERR_OK == importResult ) TempResult = GET_$1(CM_IMPORT_OPTIONAL_FUNCTION);\
')')')
define(`REQUIRED_IMPORTS', dnl
`ifelse($1,`', `', dnl
`_FOREACH(`DOIMPORT_REQ', $@)dnl
_FOREACH(`DOUSE', $@)dnl
')
')
define(`OPTIONAL_IMPORTS', dnl
`ifelse($1,`', `', dnl
`_FOREACH(`DOIMPORT_OPT', $@) dnl
_FOREACH(`DOUSE', $@) dnl
')
')
changecom(`/*', `*/')
divert
+113
View File
@@ -0,0 +1,113 @@
divert(-1)
changecom(`%')
%
% Copyright: Copyright CODESYS Development GmbH
%
define(`_CONCAT',`ifelse($#,0,`',$#,1,`$1',$#,2,`$1$2',`_CONCAT(`$1$2',`_CONCAT(shift(shift($@)))')')')
define(`_UPPERCASE', `translit($1, `a-z', `A-Z')')
define(`_TRIM',`regexp(`$1',`^[ ]*\(.*[^ ]\)[ ]*$',`\1')')
define(`_TRIMCMPPREFIX',`regexp(`$1',`...\(.*\)$',`\1')')
define(`_TRIMPOINTERPARAM', `ifelse(regexp(`$1',`[^,]\*',`RTS_HANDLE'),-1,0,1)')
define(`_TRIMVARPARAMLIST', `ifelse(regexp(`$1',`\.\.\.'),-1,0,1)')
define(`_FUN_PTR', _FUN_PTR1(`_UPPERCASE(`$1')'))
define(`_FUN_PTR_IEC', _FUN_PTR1(`_UPPERCASE(`$1')'_IEC))
define(`_FUN_PTR1', `PF'$1)
define( _CHKSUM, 0)
define(`_EXTRACTFIRSTPARAMTYPE',`_EXTRACTTYPE(_EXTRACTPARAMS(`$1'))')
define(`_EXTRACTTYPE',`regexp(`$1',`\([\w_]*[^ ]*\)',`\1')')
define(`_EXTRACTFIRSTPARAMTYPEPOINTER',`_EXTRACTTYPEPOINTER(_EXTRACTPARAMS(`$1'))')
define(`_EXTRACTTYPEPOINTER',`regexp(`$1',`\([\*]\)',`\1')')
define(`_EXTRACTFIRSTPARAMTYPERTS',`_EXTRACTTYPERTS(_EXTRACTPARAMS(`$1'))')
define(`_EXTRACTTYPERTS',`regexp(`$1',`\(RTS_\)',`\1')')
define(`_EXTRACTPARAMS',`regexp(`$1',`^[ ]*(\(.*\))[ ]*$',`\1')')
define(`_EXTRACTPARAMNAME',`regexp(`$1',`\([a-zA-Z0-9_]+\)$',`\1')')
define(`_HAS_VARIABLEPARAMETER',`regexp(`$1',`\.\.\.')')
define(`_EXTRACT_VARIABLEPARAMETER_FORMAT',`regexp(`$1',` \*?\([a-zA-Z0-9_]+\), \.\.\.',`\1')')
%
% Extract the parameter out of a parameter list.
%
% _EXTRACT_PARAM_LIST(`(int nCount, char *pBuf)') => `nCount,pBuf'
% _EXTRACT_PARAM_LIST(`(void)') => `'
%
define(_EXTRACT_PARAM_LIST_RECURSIVE,`ifelse($#,1,`_EXTRACTPARAMNAME(`$1')',`_EXTRACTPARAMNAME(`$1')`',_EXTRACT_PARAM_LIST_RECURSIVE(shift($@))')')
define(_EXTRACT_PARAM_LIST_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_EXTRACT_PARAM_LIST_RECURSIVE($@)')')
define(_EXTRACT_PARAM_LIST,`_EXTRACT_PARAM_LIST_(_EXTRACTPARAMS(`$1'))')
define(_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE,`ifelse($#,1,`,_EXTRACTPARAMNAME(`$1')',`,_EXTRACTPARAMNAME(`$1')`'_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE(shift($@))')')
define(_EXTRACT_PARAM_LIST_HANDLE_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_EXTRACT_PARAM_LIST_HANDLE_RECURSIVE($@)')')
define(_EXTRACT_PARAM_LIST_HANDLE,`_EXTRACT_PARAM_LIST_HANDLE_(shift(_EXTRACTPARAMS(`$1')))')
%
% Creates a parameter list for a macro call "(p1, p2, p3)", with one parameter for each parameter
% in the c parameterlist provided as parameter. If the provided parameterlist contains only `void'
% then it is treated as if it was empty.
%
% _MAKE_DUMMYPARAMLIST(`(int nCount, char *pBuf)') => `p0,p1'
% _MAKE_DUMMYPARAMLIST(`(void)') => `'
%
define(_MAKE_DUMMYPARAMLIST1,`ifelse($#,1,`',$#,2,`p$1',`p$1,_MAKE_DUMMYPARAMLIST1(incr($1),shift(shift($@)))')')
define(_MAKE_DUMMYPARAMLIST_,`ifelse(_TRIM(`$1'),`',`',_TRIM(`$1'),`void',`',`_MAKE_DUMMYPARAMLIST1(0,$@)')')
define(_MAKE_DUMMYPARAMLIST_WITHVOID_,`ifelse(_TRIM(`$1'),`',`',`_MAKE_DUMMYPARAMLIST1(0,$@)')')
define(_MAKE_DUMMYPARAMLIST,`_MAKE_DUMMYPARAMLIST_(_EXTRACTPARAMS(`$1'))')
%
% Creates a parameter list for a macro call "(p1, p2, p3)", with one parameter for each parameter
% in the c parameterlist provided as parameter. If the provided parameterlist contains only `void'
% then it is treated as if it had exactly one parameter.
%
% _MAKE_DUMMYPARAMLIST(`(int nCount, char *pBuf)') => `p0,p1'
% _MAKE_DUMMYPARAMLIST(`(void)') => `p0'
%
define(_MAKE_DUMMYPARAMLIST_WITHVOID,`_MAKE_DUMMYPARAMLIST_WITHVOID_(_EXTRACTPARAMS(`$1'))')
%
% Same as _MAKE_DUMMYPARAMLIST, but drops the first parameter
%
% _MAKE_HANDLE_DUMMYPARAMLIST(`(RTS_HANDLE, hHandle, int nCount, char *pBuf)') => `p1,p2'
%
define(_MAKE_HANDLE_DUMMYPARAMLIST,`shift(_MAKE_DUMMYPARAMLIST(`$1'))')
%
% Same as _MAKE_DUMMYPARAMLIST, but drops the first n parameter
%
% _MAKE_HANDLE_DUMMYPARAMLIST2(`(RTS_HANDLE hHandle, int nCount, char *pBuf, int nValue)', 2) => `p2,p3'
%
define(nshift, `ifelse($2, 0, `$1', `nshift(`shift($1)', decr($2))')')
define(_MAKE_HANDLE_DUMMYPARAMLIST2,`nshift(`_MAKE_DUMMYPARAMLIST(`$1')', $2)')
define(_MAKE_PARAMLIST2,`ifelse(_TRIM($@),`',`',`, $1`'_MAKE_PARAMLIST2(shift($@))')')
define(_MAKE_PARAMLIST1,`ifelse(_TRIM($@),`',`',`$1`'_MAKE_PARAMLIST2(shift($@))')')
define(_MAKE_HANDLEITF_PARAMLIST,`ifelse(_TRIM(shift($@)),`',`void',_TRIM(shift($@)),`void',`void',`_MAKE_PARAMLIST1(shift($@))')')
define(_MAKE_ITF_PARAMLIST, `ifelse(_TRIM($@),`',`void',_TRIM($@),`void',`void',`_MAKE_PARAMLIST1($@)')')
define(_MAKE_CREATEITF_PARAMLIST,`ifelse(_TRIM($@),`',`void',_TRIM($@),`void',`void',`_MAKE_PARAMLIST1($@)')')
%
% Ieration over some string elements
%
define(`_FOREACH', `ifelse(1,$#,` ',`$1($2)
ifelse(2,$#, ,`_FOREACH(`$1',shift(shift($@)))')')')
%
% Split component version number
%
% Example:
% $1 = "0x03020104"
% _VERSION_MAJOR => 3
% _VERSION_SUB0 => 2
% _VERSION_SUB1 => 1
% _VERSION_SUB2 => 4
%
define(`_TRIMLEFT_ZERO', `regexp(`$1', `^0*\(.+\)', `\1')')
define(`_VERSION_MAJOR',`_TRIMLEFT_ZERO(regexp(`$1',`^0x\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))')
define(`_VERSION_SUB0',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))')
define(`_VERSION_SUB1',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))')
define(`_VERSION_SUB2',`_TRIMLEFT_ZERO(regexp(`$1',`^0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\([0-9a-fA-F][0-9a-fA-F]\)', `eval(0x\1)'))')
divert`'dnl
+183
View File
@@ -0,0 +1,183 @@
##############################################################################
# Copyright: Copyright CODESYS Development GmbH
# Program: Extension API for the Linux SL products
##############################################################################
ifeq ($(dir $(lastword $(MAKEFILE_LIST))), ./)
#
# MAKEFILE is used wrong! Never call it from this folder directly
#
.PHONY:help
help:
@echo "**********************"
@echo "Dont work inside this folder! Please go to your library folder and call this makefile e.g.:"
@echo "make -f ../sdk/makefile newproject"
@echo
@echo "**********************"
.PHONY:all
all: help
.PHONY:newproject
newproject: help
.PHONY:makefile
makefile: help
else
#
# NORMAL RULES
#
# get some info from generated files
ITFM4 = $(wildcard *Itf.m4)
CSRC = $(wildcard *.c)
CMPBASENAME=$(CSRC:.c=)
# set some variables
OUTDIR=./out
ITF = $(basename ${ITFM4})
TEMPLATEDEP = $(SDKDIR)/m4/TemplateDep.m4
DEP = ${CMPBASENAME}Dep
# set name of shared object
SONAME= lib${CMPBASENAME}.so
# set sources of component
SRC += ${CSRC} CmpFrame.c
# objects: all sources
OBJS = $(addprefix ${OUTDIR}/,$(SRC:.c=.o))
# set default includes
INCLUDES += -I${SDKDIR}/include/ -I${OUTDIR}/ -I.
CFLAGS += -fPIC -g -O0 -Wall -Wextra -Wno-unused
CFLAGS += -DDEP_H=\"${DEP}.h\"
# set a vendorID
CMPVENDORID=0xFFFF
vpath %.m4 ${SDKDIR}/cmpstub/
vpath %.c ${SDKDIR}/src/
.PHONY:help
help:
@echo "**********************"
@echo "help / usage / available make targets:"
@echo "-all : just do everything needed"
@echo "-clean : cleanup all generated files"
@echo "-newproject : create a new project"
@echo "-showenv : show intern variables (for debugging)"
@echo "**********************"
.PHONY:newproject
newproject: makefile
.PHONY:all
all: ${OUTDIR}/$(SONAME)
SDK_BASE=$(dir $(lastword $(MAKEFILE_LIST)))
.PHONY:showenv
showenv:
@echo "**********************"
@echo "ITFM4: ${ITFM4}"
@echo "CSRC: ${CSRC}"
@echo "SONAME: ${SONAME}"
@echo "ITF: ${ITF}"
@echo "DEP: ${DEP}"
@echo "OBJS: ${OBJS}"
@echo "CC: $(CC)"
@echo "CFLAGS: ${CFLAGS}"
@echo "SDKDIR: ${SDKDIR}"
@echo "CMPBASENAME: ${CMPBASENAME}"
@echo "MAKEFILE_LIST: $(MAKEFILE_LIST)"
@echo "**********************"
@echo ""
# check if there are whitespaces in the SDK BASE DIR
# this causes horrible things in make, so we detect/avoid it directly at creating a new project
ifeq (1,$(words $(MAKEFILE_LIST)))
makefile:
@echo "**********************"
@echo "creating makefile for your project "
cp ${SDK_BASE}/makefile_template $@
@echo "# include of the SDK makefiles" >> $@
@echo "SDKDIR="$(abspath ${SDK_BASE}) >> $@
@echo 'include $${SDKDIR}/makefile' >> $@
@echo "" >> $@
@echo "now check your makefile here"
@echo "**********************"
@echo ""
else
makefile:
@echo "**********************"
@echo "creating makefile for your project FAILED:"
@echo "there are whitespaces in the SDK path"
@echo "this does not work with 'make'!"
@echo "Please put your SDK to a path without whitespaces"
@echo "**********************"
@echo ""
endif
${OUTDIR}/%.o: %.c
@echo "**********************"
@echo "compile: $@ from $<"
@mkdir -p ${OUTDIR}
$(CC) -c $(CFLAGS) $(INCLUDES) -o $@ $<
@echo "**********************"
@echo ""
${OUTDIR}/${SONAME}: ${OUTDIR}/$(DEP).h ${OUTDIR}/$(ITF).h $(OBJS)
@echo "**********************"
@echo "link: $@"
@mkdir -p ${OUTDIR}
$(CC) $(LDFLAGS) -shared $(OBJS) -Wl,-soname,$(SONAME) -o $@ $(LDLIBS)
@echo "**********************"
@echo ""
${OUTDIR}/$(DEP).m4:
@echo "**********************"
@echo "create $@ from template"
@mkdir -p ${OUTDIR}
echo 'SET_COMPONENT_NAME(`'${CMPBASENAME}"')" > $@
echo 'COMPONENT_VERSION(`'${CMPVERSION}"')" >> $@
echo 'COMPONENT_VENDORID(`'${CMPVENDORID}"')" >> $@
echo "#define CMPID_${CMPBASENAME} ${CMPID}" >> $@
echo "IMPLEMENT_ITF("'`'"${CMPBASENAME}Itf.m4')" >> $@
@echo "**********************"
@echo ""
${OUTDIR}/$(DEP).h: ${OUTDIR}/$(DEP).m4 $(ITF).m4
@echo "**********************"
@echo "build dependency header: $@ from $<"
@mkdir -p ${OUTDIR}
$(DOS2UNIX) $<
$(M4) -I$(SDKDIR)/m4 -I. Implementation.m4 - < $< > $@
@echo "**********************"
@echo ""
${OUTDIR}/$(ITF).h: $(ITF).m4
@echo "**********************"
@echo "build interface header: $@ from $<"
@sed -n '/DEF_API/ !d; /_cext/ ! { s/^/ERROR: This functionname does not contain _cext (this is mandatory, see help for details):\n/p;q 1}' $<
@mkdir -p ${OUTDIR}
$(DOS2UNIX) $<
$(M4) -I$(SDKDIR)/m4 Exports.m4 - < $< > $@
@echo "**********************"
@echo ""
.PHONY:clean
clean:
@echo ""
@echo "**********************"
@echo "clean up"
rm -rf ${OUTDIR};
@echo "**********************"
@echo ""
endif
+48
View File
@@ -0,0 +1,48 @@
##############################################################################
# Copyright: Copyright CODESYS Development GmbH
# Program: Extension API for the Linux SL products
##############################################################################
#
# with this makefile you can influence the build of your component
#
#
# Hints:
# - the name of the *Itf.m4 file in this folder will be used as name of the shared object (and component name)
# - all C files in this folder will be compiled by default
#
# set versions of your shared object
# must be single digit, decimal
MAJORVER=0
MINORVER=1
# set a component version (must match your IEC library version)
# must be 4 bytes, hexadecimal
CMPVERSION=0x01000000
# set a component ID
# must be 2 bytes >= 0x2000 and hexadecimal and can be used to differentiate different components
CMPID=0x2000
# set your tools
DOS2UNIX = dos2unix
M4 = m4
# set a compiler
CC ?= gcc
# add some compiler flags (with += )
#CFLAGS += -g
# add some include paths (with +=)
#INCLUDES += -I.
# add some linker flags (with += )
#LDFLAGS +=
# add some libraries that you might need (with += )
#LDLIBS += -lc
+78
View File
@@ -0,0 +1,78 @@
/*****************************************************************************
* Copyright: Copyright CODESYS Development GmbH
* Program: Extension API for the Linux SL products
******************************************************************************/
#include "CmpStd.h"
#include DEP_H
USE_STMT
DLL_DECL int CDECL ComponentEntry(INIT_STRUCT *pInitStruct)
/* Used to exchange function pointers between component manager and components.
Called at startup for each component.
pInitStruct: IN Pointer to structure with:
pfExportFunctions OUT Pointer to function that exports component functions
pfImportFunctions OUT Pointer to function that imports functions from other components
pfGetVersion OUT Pointer to function to get component version
pfRegisterAPI IN Pointer to component manager function to register a API function
pfGetAPI IN Pointer to component manager function to get a API function
pfCallHook IN Pointer to component manager function to call a hook function
Return ERR_OK if library could be initialized, else error code
*/
{
pInitStruct->CmpId = COMPONENT_ID;
pInitStruct->pfExportFunctions = ExportFunctions;
pInitStruct->pfImportFunctions = ImportFunctions;
pInitStruct->pfGetVersion = CmpGetVersion;
pInitStruct->pfHookFunction = HookFunction;
pInitStruct->pfCreateInstance = NULL;
pInitStruct->pfDeleteInstance = NULL;
s_pfCMRegisterAPI = pInitStruct->pfCMRegisterAPI;
s_pfCMRegisterAPI2 = pInitStruct->pfCMRegisterAPI2;
s_pfCMGetAPI = pInitStruct->pfCMGetAPI;
s_pfCMGetAPI2 = pInitStruct->pfCMGetAPI2;
s_pfCMCallHook = pInitStruct->pfCMCallHook;
s_pfCMRegisterClass = pInitStruct->pfCMRegisterClass;
s_pfCMCreateInstance = pInitStruct->pfCMCreateInstance;
return ERR_OK;
}
/**
* <description>Publish exported functions to the component manager, to make them available for other components.
* Use generated macro EXPORT_STMT.</description>
* <result>ERR_OK or error code in case of error</result>
*/
static int CDECL ExportFunctions(void)
{
/* Macro to export functions */
EXPORT_STMT;
return ERR_OK;
}
/**
* <description>Import function pointers from other components. Use generated macro IMPORT_STMT.</description>
* <result>ERR_OK or error code if function name could not be resolved</result>
*/
static int CDECL ImportFunctions(void)
{
/* Macro to import functions */
IMPORT_STMT;
return ERR_OK;
}
/**
* <description>Return the version of our component.</description>
* <result>Version as defined in dep file</result>
*/
static RTS_UI32 CDECL CmpGetVersion(void)
{
return CMP_VERSION;
}
static RTS_RESULT CDECL HookFunction(RTS_UI32 ulHook, RTS_UINTPTR ulParam1, RTS_UINTPTR ulParam2)
{
return ERR_OK;
}
+86
View File
@@ -0,0 +1,86 @@
#include "atom_bridge.h"
#include "atom_mb.h"
#include <stdio.h>
#include <string.h>
typedef struct { am_client c; 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) {
unsigned n=h&255u;
if(!n || n>AM_POOL_SIZE) return NULL;
slot *s=&slots[n-1];
return s->used && s->generation==(h>>8)?s:NULL;
}
static void snapshot(const am_client *c,uint32_t h,uint32_t *s) {
s[0]=h; s[1]=(uint32_t)c->state; s[2]=(uint32_t)c->busy;
s[3]=(uint32_t)c->done; s[4]=(uint32_t)c->error; s[5]=(uint32_t)c->result;
s[6]=(uint32_t)c->last_error; s[7]=(uint32_t)c->socket_error;
s[8]=c->exception; s[9]=(uint32_t)c->write_uncertain;
s[10]=c->connect_attempts; s[11]=c->reconnects; s[12]=c->completed; s[13]=c->failed;
s[14]=(uint32_t)c->has_success; s[15]=(uint32_t)c->last_success_ms;
s[16]=(uint32_t)(c->last_success_ms>>32); s[17]=c->quantity;
s[18]=c->function; s[19]=c->unit; s[20]=(uint32_t)c->enabled;
s[21]=c->connect_timeout_ms; s[22]=c->response_timeout_ms; s[23]=c->retry_delay_ms;
}
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) {
slot *s; unsigned i; uint32_t result=0;
if(!status || sc<AM_STATUS_WORDS) return AB_BAD_CAPACITY;
/* Caller must provide distinct data/status buffers with declared capacities. */
if(cmd==AB_OPEN) {
char text[16];
memset(status,0,AM_STATUS_WORDS*sizeof(*status));
if(!port) return AM_ARGUMENT;
for(i=0;i<AM_POOL_SIZE;i++) if(!slots[i].used && !slots[i].retired) break;
if(i==AM_POOL_SIZE) return AB_POOL_FULL;
s=&slots[i];
snprintf(text,sizeof(text),"%u.%u.%u.%u",(unsigned)(ip>>24),(unsigned)((ip>>16)&255u),
(unsigned)((ip>>8)&255u),(unsigned)(ip&255u));
if(!am_init(&s->c,text,port)) return AM_ARGUMENT;
s->generation++; s->used=1; h=token(i);
snapshot(&s->c,h,status); return 0;
}
s=lookup(h);
if(!s) { memset(status,0,AM_STATUS_WORDS*sizeof(*status)); return AB_BAD_HANDLE; }
switch(cmd) {
case AB_CLOSE:
am_close(&s->c); s->used=0;
/* 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: am_enable(&s->c,1,now); break;
case AB_DISABLE: am_enable(&s->c,0,now); break;
case AB_TICK: am_tick(&s->c,now); break;
case AB_RECONNECT: am_reconnect(&s->c,now); break;
case AB_CONFIGURE:
if(s->c.enabled) result=AM_BUSY;
else if(!ct || !rt || !retry) result=AM_ARGUMENT;
else { s->c.connect_timeout_ms=ct; s->c.response_timeout_ms=rt; s->c.retry_delay_ms=retry; }
break;
case AB_SUBMIT:
if((f==5 || f==6 || f==15 || f==16) && (!data || cap<q)) result=AB_BAD_CAPACITY;
else result=(uint32_t)am_request(&s->c,unit,f,addr,q,data,now);
break;
case AB_COPY_READ:
if(!s->c.done || s->c.function>4 || s->c.function<1) result=AM_ARGUMENT;
else if(!data || cap<s->c.quantity) result=AB_BAD_CAPACITY;
else memcpy(data,s->c.data,s->c.quantity*sizeof(*data));
break;
case AB_ACK: s->c.done=0; s->c.error=0; break;
case AB_REQUEST_TIMEOUT:
if(s->c.busy) result=AM_BUSY;
else if(!rt) result=AM_ARGUMENT;
else s->c.response_timeout_ms=rt;
break;
default: result=AB_BAD_COMMAND; break;
}
snapshot(&s->c,h,status); return result;
}
void ab_shutdown(void) {
unsigned i;
for(i=0;i<AM_POOL_SIZE;i++) if(slots[i].used) {
am_close(&slots[i].c); slots[i].used=0;
if(slots[i].generation==0xffffffu) slots[i].retired=1;
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef ATOM_BRIDGE_H
#define ATOM_BRIDGE_H
#include <stdint.h>
/* Fixed pool, one IEC task owns ALL calls. Do not share across tasks.
* 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_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,
uint64_t now,uint32_t connect_ms,uint32_t response_ms,uint32_t retry_ms);
/* Call on component unload; invalidate handles and close all sockets. */
void ab_shutdown(void);
#endif
+146
View File
@@ -0,0 +1,146 @@
#include "atom_mb.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static uint16_t get16(const uint8_t *p) { return (uint16_t)((p[0]<<8)|p[1]); }
static void put16(uint8_t *p,uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; }
static int writing(uint8_t f) { return f==5 || f==6 || f==15 || f==16; }
static void drop(am_client *c) { if(c->fd>=0) close(c->fd); c->fd=-1; }
static void finish(am_client *c,am_error e) {
c->busy=0; c->done=e==AM_OK; c->error=e!=AM_OK; c->result=e;
if(e==AM_OK) c->completed++; else { c->failed++; c->last_error=e; }
}
static void fault(am_client *c,am_error e,uint64_t now) {
if(c->busy) { c->write_uncertain=writing(c->function) && c->tx_pos>0; finish(c,e); }
c->last_error=e; drop(c); c->state=c->enabled?AM_RETRY:AM_DISABLED;
c->deadline_ms=now+c->retry_delay_ms;
}
int am_init(am_client *c,const char *ip,uint16_t port) {
struct in_addr a;
if(!c || !ip || strlen(ip)>=16 || !port || inet_pton(AF_INET,ip,&a)!=1) return 0;
memset(c,0,sizeof(*c)); c->fd=-1; strcpy(c->ip,ip); c->port=port;
c->connect_timeout_ms=2000; c->response_timeout_ms=1000; c->retry_delay_ms=1000;
return 1;
}
void am_close(am_client *c) { c->enabled=0; fault(c,AM_CANCELLED,0); }
void am_enable(am_client *c,int enabled,uint64_t now) {
if(!enabled) { if(c->enabled) am_close(c); return; }
if(!c->enabled) { c->enabled=1; c->state=AM_RETRY; c->deadline_ms=now; }
}
void am_reconnect(am_client *c,uint64_t now) { fault(c,AM_CANCELLED,now); c->deadline_ms=now; }
am_error am_request(am_client *c,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,
const uint16_t *v,uint64_t now) {
size_t n=12, bytes=0; unsigned i;
if(c->busy) return AM_BUSY;
if(c->state!=AM_READY) return AM_OFFLINE;
if(!q || (uint32_t)addr+q>65536u) return AM_ARGUMENT;
switch(f) {
case 1: case 2: if(q>2000) return AM_ARGUMENT; break;
case 3: case 4: if(q>125) return AM_ARGUMENT; break;
case 5: case 6: if(q!=1 || !v || (f==5 && v[0]>1)) return AM_ARGUMENT; break;
case 15: if(q>1968 || !v) return AM_ARGUMENT;
for(i=0;i<q;i++) if(v[i]>1) return AM_ARGUMENT;
bytes=(q+7u)/8u; n=13+bytes; break;
case 16: if(q>123 || !v) return AM_ARGUMENT; bytes=2u*q; n=13+bytes; break;
default: return AM_ARGUMENT;
}
memset(c->tx,0,sizeof(c->tx)); c->transaction++;
put16(c->tx,c->transaction); put16(c->tx+4,(uint16_t)(n-6));
c->tx[6]=unit; c->tx[7]=f; put16(c->tx+8,addr); put16(c->tx+10,q);
if(f==5 || f==6) put16(c->tx+10,f==5?(v[0]?0xff00:0):v[0]);
if(f==15 || f==16) {
c->tx[12]=(uint8_t)bytes;
for(i=0;i<q;i++) {
if(f==16) put16(c->tx+13+2*i,v[i]);
else if(v[i]) c->tx[13+i/8]|=(uint8_t)(1u<<(i%8));
}
}
c->unit=unit; c->function=f; c->address=addr; c->quantity=q;
c->tx_len=n; c->tx_pos=0; c->rx_len=0; c->rx_need=7;
c->busy=1; c->done=0; c->error=0; c->exception=0; c->write_uncertain=0;
c->result=AM_OK; c->state=AM_SENDING; c->deadline_ms=now+c->response_timeout_ms;
return AM_OK;
}
static void response(am_client *c,uint64_t now) {
unsigned i; size_t bytes;
if(c->rx[7]==(uint8_t)(c->function|0x80)) {
if(c->rx_need!=9 || !c->rx[8]) { fault(c,AM_PROTOCOL,now); return; }
c->exception=c->rx[8]; finish(c,AM_EXCEPTION); c->state=AM_READY; return;
}
if(c->rx[7]!=c->function) { fault(c,AM_PROTOCOL,now); return; }
if(writing(c->function)) {
if(c->rx_need!=12 || memcmp(c->rx+8,c->tx+8,4)) { fault(c,AM_PROTOCOL,now); return; }
} else {
bytes=c->function<=2?(c->quantity+7u)/8u:2u*c->quantity;
if(c->rx_need!=9+bytes || c->rx[8]!=bytes) { fault(c,AM_PROTOCOL,now); return; }
for(i=0;i<c->quantity;i++) c->data[i]=c->function<=2?
(uint16_t)((c->rx[9+i/8]>>(i%8))&1):get16(c->rx+9+2*i);
}
c->last_success_ms=now; c->has_success=1; finish(c,AM_OK); c->state=AM_READY;
}
void am_tick(am_client *c,uint64_t now) {
struct sockaddr_in a; struct pollfd p; int e,r; socklen_t len; ssize_t n;
if(!c->enabled) return;
if(c->state==AM_RETRY) {
if(now<c->deadline_ms) return;
c->connect_attempts++; if(c->connect_attempts>1) c->reconnects++;
c->socket_error=0; c->fd=socket(AF_INET,SOCK_STREAM,0);
if(c->fd<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
r=fcntl(c->fd,F_GETFL,0);
if(r<0 || fcntl(c->fd,F_SETFL,r|O_NONBLOCK)<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
memset(&a,0,sizeof(a)); a.sin_family=AF_INET; a.sin_port=htons(c->port);
if(inet_pton(AF_INET,c->ip,&a.sin_addr)!=1) { fault(c,AM_ARGUMENT,now); return; }
r=connect(c->fd,(struct sockaddr*)&a,sizeof(a));
if(r==0) c->state=AM_READY;
else if(errno==EINPROGRESS) { c->state=AM_CONNECTING; c->deadline_ms=now+c->connect_timeout_ms; }
else { c->socket_error=errno; fault(c,AM_SOCKET,now); }
return;
}
if(c->state==AM_CONNECTING) {
if(now>=c->deadline_ms) { fault(c,AM_CONNECT_TIMEOUT,now); return; }
p.fd=c->fd; p.events=POLLOUT; p.revents=0; r=poll(&p,1,0);
if(r<0) { if(errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; }
if(!r) return;
len=sizeof(e); e=0;
if(getsockopt(c->fd,SOL_SOCKET,SO_ERROR,&e,&len)<0) e=errno;
if(e) { c->socket_error=e; fault(c,AM_SOCKET,now); } else c->state=AM_READY;
return;
}
if(c->state==AM_READY) {
/* Detect idle peer closure; unsolicited bytes cannot belong to a request. */
uint8_t b; n=recv(c->fd,&b,1,MSG_PEEK);
if(n==0) fault(c,AM_PEER_CLOSED,now);
else if(n>0) fault(c,AM_PROTOCOL,now);
else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); }
return;
}
if(now>=c->deadline_ms) { fault(c,AM_RESPONSE_TIMEOUT,now); return; }
if(c->state==AM_SENDING) {
n=send(c->fd,c->tx+c->tx_pos,c->tx_len-c->tx_pos,MSG_NOSIGNAL);
if(n>0) { c->tx_pos+=(size_t)n; if(c->tx_pos==c->tx_len) c->state=AM_RECEIVING; }
else if(n==0) fault(c,AM_PEER_CLOSED,now);
else if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); }
return;
}
if(c->state!=AM_RECEIVING) return;
n=recv(c->fd,c->rx+c->rx_len,c->rx_need-c->rx_len,0);
if(n==0) { fault(c,AM_PEER_CLOSED,now); return; }
if(n<0) { if(errno!=EAGAIN && errno!=EWOULDBLOCK && errno!=EINTR) { c->socket_error=errno; fault(c,AM_SOCKET,now); } return; }
c->rx_len+=(size_t)n;
if(c->rx_len!=c->rx_need) return;
if(c->rx_need==7) {
uint16_t l=get16(c->rx+4);
if(get16(c->rx)!=c->transaction || get16(c->rx+2)!=0 || c->rx[6]!=c->unit || l<3 || l>254) {
fault(c,AM_PROTOCOL,now); return;
}
c->rx_need=6u+l; return;
}
response(c,now);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef ATOM_MB_H
#define ATOM_MB_H
#include <stddef.h>
#include <stdint.h>
/* Single owner/task per context. Init once, close before disposal. No heap allocation.
* tick() time must be monotonic milliseconds. POSIX IPv4 transport, numeric IP only. */
typedef enum { AM_DISABLED, AM_RETRY, AM_CONNECTING, AM_READY, AM_SENDING, AM_RECEIVING } am_state;
typedef enum { AM_OK, AM_ARGUMENT, AM_BUSY, AM_OFFLINE, AM_SOCKET, AM_CONNECT_TIMEOUT,
AM_RESPONSE_TIMEOUT, AM_PEER_CLOSED, AM_PROTOCOL, AM_EXCEPTION, AM_CANCELLED } am_error;
typedef struct {
int fd, enabled;
am_state state;
am_error last_error, result;
int socket_error;
uint8_t exception, unit, function;
uint16_t transaction, address, quantity;
uint32_t connect_timeout_ms, response_timeout_ms, retry_delay_ms;
uint64_t deadline_ms, last_success_ms;
uint32_t connect_attempts, reconnects, completed, failed;
int busy, done, error, write_uncertain, has_success;
char ip[16]; uint16_t port;
uint8_t tx[260], rx[260];
size_t tx_len, tx_pos, rx_len, rx_need;
/* Reads: coils/discretes unpacked as 0/1; registers as host-order uint16_t.
* Result is copied only after the entire response validates. */
uint16_t data[2000];
} am_client;
int am_init(am_client *c, const char *ip, uint16_t port);
void am_enable(am_client *c, int enabled, uint64_t now_ms);
void am_reconnect(am_client *c, uint64_t now_ms);
void am_close(am_client *c);
void am_tick(am_client *c, uint64_t now_ms);
/* Supported functions: 1,2,3,4,5,6,15,16. Zero-based protocol addresses.
* Writes copy input immediately. No automatic replay of a failed request.
* Completion flags persist until next accepted request; result is per-request. */
am_error am_request(am_client *c, uint8_t unit, uint8_t function,
uint16_t address, uint16_t quantity, const uint16_t *values, uint64_t now_ms);
#endif
+29
View File
@@ -0,0 +1,29 @@
#define _POSIX_C_SOURCE 200809L
#include "atom_bridge.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static uint64_t now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return (uint64_t)t.tv_sec*1000+(uint64_t)t.tv_nsec/1000000; }
int main(int argc,char **argv) {
uint32_t s[24],h=0; uint16_t data[2]={42,43}; unsigned ok=0,err=0;
int uncertain=0,last=0; uint64_t start=now(); struct timespec pause={0,1000000};
if(argc!=4) return 2;
uint16_t port=(uint16_t)atoi(argv[1]); uint8_t f=(uint8_t)atoi(argv[2]);
#define CALL(cmd) ab_call(cmd,h,0x7f000001u,port,1,f,0,2,data,2,s,24,now(),100,80,20)
if(CALL(AB_OPEN)) return 2;
h=s[0]; if(CALL(AB_CONFIGURE) || CALL(AB_ENABLE)) return 2;
while(now()-start<1500 && ok<(unsigned)atoi(argv[3])) {
if(CALL(AB_TICK)) return 2;
if(s[3]) {
if(f<=4 && CALL(AB_COPY_READ)) return 2;
ok++; if(CALL(AB_ACK)) return 2;
}
if(s[4]) { err++; last=(int)s[5]; uncertain|=(int)s[9]; if(CALL(AB_ACK)) return 2; }
if(s[1]==3 && ok<(unsigned)atoi(argv[3])) {
if(CALL(AB_REQUEST_TIMEOUT) || CALL(AB_SUBMIT)) return 2;
}
nanosleep(&pause,0);
}
printf("%u %u %u %d %d %u %u\n",ok,err,s[11],uncertain,last,data[0],data[1]);
CALL(AB_CLOSE); return ok==(unsigned)atoi(argv[3])?0:1;
}
+24
View File
@@ -0,0 +1,24 @@
#define _POSIX_C_SOURCE 200809L
#include "atom_mb.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static uint64_t now(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC,&t); return (uint64_t)t.tv_sec*1000+(uint64_t)t.tv_nsec/1000000; }
int main(int argc,char **argv) {
am_client c; uint64_t start=now(); unsigned successes=0,errors=0; int uncertain=0,last=0;
struct timespec pause={0,1000000}; uint16_t values[2]={42,43};
if(argc!=4 || !am_init(&c,"127.0.0.1",(uint16_t)atoi(argv[1]))) return 2;
c.retry_delay_ms=20; c.response_timeout_ms=80; c.connect_timeout_ms=100;
am_enable(&c,1,start);
while(now()-start<1500 && successes<(unsigned)atoi(argv[3])) {
uint64_t t=now(); am_tick(&c,t);
if(c.done) { successes++; c.done=0; }
if(c.error) { errors++; last=c.result; uncertain|=c.write_uncertain; c.error=0; }
if(c.state==AM_READY && successes<(unsigned)atoi(argv[3])) {
int f=atoi(argv[2]); am_request(&c,1,(uint8_t)f,0,2,values,t);
}
nanosleep(&pause,0);
}
printf("%u %u %u %d %d %u %u\n",successes,errors,c.reconnects,uncertain,last,c.data[0],c.data[1]);
am_close(&c); return successes==(unsigned)atoi(argv[3])?0:1;
}
+19
View File
@@ -0,0 +1,19 @@
#include "atom_bridge.h"
#include <assert.h>
#include <stdio.h>
#define CALL(cmd,h) ab_call(cmd,h,0x7f000001u,502,1,3,0,2,NULL,0,status,24,100,2000,1000,1000)
int main(void) {
uint32_t status[24],handles[16],old,newh; unsigned i;
assert(ab_call(AB_OPEN,0,0,502,0,0,0,0,NULL,0,status,23,0,0,0,0)==AB_BAD_CAPACITY);
for(i=0;i<16;i++) { assert(CALL(AB_OPEN,0)==0); handles[i]=status[0]; assert(handles[i]); }
assert(CALL(AB_OPEN,0)==AB_POOL_FULL);
old=handles[0]; assert(CALL(AB_CLOSE,old)==0); assert(CALL(AB_TICK,old)==AB_BAD_HANDLE);
assert(CALL(AB_OPEN,0)==0); newh=status[0]; assert(newh!=old);
assert(CALL(AB_CONFIGURE,newh)==0); assert(CALL(AB_ENABLE,newh)==0);
assert(CALL(AB_REQUEST_TIMEOUT,newh)==0); assert(status[22]==1000);
assert(CALL(AB_CONFIGURE,newh)==2); assert(CALL(AB_SUBMIT,newh)==3);
assert(CALL(AB_DISABLE,newh)==0); assert(CALL(AB_COPY_READ,newh)==1);
ab_shutdown(); assert(CALL(AB_TICK,newh)==AB_BAD_HANDLE);
puts("PASS bridge: capacity, pool limit, stale handles, configure, offline submission, shutdown");
return 0;
}
+59
View File
@@ -0,0 +1,59 @@
import socket, struct, subprocess, threading, time, sys
from pathlib import Path
ROOT=Path(__file__).resolve().parents[1]
DRIVER=ROOT/'tests'/(sys.argv[1] if len(sys.argv)>1 else 'driver')
def exact(s,n):
b=b''
while len(b)<n:
part=s.recv(n-len(b))
if not part: raise EOFError
b+=part
return b
def run(mode, function=3):
listener=socket.socket(); listener.bind(('127.0.0.1',0)); listener.listen(); listener.settimeout(2)
issues=[]
def server():
first=True
try:
while True:
try: s,_=listener.accept()
except (socket.timeout,OSError): break
with s:
s.settimeout(2)
while True:
try:
h=exact(s,7); p=exact(s,struct.unpack('>H',h[4:6])[0]-1)
except (EOFError,ConnectionError,socket.timeout): break
if first and mode in ('close','timeout','bad_tid','bad_length','write_drop'):
first=False
if mode=='timeout': time.sleep(.12)
elif mode=='bad_tid': s.sendall(b'\xff\xff'+h[2:4]+b'\x00\x07'+h[6:7]+b'\x03\x04\x00\x2a\x00\x2b')
elif mode=='bad_length': s.sendall(h[:4]+b'\xff\xff'+h[6:7])
break
if first and mode=='exception': reply=bytes([p[0]|128,2]); first=False
elif function==16: reply=p[:5]
elif function==1: reply=b'\x01\x01\x03'
else: reply=b'\x03\x04\x00\x2a\x00\x2b'
packet=h[:4]+struct.pack('>H',len(reply)+1)+h[6:7]+reply
if mode=='fragment':
for b in packet: s.sendall(bytes([b])); time.sleep(.001)
else: s.sendall(packet)
except Exception as e: issues.append(e)
worker=threading.Thread(target=server); worker.start()
try:
p=subprocess.run([str(DRIVER),str(listener.getsockname()[1]),str(function),'2'],capture_output=True,text=True,timeout=4)
assert p.returncode==0,(mode,p.stdout,p.stderr)
ok,err,reconnect,uncertain,last,a,b=map(int,p.stdout.split())
assert ok==2
if function==3: assert (a,b)==(42,43)
if function==1: assert (a,b)==(1,1)
if mode in ('close','timeout','bad_tid','bad_length','write_drop'): assert err>=1 and reconnect>=1
if mode=='exception': assert err==1 and reconnect==0 and last==9
if mode=='write_drop': assert uncertain==1
print('PASS',mode,p.stdout.strip())
finally:
listener.close(); worker.join(3)
assert not issues,issues
for mode in ('normal','fragment','close','timeout','bad_tid','bad_length','exception'): run(mode)
run('write_drop',16)
run('coils',1)
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
target="${1:?Usage: build-linux.sh linux-aarch64|linux-armhf|linux-x86_64}"
case "$target" in linux-aarch64|linux-armhf|linux-x86_64) ;; *) echo "Unsupported target label" >&2; exit 2;; esac
sdk="${SDKDIR:-$root/sdk/ExtensionSDK}"
compiler="${CC:-gcc}"
for tool in "$compiler" make m4 dos2unix python3 file; do command -v "$tool" >/dev/null; done
sdk="$(realpath "$sdk")"
make -C "$root/component" SDKDIR="$sdk" CC="$compiler" clean
make -C "$root/component" SDKDIR="$sdk" CC="$compiler" all
binary="$root/component/out/libCmpAtomModbus.so"
identity="$(file -b "$binary")"
case "$target" in
linux-aarch64) [[ "$identity" == *aarch64* ]] ;;
linux-armhf) [[ "$identity" == *"ELF 32-bit"* && "$identity" == *ARM* ]] ;;
linux-x86_64) [[ "$identity" == *x86-64* ]] ;;
esac || { echo "Binary architecture does not match $target: $identity" >&2; exit 3; }
destination="$root/dist/0.2.0/$target"
mkdir -p "$destination"
cp "$binary" "$destination/"
{
date -u '+Build UTC: %Y-%m-%dT%H:%M:%SZ'
uname -a
"$compiler" --version
file "$binary"
if command -v getconf >/dev/null; then getconf GNU_LIBC_VERSION || true; fi
if command -v git >/dev/null; then git -C "$root" rev-parse HEAD 2>/dev/null || true; fi
printf 'SDK file hashes:\n'
find "$sdk" -type f -exec sha256sum {} +
printf 'Record tested runtime, target OS/libc and cross sysroot here before release.\n'
} > "$destination/build-info.txt"
sha256sum "$destination/libCmpAtomModbus.so" > "$destination/SHA256SUMS.txt"
printf 'Built: %s\n' "$destination/libCmpAtomModbus.so"
+542
View File
@@ -0,0 +1,542 @@
"""Patch original native CODESYS export; keep existing public declarations/IDs."""
from pathlib import Path
import xml.etree.ElementTree as E
import copy, uuid
ROOT=Path(__file__).resolve().parents[1]
r=E.parse(ROOT/'compat/Original_HomeCodesys.export').getroot()
listing=r.find('.//List2[@Name="EntryList"]')
entries=list(listing)
def meta(e,n): return e.find('./Single[@Name="MetaObject"]/Single[@Name="'+n+'"]')
names={meta(e,'Guid').text:meta(e,'Name').text for e in entries}
def key(e):
p=e.find('./Single[@Name="ParentSVNodeGuid"]')
return (names.get(p.text,''),meta(e,'Name').text)
by={key(e):e for e in entries}
def node(parent,name): return by[(parent,name)]
def textnode(e,part): return e.find('./Single[@Name="Object"]/Single[@Name="'+part+'"]//Single[@Name="TextBlobForSerialisation"]')
def get(parent,name,part='Implementation'): return textnode(node(parent,name),part).text or ''
def put(parent,name,body,part='Implementation'): textnode(node(parent,name),part).text=body
def append_vars(parent,name,body,kind='VAR'):
put(parent,name,get(parent,name,'Interface')+'\n'+kind+'\n'+body+'\nEND_VAR\n','Interface')
def method(parent,name,decl,body):
e=copy.deepcopy(node('atMbClient','resetStats'))
old=meta(e,'Guid').text; new=str(uuid.uuid5(uuid.NAMESPACE_URL,'atom-modbus/'+parent+'/'+name))
pg=meta(node('ModbusMaster',parent),'Guid').text
for v in e.iter():
if v.text and old in v.text:v.text=v.text.replace(old,new)
meta(e,'Name').text=name; meta(e,'ParentGuid').text=pg
e.find('./Single[@Name="ParentSVNodeGuid"]').text=pg
path=e.find('./Array[@Name="Path"]'); path[-1].text=parent
textnode(e,'Interface').text=decl; textnode(e,'Implementation').text=body
listing.append(e); by[(parent,name)]=e
append_vars('ModbusMaster','atMbClient','''nativeMode : BOOL;
nativeHandle : UDINT;
nativeIP : DWORD;
nativePort : UINT;
nativeNowUs : SYSTIME;
nativeNowMs : ULINT;
nativeStatus : ARRAY[0..23] OF UDINT;
nativeIO : ARRAY[0..1999] OF WORD;
nativeActiveRequest : POINTER TO atMbReqBase;
nativeActiveIndex : DINT := -1;
nativeSelected : POINTER TO atMbReqBase;
nativeResultReady : BOOL;
nativeFinishedIndex : DINT := -1;
nativeArgUnit : BYTE;
nativeArgFunction : BYTE;
nativeArgAddress : UINT;
nativeArgQuantity : UINT;
nativeArgTimeout : UDINT := 1000;
nativeCallResult : UDINT;
nativeSent : UDINT;
nativeReplied : UDINT;
nativeWindowStart : ULINT;
nativeWindowCount : UDINT;
nativeWindowTime : UDINT;
nativePrevConnected : BOOL;
nativeEnable : BOOL := TRUE;
nativeConnectTimeout : UDINT := 2000;
nativeRetryDelay : UDINT := 1000;
nativeHoldUncertainWrites : BOOL := FALSE;''')
# CODESYS permits another FB to assign inputs, but not local VAR members.
# Match the original stats pattern for the shared scheduler working fields.
e=node('ModbusMaster','atMbClient')
decl=textnode(e,'Interface').text
shared=['nativeActiveRequest','nativeIO','nativeArgUnit','nativeArgFunction',
'nativeArgAddress','nativeArgQuantity','nativeArgTimeout','nativeSent',
'nativeReplied','nativeWindowCount','nativeWindowTime']
lines=decl.splitlines(); moved=[]; kept=[]
for line in lines:
if any(line.strip().startswith(n+' :') for n in shared): moved.append(line)
else: kept.append(line)
textnode(e,'Interface').text='\n'.join(kept)+'\nVAR_INPUT\n'+''.join(
"{attribute 'symbol' := 'none'}\n"+line+'\n' for line in moved)+'END_VAR\n'
put('atMbClient','setAllRequestsOffline',
get('atMbClient','setAllRequestsOffline','Interface').replace('METHOD PRIVATE','METHOD PROTECTED'),
'Interface')
append_vars('ModbusMaster','atMbClient','''out_connected : BOOL;
out_connectionState : UDINT;
out_lastNativeError : UDINT;
out_socketError : UDINT;
out_reconnectCount : UDINT;
out_registrationFull : BOOL;''','VAR_OUTPUT')
method('atMbClient','nativeCall','''METHOD nativeCall : UDINT
VAR_INPUT
command : UDINT;
END_VAR''','''nativeCall := CmpAtomModbus.atMbCall_cext(
command := command, handle := nativeHandle, ipAddress := nativeIP,
port := nativePort, unitId := nativeArgUnit, functionCode := nativeArgFunction,
startAddress := nativeArgAddress, quantity := nativeArgQuantity,
pData := ADR(nativeIO[0]), dataCapacity := 2000,
pStatus := ADR(nativeStatus[0]), statusCapacity := 24, nowMs := nativeNowMs,
connectTimeoutMs := nativeConnectTimeout, responseTimeoutMs := nativeArgTimeout,
retryDelayMs := nativeRetryDelay);''')
method('atMbClient','nativeCycle','''METHOD nativeCycle
VAR
j : DINT;
best : DINT;
priority : INT;
age : UDINT;
rc : UDINT;
END_VAR''','''SysTimeGetUs(nativeNowUs);
nativeNowMs := nativeNowUs / 1000;
IF in_resetStats THEN
resetStats();
in_resetStats := FALSE;
END_IF;
IF nativeHandle = 0 AND nativeEnable THEN
rc := nativeCall(1);
IF rc <> 0 THEN
out_lastNativeError := rc;
RETURN;
END_IF;
nativeHandle := nativeStatus[0];
nativeArgTimeout := 1000;
rc := nativeCall(9);
IF rc <> 0 THEN
out_lastNativeError := rc;
nativeCall(2); nativeHandle := 0;
RETURN;
END_IF;
nativeCall(3);
END_IF;
IF nativeHandle = 0 THEN
out_connected := FALSE;
out_connectionState := 0;
RETURN;
END_IF;
rc := nativeCall(5);
IF rc = 256 THEN
nativeHandle := 0;
IF nativeActiveRequest <> 0 THEN
nativeActiveRequest^.nativeFinish(10);
END_IF;
nativeActiveRequest := 0; nativeActiveIndex := -1;
RETURN;
ELSIF rc <> 0 THEN
out_lastNativeError := rc;
RETURN;
END_IF;
out_connectionState := nativeStatus[1];
out_connected := (nativeStatus[1] >= 3) AND (nativeStatus[1] <= 5);
out_lastNativeError := nativeStatus[6];
out_socketError := nativeStatus[7];
out_reconnectCount := nativeStatus[11];
IF nativePrevConnected AND NOT out_connected THEN
setAllRequestsOffline();
END_IF;
nativePrevConnected := out_connected;
nativeFinishedIndex := -1;
nativeResultReady := (nativeActiveRequest <> 0) AND
((nativeStatus[3] <> 0) OR (nativeStatus[4] <> 0));
IF nativeResultReady AND (nativeStatus[3] <> 0) AND (nativeStatus[18] <= 4) THEN
rc := nativeCall(7);
IF rc <> 0 THEN
nativeStatus[3] := 0;
nativeStatus[4] := 1;
nativeStatus[5] := rc;
END_IF;
END_IF;
processAllRequests();
IF nativeResultReady THEN
nativeFinishedIndex := nativeActiveIndex;
nativeCall(10);
nativeActiveRequest := 0; nativeActiveIndex := -1;
nativeSelected := 0; nativeResultReady := FALSE;
END_IF;
IF nativeEnable AND nativeStatus[1] = 3 AND nativeActiveRequest = 0 THEN
best := -1; priority := 32767; age := 0;
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[j] <> 0 AND_THEN requests[j]^.stats.active
AND NOT requests[j]^.nativeWriteHold AND j <> nativeFinishedIndex THEN
IF requests[j]^.stats.sinceLastExec >= TIME_TO_UDINT(requests[j]^.in_minDelay) THEN
IF (requests[j]^.in_priorityCurrent < priority) OR
((requests[j]^.in_priorityCurrent = priority) AND
(requests[j]^.stats.sinceLastExec > age)) THEN
best := j; priority := requests[j]^.in_priorityCurrent;
age := requests[j]^.stats.sinceLastExec;
END_IF;
END_IF;
END_IF;
END_FOR;
IF best >= 0 THEN
IF priority > 0 THEN
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[j]^.in_priorityCurrent > 0 THEN
requests[j]^.in_priorityCurrent := requests[j]^.in_priorityCurrent - 1;
END_IF;
END_FOR;
END_IF;
nativeSelected := requests[best]; nativeActiveIndex := best;
nativeSelected^.stats.execute := TRUE;
nativeSelected^.processRequest();
IF nativeActiveRequest = 0 THEN
nativeActiveIndex := -1; nativeSelected := 0;
END_IF;
END_IF;
END_IF;
allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt);
allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0);
numMsgSent := nativeSent; numMsgReply := nativeReplied;
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
reqStats[j] := ADR(requests[j]^.stats);
END_FOR;
IF nativeWindowStart = 0 THEN nativeWindowStart := nativeNowMs; END_IF;
IF nativeNowMs - nativeWindowStart >= 1000 THEN
msgLastSecondCnt := nativeWindowCount; msgLastSecondTime := nativeWindowTime;
nativeWindowCount := 0; nativeWindowTime := 0; nativeWindowStart := nativeNowMs;
END_IF;''')
put('ModbusMaster','atMbClient','''IF nativeMode THEN
nativeCycle();
RETURN;
END_IF;
'''+get('ModbusMaster','atMbClient'))
# RTU retains old code and public compatibility methods.
put('atMbClient','resetStats','''IF nativeMode THEN
nativeSent := 0; nativeReplied := 0;
nativeWindowCount := 0; nativeWindowTime := 0;
msgLastSecondCnt := 0; msgLastSecondTime := 0;
numMsgSent := 0; numMsgReply := 0;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[i]^.stats.resetStats();
END_FOR;
RETURN;
END_IF;
'''+get('atMbClient','resetStats'))
put('atMbClient','addRequest','''IF request = 0 THEN RETURN; END_IF;
IF requestCnt > 0 THEN
FOR i := 0 TO requestCnt-1 DO
IF request = requests[i] THEN RETURN; END_IF;
END_FOR;
END_IF;
IF requestCnt >= 100 THEN
out_registrationFull := TRUE;
RETURN;
END_IF;
requests[requestCnt] := request;
requestCnt := requestCnt + 1;''')
put('activeIndex','Get','''IF nativeMode THEN
activeIndex := nativeActiveIndex;
RETURN;
END_IF;
'''+get('activeIndex','Get'))
# TCP declarations: existing inputs stay unchanged, extra diagnostics inherited.
append_vars('ModbusMaster','atMbClientTCP','''in_enable : BOOL := TRUE;
in_connectTimeout : TIME := T#2S;
in_reconnectDelay : TIME := T#1S;
in_holdUncertainWrites : BOOL := FALSE;''','VAR_INPUT')
append_vars('ModbusMaster','atMbClientTCP','''nativeEndpointIP : DWORD;
nativeEndpointPort : UINT;
nativeConfigConnect : UDINT;
nativeConfigRetry : UDINT;''')
put('ModbusMaster','atMbClientTCP','''nativeMode := TRUE;
nativeIP := SHL(BYTE_TO_DWORD(in_ipAddress[0]),24) OR
SHL(BYTE_TO_DWORD(in_ipAddress[1]),16) OR
SHL(BYTE_TO_DWORD(in_ipAddress[2]),8) OR BYTE_TO_DWORD(in_ipAddress[3]);
nativePort := in_port;
nativeConnectTimeout := MAX(UDINT#1,TIME_TO_UDINT(in_connectTimeout));
nativeRetryDelay := MAX(UDINT#1,TIME_TO_UDINT(in_reconnectDelay));
nativeHoldUncertainWrites := in_holdUncertainWrites;
IF nativeHandle <> 0 AND ((nativeEndpointIP <> nativeIP) OR
(nativeEndpointPort <> nativePort) OR (nativeConfigConnect <> nativeConnectTimeout) OR
(nativeConfigRetry <> nativeRetryDelay) OR NOT in_enable) THEN
nativeRelease();
END_IF;
nativeEndpointIP := nativeIP; nativeEndpointPort := nativePort;
nativeConfigConnect := nativeConnectTimeout; nativeConfigRetry := nativeRetryDelay;
nativeEnable := in_enable;
IF in_baudrate <> 0 THEN dataRate := in_baudrate; ELSE dataRate := 105200; END_IF;
SUPER^();''')
put('atMbClientTCP','recreateClient','''nativeRelease();
// Next scan creates a fresh native context with current endpoint settings.''')
# Remove the old local pointer used by the recreated built-in TCP client.
put('atMbClientTCP','recreateClient','METHOD recreateClient\nVAR\nEND_VAR','Interface')
method('atMbClientTCP','nativeRelease','''METHOD nativeRelease : BOOL
VAR
j : DINT;
END_VAR''','''IF nativeHandle <> 0 THEN
nativeCall(2);
IF nativeActiveRequest <> 0 THEN
nativeActiveRequest^.nativeFinish(10);
END_IF;
nativeHandle := 0;
END_IF;
nativeActiveRequest := 0; nativeSelected := 0; nativeActiveIndex := -1;
nativeResultReady := FALSE;
out_connected := FALSE; out_connectionState := 0;
setAllRequestsOffline();
FOR j := 0 TO UINT_TO_DINT(requestCnt)-1 DO
requests[j]^.invalidateNativeData();
END_FOR;
nativeRelease := TRUE;''')
method('atMbClientTCP','release','''METHOD release : BOOL''','''in_enable := FALSE;
release := nativeRelease();''')
method('atMbClientTCP','FB_exit','''METHOD FB_exit : BOOL
VAR_INPUT
bInCopyCode : BOOL;
END_VAR''','''FB_exit := nativeRelease();''')
method('atMbClientTCP','FB_reinit','''METHOD FB_reinit : BOOL
VAR
j : DINT;
END_VAR''','''nativeHandle := 0;
nativeActiveRequest := 0; nativeSelected := 0; nativeActiveIndex := -1;
nativeResultReady := FALSE; nativePrevConnected := FALSE;
requestCnt := 0; out_registrationFull := FALSE;
FOR j := 0 TO 99 DO requests[j] := 0; reqStats[j] := 0; END_FOR;
FB_reinit := TRUE;''')
append_vars('ModbusMaster','atMbReqBase','''nativeFunction : BYTE;
nativeCount : UINT;
nativeOffset : UINT;
nativeInFlight : BOOL;
nativeReadComplete : BOOL;
nativeCopyAllowed : BOOL;
nativeWriteHold : BOOL;
nativeStartMs : ULINT;
nativeLoop : DINT;
nativeRC : UDINT;''')
append_vars('ModbusMaster','atMbReqBase','''out_nativeError : UDINT;
out_socketError : UDINT;
out_writeUncertain : BOOL;
out_validData : BOOL;
out_dataAgeMs : ULINT;
out_stale : BOOL := TRUE;
out_parameterError : BOOL;''','VAR_OUTPUT')
append_vars('ModbusMaster','atMbReqBase','''in_staleAfter : TIME := T#2S;''','VAR_INPUT')
method('atMbReqBase','invalidateNativeData','''METHOD PUBLIC invalidateNativeData''','''out_validData := FALSE;
out_stale := TRUE;''')
method('atMbReqBase','nativeFinish','''METHOD nativeFinish
VAR_INPUT
result : UDINT;
END_VAR
VAR
mapped : ModbusFB.Error;
ex : ModbusFB.ExceptionCodes;
duration : UDINT;
END_VAR''','''out_nativeError := result;
out_socketError := client.nativeStatus[7];
out_writeUncertain := nativeInFlight AND (client.nativeStatus[9] <> 0);
IF (result = 10) AND nativeInFlight AND (nativeFunction >= 15) THEN
out_writeUncertain := TRUE;
END_IF;
IF out_writeUncertain AND client.nativeHoldUncertainWrites THEN
nativeWriteHold := TRUE;
END_IF;
stats.reqBusy := FALSE;
IF result = 0 THEN
duration := ULINT_TO_UDINT(client.nativeNowMs - nativeStartMs);
stats.lastValidDuration := duration;
IF duration > stats.longestValidDuration THEN stats.longestValidDuration := duration; END_IF;
client.nativeReplied := client.nativeReplied + 1;
client.nativeWindowTime := client.nativeWindowTime + duration;
stats.nativeResponseSeen := TRUE;
IF nativeFunction <= 4 THEN
nativeReadComplete := TRUE;
stats.nativeLastSuccessMs := client.nativeNowMs;
out_validData := TRUE;
END_IF;
done(TRUE);
ELSE
CASE result OF
1,258: mapped := ModbusFB.Error.RequestParameterError;
4,7: mapped := ModbusFB.Error.TcpSocketError;
5: mapped := ModbusFB.Error.TcpConnectTimeout;
6: mapped := ModbusFB.Error.ReplyTimeout;
8: mapped := ModbusFB.Error.InvalidReply;
9: mapped := ModbusFB.Error.RequestException;
10: mapped := ModbusFB.Error.RequestCancelled;
ELSE mapped := ModbusFB.Error.InternalError;
END_CASE;
ex := ModbusFB.ExceptionCodes.RESPONSE_SUCCESS;
IF result = 9 THEN
CASE client.nativeStatus[8] OF
1: ex := ModbusFB.ExceptionCodes.ILLEGAL_FUNCTION;
2: ex := ModbusFB.ExceptionCodes.ILLEGAL_DATA_ADDRESS;
3: ex := ModbusFB.ExceptionCodes.ILLEGAL_DATA_VALUE;
4: ex := ModbusFB.ExceptionCodes.SLAVE_DEVICE_FAILURE;
5: ex := ModbusFB.ExceptionCodes.ACKNOWLEDGE;
6: ex := ModbusFB.ExceptionCodes.SLAVE_DEVICE_BUSY;
8: ex := ModbusFB.ExceptionCodes.MEMORY_PARITY_ERROR;
10: ex := ModbusFB.ExceptionCodes.GATEWAY_PATH_UNAVAILABLE;
11: ex := ModbusFB.ExceptionCodes.GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND;
END_CASE;
client.nativeReplied := client.nativeReplied + 1;
stats.nativeResponseSeen := TRUE;
ELSE
stats.nativeResponseSeen := FALSE;
END_IF;
err(in := TRUE,errorId := mapped,exceptionCode := ex);
END_IF;
nativeInFlight := FALSE;
stats();''')
method('atMbReqBase','nativeProcess','''METHOD nativeProcess
VAR_INPUT
functionCode : BYTE;
END_VAR''','''nativeReadComplete := FALSE;
stats.reqDone := FALSE; stats.reqError := FALSE;
stats();
IF NOT client.out_connected THEN stats.setClientOffline(); END_IF;
IF client.nativeActiveRequest = THIS AND client.nativeResultReady THEN
IF (client.nativeStatus[3] <> 0) AND (nativeFunction <= 4) AND NOT nativeCopyAllowed THEN
nativeFinish(1);
ELSE
nativeFinish(client.nativeStatus[5]);
END_IF;
RETURN;
END_IF;
IF stats.execute AND NOT nativeInFlight AND client.nativeSelected = THIS THEN
nativeFunction := functionCode;
nativeCount := stats.reqLength; nativeOffset := in_dataOffset;
nativeStartMs := client.nativeNowMs;
IF out_parameterError OR nativeCount = 0 THEN nativeFinish(1); RETURN; END_IF;
client.nativeArgUnit := in_unitId;
client.nativeArgFunction := functionCode;
client.nativeArgAddress := in_startAddr;
client.nativeArgQuantity := nativeCount;
client.nativeArgTimeout := MAX(UDINT#1,(replyTimeout + 999)/1000);
nativeRC := client.nativeCall(11);
IF nativeRC = 0 THEN nativeRC := client.nativeCall(6); END_IF;
IF nativeRC = 0 THEN
client.nativeActiveRequest := THIS;
nativeInFlight := TRUE;
stats.reqBusy := TRUE;
client.nativeSent := client.nativeSent + 1;
client.nativeWindowCount := client.nativeWindowCount + 1;
ELSE nativeFinish(nativeRC);
END_IF;
stats();
END_IF;
IF out_validData THEN
out_dataAgeMs := client.nativeNowMs - stats.nativeLastSuccessMs;
out_stale := out_dataAgeMs > TIME_TO_UDINT(in_staleAfter);
ELSE out_stale := TRUE;
END_IF;''')
# Request body remains registration/clipping; fix remaining array capacity in native mode.
for name,f,bit,write in [('atMbReqReadCoil',1,True,False),('atMbReqReadDiscrete',2,True,False),('atMbReqReadHolding',3,False,False),('atMbReqReadInput',4,False,False),('atMbReqWriteCoil',15,True,True),('atMbReqWriteHolding',16,False,True)]:
original=get('ModbusMaster',name)
put('ModbusMaster',name,'''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
out_parameterError := (UINT_TO_DINT(in_dataOffset) < LOWER_BOUND(data,1)) OR
(UINT_TO_DINT(in_dataOffset) > UPPER_BOUND(data,1));
IF out_parameterError THEN dataSize := 0;
ELSE dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - UINT_TO_DINT(in_dataOffset) + 1);
END_IF;
SUPER^();
RETURN;
END_IF;
'''+original)
native='''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
nativeCopyAllowed := (UINT_TO_DINT(nativeOffset) >= LOWER_BOUND(data,1)) AND
(UINT_TO_DINT(nativeOffset) + UINT_TO_DINT(nativeCount) - 1 <= UPPER_BOUND(data,1));
'''
if write:
native+=''' IF stats.execute AND NOT nativeInFlight AND client.nativeSelected = THIS AND NOT out_parameterError THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(stats.reqLength)-1 DO
'''
native+=(' client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);\n' if bit else ' client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]);\n')
native+=' END_FOR;\n END_IF;\n'
native+=f' nativeProcess({f});\n'
if not write:
native+=''' IF nativeReadComplete AND nativeCopyAllowed THEN
FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO
'''
native+=(' data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0;\n' if bit else ' data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]);\n')
native+=' END_FOR;\n END_IF;\n'
native+=' RETURN;\nEND_IF;\n'
put(name,'processRequest',native+get(name,'processRequest'))
put(name,'recreateRequest','''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF;
nativeInFlight := FALSE;
recreateRequest := 0;
RETURN;
END_IF;
'''+get(name,'recreateRequest'))
if write:put(name,'writeNow','''nativeWriteHold := FALSE;
out_writeUncertain := FALSE;
'''+get(name,'writeNow'))
method('atMbReqBase','FB_reinit','''METHOD FB_reinit : BOOL''','''nativeInFlight := FALSE;
stats.execute := FALSE;
stats.reqBusy := FALSE;
stats.reqDone := FALSE;
stats.reqError := FALSE;
FB_reinit := TRUE;''')
put('atMbClient','setupClient','''IF nativeMode THEN
setupClient := nativeHandle <> 0;
RETURN;
END_IF;
'''+get('atMbClient','setupClient'))
put('atMbReqBase','setupClient','''IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN
setupClient := FALSE; // Native path has no ModbusFB request object.
RETURN;
END_IF;
'''+get('atMbReqBase','setupClient'))
# Stats type names remain unchanged; add native health/freshness fields.
append_vars('ModbusMaster','atMbRequestStats','''nativeResponseSeen : BOOL;
nativeLastSuccessMs : ULINT;''','VAR_INPUT')
s=get('ModbusMaster','atMbRequestStats')
s=s.replace('IF doneCntRst >=3 THEN','IF nativeResponseSeen OR doneCntRst >=3 THEN')
s=s.replace('IF errorCntRst >= 3 THEN','IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN')
a=s.index('IF lastReqStart < TSSus - 60000000 THEN');b=s.index('sinceLastExec :=',a)
s=s[:a]+'''IF TSSus >= lastReqStart THEN
IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF;
ELSE lastReqStart := TSSus;
END_IF;
'''+s[b:]
put('ModbusMaster','atMbRequestStats',s)
put('atMbRequestStats','setClientOffline','nativeResponseSeen := FALSE;\n'+get('atMbRequestStats','setClientOffline'))
# Dispatch may run before any request FB has registered on the first scan.
for name, call in [('processAllRequests', 'processRequest()'),
('recreateAllRequests', 'recreateRequest()'),
('setAllRequestsOffline', 'stats.setClientOffline()')]:
decl = get('atMbClient', name, 'Interface')
if 'i:' not in decl and 'i :' not in decl:
decl += '\nVAR\n i : DINT;\nEND_VAR\n'
decl = decl.replace('i: UINT;', 'i: DINT;')
put('atMbClient', name, decl, 'Interface')
put('atMbClient', name, f'''IF requestCnt = 0 OR requestCnt > 100 THEN RETURN; END_IF;
FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO
IF requests[i] <> 0 THEN
requests[i]^.{call};
END_IF;
END_FOR;''')
# Keep subtraction signed in every other registry consumer too.
import re
for e in listing:
t = textnode(e, 'Implementation')
if t is not None and t.text and 'requestCnt' in t.text:
t.text = re.sub(r'(?<!\()\brequestCnt\s*-\s*1',
'UINT_TO_DINT(requestCnt)-1', t.text)
# Export native CODESYS XML, retaining all original objects and metadata IDs.
E.indent(r,space=' ')
E.ElementTree(r).write(ROOT/'compat/AtomModbus-Compatible.export',encoding='utf-8',xml_declaration=False)
# Readable ST per changed/new object for reviewing or manual import fallback.
changed=[]
for e in listing:
p,n=key(e)
for part in ['Interface','Implementation']:
t=textnode(e,part)
if t is not None:
changed.append(f'\n(* {p}/{n} -- {part} *)\n{t.text or ""}\n')
(ROOT/'compat/All_Source_Review.st').write_text(''.join(changed))
print('Wrote compatible native export:',len(listing),'objects')
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Insert implementation into an official CODESYS-generated external stub.
Fails closed on unexpected syntax; never invents ABI, signature or checksum.
"""
import argparse
from pathlib import Path
import re
def prepare(source, interface, sdk, destination):
if source.stem != 'CmpAtomModbus' or interface.name != 'CmpAtomModbusItf.m4':
raise ValueError('Generate component CmpAtomModbus, with its matching Itf.m4')
c=source.read_text(); m4=interface.read_text()
match=re.search(r'\batmbcall_cext\s*\(\s*(\w+)\s*\*\s*(\w+)\s*\)\s*\{',c,re.I)
if not match: raise ValueError('Cannot find one-struct-pointer atMbCall_cext stub')
typ,param=match.groups()
structs=re.findall(r'typedef\s+struct(?:\s+\w+)?\s*\{([^{}]*)\}\s*(\w+)\s*;',m4,re.S)
fields=next((body for body,name in structs if name==typ),None)
if fields is None: raise ValueError('Cannot find the generated parameter struct in M4')
names=re.findall(r'\b(\w+)\s*(?:\[[^\]]*\])?\s*;',fields)
lookup={n.lower():n for n in names}
args=['command','handle','ipaddress','port','unitid','functioncode','startaddress',
'quantity','pdata','datacapacity','pstatus','statuscapacity','nowms',
'connecttimeoutms','responsetimeoutms','retrydelayms']
for n in args+['atmbcall_cext']:
if n not in lookup: raise ValueError('Missing generated field '+n)
# Match the stub body, preserving all surrounding generated source.
start=match.end()-1; depth=1; end=start+1
# Stub bodies must be empty or comments, not application code.
while end<len(c) and depth:
if c[end]=='{': depth+=1
elif c[end]=='}': depth-=1
end+=1
if depth: raise ValueError('Unbalanced generated stub')
old=c[start+1:end-1]
old=re.sub(r'/\*.*?\*/|//[^\n]*','',old,flags=re.S).strip()
if old: raise ValueError('Stub contains code; inspect manually instead of replacing it')
body='{\n '+param+'->'+lookup['atmbcall_cext']+' = ab_call(\n '
body+=',\n '.join(param+'->'+lookup[n] for n in args)+');\n}'
c=c[:start]+body+c[end:]
# Use one component C translation unit because the supplied SDK derives
# component identity from the top-level C filename list.
c='#include "../src/atom_bridge.h"\n'+c
c+='\n#include "../src/atom_mb.c"\n#include "../src/atom_bridge.c"\n'
frame=(sdk/'src/CmpFrame.c').read_text()
hook=re.search(r'(static\s+RTS_RESULT\s+CDECL\s+HookFunction\([^)]*\)\s*\{)',frame)
if not hook: raise ValueError('Unexpected SDK HookFunction; inspect manually')
frame=frame[:hook.end()]+'\n if (ulHook == CH_EXIT) ab_shutdown();\n'+frame[hook.end():]
frame='#include "../src/atom_bridge.h"\n'+frame
destination.mkdir(parents=True,exist_ok=True)
if any(destination.iterdir()): raise ValueError('Destination must be empty; preserve existing build work')
(destination/'CmpAtomModbus.c').write_text(c)
(destination/interface.name).write_text(m4)
(destination/'CmpFrame.c').write_text(frame)
(destination/'makefile').write_text('''# Generated from supplied ExtensionSDK; native build on ARM64 Pi.
SDKDIR ?= ../../ExtensionSDK
MAJORVER=0
MINORVER=2
CMPVERSION=0x00020000
# Local development ID; check no other component uses this ID.
CMPID=0x2001
DOS2UNIX=dos2unix
M4=m4
CC ?= gcc
# The SDK otherwise includes CmpFrame in its component identity wildcard.
override CSRC := CmpAtomModbus.c
include $(SDKDIR)/makefile
''')
if __name__=='__main__':
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('source',type=Path); p.add_argument('interface',type=Path)
p.add_argument('--sdk',required=True,type=Path)
p.add_argument('--out',type=Path,default=Path('component'))
a=p.parse_args()
prepare(a.source,a.interface,a.sdk,a.out)
print('Prepared component; compile and validate the generated interface on target.')