first commit
This commit is contained in:
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Temporary build workspace. Do not store authoritative source here.
|
||||||
@@ -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.
@@ -0,0 +1,4 @@
|
|||||||
|
atom
|
||||||
|
ADAM-CODESYS
|
||||||
|
5068
|
||||||
|
639263998361642755
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Save your editable CmpAtomModbus.library and Atom library projects here, including project information and references.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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"
|
||||||
@@ -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 **/
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
Release outputs are created by tools/build-linux.sh. No precompiled .so is included.
|
||||||
+54
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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.
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
##########################
|
||||||
@@ -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
|
||||||
@@ -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__*/
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
Executable
+34
@@ -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"
|
||||||
@@ -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')
|
||||||
@@ -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.')
|
||||||
Reference in New Issue
Block a user