From 07244fc761240e9d127eb2be33c4786dc3862044 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 30 Sep 2026 21:17:41 -0600 Subject: [PATCH] first commit --- CHANGELOG.md | 11 + COMPATIBILITY.md | 15 + LICENSE-NOTES.md | 7 + MANIFEST.sha256 | 55 + Makefile | 15 + README.md | 32 + build/README.md | 1 + codesys/BUILD_ON_PI.md | 87 + codesys/CmpAtomModbus.library | Bin 0 -> 383056 bytes codesys/CmpAtomModbus.~u | 4 + codesys/INTEGRATION.md | 88 + codesys/PI_SETUP.md | 72 + codesys/PLC_PRG_declaration.st | 30 + codesys/PLC_PRG_implementation.st | 93 + codesys/PROJECTS-TO-ADD.md | 1 + codesys/STATUS.md | 54 + codesys/atMbCall_cext.st | 23 + compat/All_Source_Review.st | 2296 +++++++ .../AtomModbus-Compatible-DispatchFix.export | 5531 +++++++++++++++++ compat/AtomModbus-Compatible-Fixed.export | 5519 ++++++++++++++++ compat/AtomModbus-Compatible.export | 5531 +++++++++++++++++ compat/Original_HomeCodesys.export | 4646 ++++++++++++++ compat/README.md | 137 + component/CmpAtomModbus.c | 30 + component/CmpAtomModbusItf.m4 | 70 + component/CmpFrame.c | 81 + component/makefile | 13 + dist/README.md | 1 + docs/API.md | 54 + docs/BUILD.md | 37 + docs/PACKAGING.md | 19 + docs/TESTING.md | 14 + docs/TROUBLESHOOTING.md | 27 + generated/CmpAtomModbus.c | 10 + generated/CmpAtomModbusItf.m4 | 70 + sdk/ExtensionSDK-original.zip | Bin 0 -> 29922 bytes sdk/ExtensionSDK/README | 149 + sdk/ExtensionSDK/include/CmpErrors.h | 16 + sdk/ExtensionSDK/include/CmpItf.h | 711 +++ sdk/ExtensionSDK/include/CmpStd.h | 641 ++ sdk/ExtensionSDK/m4/Exports.m4 | 1514 +++++ sdk/ExtensionSDK/m4/Implementation.m4 | 897 +++ sdk/ExtensionSDK/m4/Util.m4 | 113 + sdk/ExtensionSDK/makefile | 183 + sdk/ExtensionSDK/makefile_template | 48 + sdk/ExtensionSDK/src/CmpFrame.c | 78 + src/atom_bridge.c | 86 + src/atom_bridge.h | 17 + src/atom_mb.c | 146 + src/atom_mb.h | 38 + tests/bridge_driver.c | 29 + tests/driver.c | 24 + tests/test_bridge.c | 19 + tests/test_tcp.py | 59 + tools/build-linux.sh | 34 + tools/build_compat_export.py | 542 ++ tools/prepare_component.py | 76 + 57 files changed, 30094 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 COMPATIBILITY.md create mode 100644 LICENSE-NOTES.md create mode 100644 MANIFEST.sha256 create mode 100644 Makefile create mode 100644 README.md create mode 100644 build/README.md create mode 100644 codesys/BUILD_ON_PI.md create mode 100644 codesys/CmpAtomModbus.library create mode 100644 codesys/CmpAtomModbus.~u create mode 100644 codesys/INTEGRATION.md create mode 100644 codesys/PI_SETUP.md create mode 100644 codesys/PLC_PRG_declaration.st create mode 100644 codesys/PLC_PRG_implementation.st create mode 100644 codesys/PROJECTS-TO-ADD.md create mode 100644 codesys/STATUS.md create mode 100644 codesys/atMbCall_cext.st create mode 100644 compat/All_Source_Review.st create mode 100644 compat/AtomModbus-Compatible-DispatchFix.export create mode 100644 compat/AtomModbus-Compatible-Fixed.export create mode 100644 compat/AtomModbus-Compatible.export create mode 100644 compat/Original_HomeCodesys.export create mode 100644 compat/README.md create mode 100644 component/CmpAtomModbus.c create mode 100644 component/CmpAtomModbusItf.m4 create mode 100644 component/CmpFrame.c create mode 100644 component/makefile create mode 100644 dist/README.md create mode 100644 docs/API.md create mode 100644 docs/BUILD.md create mode 100644 docs/PACKAGING.md create mode 100644 docs/TESTING.md create mode 100644 docs/TROUBLESHOOTING.md create mode 100644 generated/CmpAtomModbus.c create mode 100644 generated/CmpAtomModbusItf.m4 create mode 100644 sdk/ExtensionSDK-original.zip create mode 100644 sdk/ExtensionSDK/README create mode 100644 sdk/ExtensionSDK/include/CmpErrors.h create mode 100644 sdk/ExtensionSDK/include/CmpItf.h create mode 100644 sdk/ExtensionSDK/include/CmpStd.h create mode 100644 sdk/ExtensionSDK/m4/Exports.m4 create mode 100644 sdk/ExtensionSDK/m4/Implementation.m4 create mode 100644 sdk/ExtensionSDK/m4/Util.m4 create mode 100644 sdk/ExtensionSDK/makefile create mode 100644 sdk/ExtensionSDK/makefile_template create mode 100644 sdk/ExtensionSDK/src/CmpFrame.c create mode 100644 src/atom_bridge.c create mode 100644 src/atom_bridge.h create mode 100644 src/atom_mb.c create mode 100644 src/atom_mb.h create mode 100644 tests/bridge_driver.c create mode 100644 tests/driver.c create mode 100644 tests/test_bridge.c create mode 100644 tests/test_tcp.py create mode 100755 tools/build-linux.sh create mode 100644 tools/build_compat_export.py create mode 100644 tools/prepare_component.py diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a1a1d82 --- /dev/null +++ b/CHANGELOG.md @@ -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. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 0000000..39c91a6 --- /dev/null +++ b/COMPATIBILITY.md @@ -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. diff --git a/LICENSE-NOTES.md b/LICENSE-NOTES.md new file mode 100644 index 0000000..94c46f5 --- /dev/null +++ b/LICENSE-NOTES.md @@ -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. diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 new file mode 100644 index 0000000..4aea9eb --- /dev/null +++ b/MANIFEST.sha256 @@ -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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..718e302 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab6e336 --- /dev/null +++ b/README.md @@ -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. diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..1269d03 --- /dev/null +++ b/build/README.md @@ -0,0 +1 @@ +Temporary build workspace. Do not store authoritative source here. diff --git a/codesys/BUILD_ON_PI.md b/codesys/BUILD_ON_PI.md new file mode 100644 index 0000000..8cf8320 --- /dev/null +++ b/codesys/BUILD_ON_PI.md @@ -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. diff --git a/codesys/CmpAtomModbus.library b/codesys/CmpAtomModbus.library new file mode 100644 index 0000000000000000000000000000000000000000..69b4a1edfb3f4c4d66bc6de06cbfd307cdf516c1 GIT binary patch literal 383056 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%0AK?AT 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 literal 0 HcmV?d00001 diff --git a/codesys/CmpAtomModbus.~u b/codesys/CmpAtomModbus.~u new file mode 100644 index 0000000..d137df2 --- /dev/null +++ b/codesys/CmpAtomModbus.~u @@ -0,0 +1,4 @@ +atom +ADAM-CODESYS +5068 +639263998361642755 diff --git a/codesys/INTEGRATION.md b/codesys/INTEGRATION.md new file mode 100644 index 0000000..c2b952d --- /dev/null +++ b/codesys/INTEGRATION.md @@ -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 diff --git a/codesys/PI_SETUP.md b/codesys/PI_SETUP.md new file mode 100644 index 0000000..55cbefe --- /dev/null +++ b/codesys/PI_SETUP.md @@ -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. diff --git a/codesys/PLC_PRG_declaration.st b/codesys/PLC_PRG_declaration.st new file mode 100644 index 0000000..b424426 --- /dev/null +++ b/codesys/PLC_PRG_declaration.st @@ -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 diff --git a/codesys/PLC_PRG_implementation.st b/codesys/PLC_PRG_implementation.st new file mode 100644 index 0000000..68c4269 --- /dev/null +++ b/codesys/PLC_PRG_implementation.st @@ -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; diff --git a/codesys/PROJECTS-TO-ADD.md b/codesys/PROJECTS-TO-ADD.md new file mode 100644 index 0000000..71e8192 --- /dev/null +++ b/codesys/PROJECTS-TO-ADD.md @@ -0,0 +1 @@ +Save your editable CmpAtomModbus.library and Atom library projects here, including project information and references. diff --git a/codesys/STATUS.md b/codesys/STATUS.md new file mode 100644 index 0000000..372dfb3 --- /dev/null +++ b/codesys/STATUS.md @@ -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. diff --git a/codesys/atMbCall_cext.st b/codesys/atMbCall_cext.st new file mode 100644 index 0000000..37caf1c --- /dev/null +++ b/codesys/atMbCall_cext.st @@ -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 diff --git a/compat/All_Source_Review.st b/compat/All_Source_Review.st new file mode 100644 index 0000000..a32f71f --- /dev/null +++ b/compat/All_Source_Review.st @@ -0,0 +1,2296 @@ + +(* ModbusMaster/atMbClientToString -- Interface *) +{attribute 'qualified_only'} +{attribute 'strict'} +TYPE atMbClientToString : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE + + +(* ModbusMaster/atMBExceptionCodes -- Interface *) +{attribute 'qualified_only'} +// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes. +TYPE atMBExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE + + +(* ModbusMaster/atMbClient -- Interface *) +{attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + //{attribute 'symbol' := 'none'} + reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests +END_VAR +VAR + {attribute 'symbol' := 'none'} + datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits + {attribute 'symbol' := 'none'} + client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set. + {attribute 'symbol' := 'none'} + connectCnt: DINT; //Number of connection attempts + {attribute 'symbol' := 'none'} + initDone: BOOL; + {attribute 'symbol' := 'none'} + disconnectTimer: TON; //Connecting but not connected delay + {attribute 'symbol' := 'none'} + reconnectTimer: TON; //Not connecting and not connected delay + {attribute 'symbol' := 'none'} + msgCountLast: UDINT; //Last message count + {attribute 'symbol' := 'none'} + msgCountSum: UDINT; //Message count accumulator + {attribute 'symbol' := 'none'} + msgTimeSum: UDINT; //Amount of time for messsages accumulator + {attribute 'symbol' := 'none'} + pulse: atPulse; //Pulse generator + {attribute 'symbol' := 'none'} + requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct + {attribute 'symbol' := 'none'} + requestCnt: UINT; //Number of active requests + {attribute 'symbol' := 'none'} + reqDoneSum: UDINT; //Count of done result on all requests + {attribute 'symbol' := 'none'} + reqErrSum: UDINT; //Count of error result on all requests + {attribute 'symbol' := 'none'} + i: DINT; //Loop variable + {attribute 'symbol' := 'none'} + nextIdx: DINT := -1; //Index of the active request + {attribute 'symbol' := 'none'} + priorityLowest: INT; //lowest request priority + {attribute 'symbol' := 'none'} + highestTimeSinceRun: UDINT; //lowest request priority + {attribute 'symbol' := 'none'} + allRequestsOfflineTON: TON; //All requests are offline delay + {attribute 'symbol' := 'none'} + requestNotExecutingTON: TON; //Delay after there are no requests execting + {attribute 'symbol' := 'none'} + recreateClientDly: TON; //Delay to make new client after force fail + {attribute 'symbol' := 'none'} + allRequestsOffline: BOOL; //All requests are offline + {attribute 'symbol' := 'none'} + connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true. + {attribute 'symbol' := 'none'} + forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect. + test1: REAL; + test2: REAL; + destroyClient: BOOL; + recreateTest: BOOL; +END_VAR +VAR CONSTANT + {attribute 'symbol' := 'none'} + NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay + {attribute 'symbol' := 'none'} + RECONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + DISCONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + RECREATE_CLIENT_DELAY: TIME := T#1000MS; + {attribute 'symbol' := 'none'} + ALL_OFFLINE_DELAY: TIME := T#2S; + {attribute 'symbol' := 'none'} + FORCE_RECONNECT_DELAY: TIME := T#1S; + {attribute 'symbol' := 'none'} + PRIORITY_NONE: INT := 32767; +END_VAR + +VAR +nativeMode : BOOL; +nativeHandle : UDINT; +nativeIP : DWORD; +nativePort : UINT; +nativeNowUs : SYSTIME; +nativeNowMs : ULINT; +nativeStatus : ARRAY[0..23] OF UDINT; +nativeActiveIndex : DINT := -1; +nativeSelected : POINTER TO atMbReqBase; +nativeResultReady : BOOL; +nativeFinishedIndex : DINT := -1; +nativeCallResult : UDINT; +nativeWindowStart : ULINT; +nativePrevConnected : BOOL; +nativeEnable : BOOL := TRUE; +nativeConnectTimeout : UDINT := 2000; +nativeRetryDelay : UDINT := 1000; +nativeHoldUncertainWrites : BOOL := FALSE; +END_VAR +VAR_INPUT +{attribute 'symbol' := 'none'} +nativeIO : ARRAY[0..1999] OF WORD; +{attribute 'symbol' := 'none'} +nativeActiveRequest : POINTER TO atMbReqBase; +{attribute 'symbol' := 'none'} +nativeArgUnit : BYTE; +{attribute 'symbol' := 'none'} +nativeArgFunction : BYTE; +{attribute 'symbol' := 'none'} +nativeArgAddress : UINT; +{attribute 'symbol' := 'none'} +nativeArgQuantity : UINT; +{attribute 'symbol' := 'none'} +nativeArgTimeout : UDINT := 1000; +{attribute 'symbol' := 'none'} +nativeSent : UDINT; +{attribute 'symbol' := 'none'} +nativeReplied : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowCount : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowTime : UDINT; +END_VAR + +VAR_OUTPUT +out_connected : BOOL; +out_connectionState : UDINT; +out_lastNativeError : UDINT; +out_socketError : UDINT; +out_reconnectCount : UDINT; +out_registrationFull : BOOL; +END_VAR + + +(* ModbusMaster/atMbClient -- Implementation *) +IF nativeMode THEN + nativeCycle(); + RETURN; +END_IF; +//Modbus connection +//Connects to a modbus slave +//2024-11-23 ATM Cleaned up code +//2024-12-23 CWD Add statistics +//2025-06-18 CWD Add Get priorities and interval working together +//2026-03-26 ATM Clean up code, more testing +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +// ===== Main function call ===== + +IF (in_resetStats) THEN + resetStats(); + in_resetStats := FALSE; +END_IF + +// ===== Connect and disconnect logic ===== + +//Timer to trigger a connection if not connected for to long +reconnectTimer( + IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError, + PT := RECONNECT_DELAY +); + +//Timer to trigger a disconnection if connect fails +disconnectTimer( + IN:=client.xConnect AND NOT client.xConnected, + PT:=DISCONNECT_DELAY +); + +//All requests offline timer +allRequestsOfflineTON( + IN := allRequestsOffline AND client.xConnected, + PT := ALL_OFFLINE_DELAY +); + +//Destroy Client delay timer +recreateClientDly( + IN:=destroyClient, + PT:=RECREATE_CLIENT_DELAY +); + +IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN + //All requests are offline + //Connection attempt failed so disconnect + //If Client has error then disconnect + connect := FALSE; + recreateTest := FALSE; + destroyClient := TRUE; + setAllRequestsOffline(); +ELSIF reconnectTimer.Q AND NOT destroyClient THEN +//If not connected then attempt connect + connect := TRUE; + connectCnt := connectCnt + 1; //for debugging +END_IF; + + +IF recreateClientDly.Q THEN + destroyClient := FALSE; + recreateClient(); +END_IF; + +// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect). +forceConnect( + in := connect <> client.xConnect, + pt := FORCE_RECONNECT_DELAY +); + +// Only flip status of xConnect if no current request is active. +IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN + client.xConnect := connect; +END_IF + +// Moved this lower down, so xConnect happens one scan sooner. +client(); + +// If there are no requests, dont neeed to even do anything. +IF requestCnt = 0 THEN + numMsgSent := 0; + numMsgReply := 0; + allRequestsOnline := 0; + allRequestsOffline := 0; + RETURN; +END_IF + + +requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary + +// ===== Execute next request ===== +IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN + + //Find the lowest priority of overdue requests + nextIdx := -1; + priorityLowest := PRIORITY_NONE; + highestTimeSinceRun := 0; + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF (requests[i]^.in_priorityCurrent < priorityLowest + OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun)) + AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN + priorityLowest := requests[i]^.in_priorityCurrent; + highestTimeSinceRun := requests[i]^.stats.sinceLastExec; + nextIdx := i; + END_IF + END_FOR + + //Decriment all request priorities by one. + IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1; + IF requests[i]^.in_priorityCurrent < 0 THEN + requests[i]^.in_priorityCurrent := 0; + END_IF + END_FOR + END_IF + + IF nextIdx >= 0 THEN + activeIndex := nextIdx; + END_IF + END_IF + +processAllRequests(); + +allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt); +allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0); + +// ===== Client statistics ===== +//Get a sum of done and error counts from all requests +reqErrSum := 0; +reqDoneSum := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt; + reqErrSum := reqErrSum + requests[i]^.stats.errorCnt; + + //copy stats to array for debugging + reqStats[i] := ADR(requests[i]^.stats); +END_FOR + + + +//Every time the tx message counter changes increment msg count and add last transaction time +IF client.udiNumMsgSent <> msgCountLast THEN + msgCountLast := client.udiNumMsgSent; + msgTimeSum := msgTimeSum + client.udiLastTransactionTime; + msgCountSum := msgCountSum + 1; +END_IF + +//Every 1 second compute statistics +pulse(); +IF pulse.T_Sec_OS THEN + msgLastSecondCnt := msgCountSum; + msgCountSum := 0; + msgLastSecondTime := msgTimeSum; + msgTimeSum := 0; +END_IF + +numMsgSent := client.udiNumMsgSent; +numMsgReply := client.udiNumMsgReply; + + +(* ModbusMaster/atMbClientRTU -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_port: BYTE := 0; //0=None, 1,2,3,4 + {attribute 'symbol' := 'none'} + in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200 + {attribute 'symbol' := 'none'} + in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even + {attribute 'symbol' := 'none'} + in_dataBits: BYTE := 8; //8=8 + {attribute 'symbol' := 'none'} + in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientSerial; +END_VAR + +(* ModbusMaster/atMbClientRTU -- Implementation *) +//Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-13 CWD Fix problem with not connecting properly on power up +//2024-11-15 ATM Move from structure to defaults. +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +dataRate := in_baudRate; + + +// ===== Initialization ===== +// if the param changes, force a reinit +IF _client^.iPort <> in_port + OR _client^.dwBaudrate <> in_baudRate + OR _client^.byDataBits <> in_dataBits + OR _client^.eParity <> in_parity + OR _client^.eStopBits <> in_stopBits THEN + initDone := FALSE; +END_IF; + +IF NOT initDone THEN + initDone := TRUE; + + //Other configuration parameters + _client^.iPort := in_port; + _client^.dwBaudrate := in_baudRate; + _client^.byDataBits := in_dataBits; + _client^.eParity := in_parity; + _client^.eStopBits := in_stopBits; + _client^.eDTRcontrol := 0; + _client^.eRTScontrol := 0; + _client^.eRTUASCII := 0; + _client^.udiLogOptions := ModbusFB.LoggingOptions.All; + _client^.xConnect:=FALSE; + +END_IF + +SUPER^(); + +(* ModbusMaster/atMbClientTCP -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientTCP; +END_VAR +VAR_INPUT +in_enable : BOOL := TRUE; +in_connectTimeout : TIME := T#2S; +in_reconnectDelay : TIME := T#1S; +in_holdUncertainWrites : BOOL := FALSE; +END_VAR + +VAR +nativeEndpointIP : DWORD; +nativeEndpointPort : UINT; +nativeConfigConnect : UDINT; +nativeConfigRetry : UDINT; +END_VAR + + +(* ModbusMaster/atMbClientTCP -- Implementation *) +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^(); + +(* ModbusMaster/atMbReqBase -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbReqBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO atMbClient; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: atMbRequestStats; //Request stats + +END_VAR +VAR_OUTPUT + //{attribute 'symbol' := 'none'} + //out_fault: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataSize: UINT; + //{attribute 'symbol' := 'none'} + //quantUsed: UINT; + //{attribute 'symbol' := 'none'} + //doneTrig: R_TRIG; //Succesful request trigger + //{attribute 'symbol' := 'none'} + //errTrig: R_TRIG; //Error request result trigger + + {attribute 'symbol' := 'none'} + request: REFERENCE TO ModbusFB.ClientRequest;; +END_VAR + + +VAR +nativeFunction : BYTE; +nativeCount : UINT; +nativeOffset : UINT; +nativeInFlight : BOOL; +nativeReadComplete : BOOL; +nativeCopyAllowed : BOOL; +nativeWriteHold : BOOL; +nativeStartMs : ULINT; +nativeLoop : DINT; +nativeRC : UDINT; +END_VAR + +VAR_OUTPUT +out_nativeError : UDINT; +out_socketError : UDINT; +out_writeUncertain : BOOL; +out_validData : BOOL; +out_dataAgeMs : ULINT; +out_stale : BOOL := TRUE; +out_parameterError : BOOL; +END_VAR + +VAR_INPUT +in_staleAfter : TIME := T#2S; +END_VAR + + +(* ModbusMaster/atMbReqBase -- Implementation *) +//Modbus request +//Makes a request using the start and quantity, puts the result into data +//2024-06-27 CWD Initial create +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage + +IF NOT __ISVALIDREF(client) THEN + RETURN; +END_IF + + +//Add this request to the clients request array +client.addRequest(THIS); + +//fault logic +//Fault if error +//errTrig(CLK := stats.reqError); +//IF errTrig.Q THEN +// out_fault := TRUE; +//END_IF +//not faulted if a succesfull request +//doneTrig(CLK := stats.reqDone); +// also clear fault if not active +//IF doneTrig.Q OR NOT stats.active THEN +// out_fault := FALSE; +//END_IF + +IF in_quantity <= dataSize THEN + stats.reqLength := in_quantity; +ELSE + stats.reqLength := dataSize; +END_IF; + +(* ModbusMaster/atMbReqReadCoil -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils; +END_VAR + + +(* ModbusMaster/atMbReqReadCoil -- Implementation *) +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; +//Modbus request input registers +//2024-11-16 CWD Added read coil +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + + + +(* ModbusMaster/atMbReqReadDiscrete -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs; +END_VAR + + +(* ModbusMaster/atMbReqReadDiscrete -- Implementation *) +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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + +(* ModbusMaster/atMbReqReadHolding -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters; +END_VAR + + +(* ModbusMaster/atMbReqReadHolding -- Implementation *) +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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + +(* ModbusMaster/atMbReqReadInput -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters; +END_VAR + + +(* ModbusMaster/atMbReqReadInput -- Implementation *) +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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + +(* ModbusMaster/atMbRequestStats -- Interface *) +{attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK atMbRequestStats +VAR_INPUT + lastErrorId: modbusFB.Error; //Last error id + lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + inDoneDeb: atDebounce; + {attribute 'symbol' := 'none'} + inErrDeb: atDebounce; + {attribute 'symbol' := 'none'} + inBusyDeb: atDebounce; + {attribute 'symbol' := 'none'} + executeDeb: atDebounce; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastReqStart: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + TSSus: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + lightOn: TIME:= T#100MS; //Light on time for debug indicators +END_VAR +VAR_INPUT +nativeResponseSeen : BOOL; +nativeLastSuccessMs : ULINT; +END_VAR + + +(* ModbusMaster/atMbRequestStats -- Implementation *) +SysTimeGetUs(TSSus); + +IF (reqLength = 0) THEN + active := FALSE; + doneCnt := 0; + errorCnt := 0; + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + execute := FALSE; + online := FALSE; + offline := FALSE; + reqDone := FALSE; + reqBusy := FALSE; + reqError := FALSE; + sinceLastExec := 0; + RETURN; +END_IF +active := TRUE; + +inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn); +reqDoneLight := inDoneDeb.out_val; + +inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn); +reqBusyLight := inBusyDeb.out_val; + +inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn); +reqErrorLight := inErrDeb.out_val; + +//Debounced execute indicator for debbugging +executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn); +executeLight := executeDeb.out_val; + + +//Set online status +IF nativeResponseSeen OR doneCntRst >=3 THEN + online := TRUE; + offline := FALSE; +END_IF + +//Set online status +IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN + online := FALSE; + offline := TRUE; +END_IF + +//Record start time in ms on execute rising edge +executeTrig(CLK := execute); +IF executeTrig.Q THEN + interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + lastReqStart := TSSus; +END_IF + +//Offline count +offlineTrig(CLK := NOT online); +IF offlineTrig.Q THEN + offlineCnt := offlineCnt + 1; +END_IF + +//Calculate ms since last execute +//If the last request start is more then 1 minute ago then set it for 1 minute +IF TSSus >= lastReqStart THEN + IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF; +ELSE lastReqStart := TSSus; +END_IF; +sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + + +(* ModbusMaster/atMbReqWriteCoil -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils; +END_VAR + + +(* ModbusMaster/atMbReqWriteCoil -- Implementation *) +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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + +(* ModbusMaster/atMbReqWriteHolding -- Interface *) +{attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters; +END_VAR + + +(* ModbusMaster/atMbReqWriteHolding -- Implementation *) +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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + +(* ModbusMaster/To_IPAddress -- Interface *) +FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR + + +(* ModbusMaster/To_IPAddress -- Implementation *) + //Convert parameters to array +To_IPAddress[0] := byte1; +To_IPAddress[1] := byte2; +To_IPAddress[2] := byte3; +To_IPAddress[3] := byte4; + + + + +(* atMbClient/addRequest -- Interface *) +//Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO atMbReqBase; +END_VAR +VAR + i: UINT := 0; + +END_VAR + + +(* atMbClient/addRequest -- Implementation *) +IF request = 0 THEN RETURN; END_IF; +IF requestCnt > 0 THEN + FOR i := 0 TO UINT_TO_DINT(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; + +(* atMbClient/processAllRequests -- Interface *) +METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + i: DINT; // Loop bstisnlr +END_VAR + +(* atMbClient/processAllRequests -- Implementation *) +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]^.processRequest(); + END_IF; +END_FOR; + +(* atMbClient/recreateAllRequests -- Interface *) +METHOD PROTECTED recreateAllRequests +VAR_INPUT +END_VAR + +VAR + i : DINT; +END_VAR + + +(* atMbClient/recreateAllRequests -- Implementation *) +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]^.recreateRequest(); + END_IF; +END_FOR; + +(* atMbClient/recreateClient -- Interface *) +METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR + +(* atMbClient/recreateClient -- Implementation *) + + +(* atMbClient/resetStats -- Interface *) +METHOD resetStats +VAR_INPUT +END_VAR +VAR + i: DINT; + val: POINTER TO UDINT; +END_VAR + + + + + +(* atMbClient/resetStats -- Implementation *) +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; +connectCnt := 0; +val := ADR(client.udiNumMsgSent); +val^ := 0; +val := ADR(client.udiNumMsgReply); +val^ := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.stats.resetStats(); +END_FOR; + +(* atMbClient/setAllRequestsOffline -- Interface *) +METHOD PROTECTED setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + i: DINT; +END_VAR + + + + + +(* atMbClient/setAllRequestsOffline -- Implementation *) +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]^.stats.setClientOffline(); + END_IF; +END_FOR; + +(* atMbClient/setupClient -- Interface *) +METHOD setupClient : BOOL +VAR_INPUT +END_VAR + + +(* atMbClient/setupClient -- Implementation *) +IF nativeMode THEN + setupClient := nativeHandle <> 0; + RETURN; +END_IF; +// Only create request if doesnt exist +IF NOT __ISVALIDREF(client) THEN + recreateClient(); + IF NOT __ISVALIDREF(client) THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +setupClient := TRUE; + +(* atMbClient/activeIndex -- Interface *) +PROPERTY activeIndex : DINT + +(* atMbClient/activeReqCnt -- Interface *) +PROPERTY PRIVATE activeReqCnt : UINT + +(* atMbClient/allRequestsComplete -- Interface *) +{attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL + +(* atMbClient/onlineCnt -- Interface *) +{attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online + +(* atMbClient/requestExecuting -- Interface *) +{attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing + +(* atMbClientRTU/recreateClient -- Interface *) +METHOD recreateClient +VAR_INPUT + old_client_id: POINTER TO ModbusFB.ClientSerial; +END_VAR + + +(* atMbClientRTU/recreateClient -- Implementation *) +old_client_id := _client; +_client := __NEW(ModbusFB.ClientSerial); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + +(* atMbClientRTU/toString -- Interface *) +{attribute 'symbol' := 'none'} +PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working + +(* atMbClientTCP/recreateClient -- Interface *) +METHOD recreateClient +VAR +END_VAR + +(* atMbClientTCP/recreateClient -- Implementation *) +nativeRelease(); +// Next scan creates a fresh native context with current endpoint settings. + +(* atMbReqBase/busy -- Interface *) +METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +VAR + executeTime: ULINT; + replyTimeoutTime: ULINT; +END_VAR + + + +(* atMbReqBase/busy -- Implementation *) +stats.reqBusy := in; + +IF NOT in THEN + RETURN; +END_IF; + +(* atMbReqBase/completeRequest -- Interface *) +METHOD completeRequest +VAR_INPUT +END_VAR + + +(* atMbReqBase/completeRequest -- Implementation *) +done(request.xDone); +err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException); +busy(in := request.xBusy); +stats(); + +IF (atGVL.reset) THEN + // request.AbortModel(); + request.ResetModel(TRUE); +END_IF + +(* atMbReqBase/done -- Interface *) +METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR + + +(* atMbReqBase/done -- Implementation *) +stats.reqDone := in; + +IF NOT in THEN + RETURN; +END_IF + +//Set priority for next request +in_priorityCurrent := in_prioritySetpoint; + +(* atMbReqBase/err -- Interface *) +METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: modbusFB.Error; //modbusFB error ID + exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR + + +(* atMbReqBase/err -- Implementation *) +stats.reqError := in; + +IF NOT in THEN + RETURN; +END_IF + + +//Update error info +stats.lastErrorId := errorId; +stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode); + +//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. +//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + //Since there was no response from the device set the priority to higher number priority +// in_priorityCurrent := in_prioritySetPoint * 5; +//END_IF + +//Set priority for next request, priority gets higher after each error result +priorityMultiplier := UDINT_TO_INT(stats.errorCntRst); +IF priorityMultiplier > 3 THEN + priorityMultiplier := 3; +END_IF +// set multiplier to exponential retry. +in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier; +//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier; + +// NO idea what Reset Model does, hoping it fixes comms issues +// request.ResetModel(TRUE); // I dont think this helped + +(* atMbReqBase/processRequest -- Interface *) +METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR + + +(* atMbReqBase/processRequest -- Implementation *) + + +(* atMbReqBase/recreateRequest -- Interface *) +METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqBase/recreateRequest -- Implementation *) + + +(* atMbReqBase/setupClient -- Interface *) +METHOD setupClient : BOOL +VAR_INPUT + in_request: POINTER TO ModbusFB.ClientRequest; +END_VAR + + +(* atMbReqBase/setupClient -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + setupClient := FALSE; // Native path has no ModbusFB request object. + RETURN; +END_IF; +// Only create request if doesnt exist +IF in_request = 0 THEN + in_request := recreateRequest(); + IF in_request = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +IF NOT __ISVALIDREF(mbsClient) THEN + setupClient := FALSE; + RETURN; +END_IF + +request REF= in_request^; + +setupClient := TRUE; +RETURN; + +(* atMbReqBase/mbsClient -- Interface *) +PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client + +(* atMbReqBase/replyTimeout -- Interface *) +//Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT + +(* atMbReqReadCoil/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqReadCoil/processRequest -- Implementation *) +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)); + nativeProcess(1); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readCoilReq) THEN + RETURN; +END_IF + +//Do request processing +readCoilReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqReadCoil/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqReadCoil/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readCoilReq <> 0 THEN + __DELETE(readCoilReq); + readCoilReq := 0; +END_IF +readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils); +recreateRequest := readCoilReq; + +(* atMbReqReadDiscrete/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqReadDiscrete/processRequest -- Implementation *) +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)); + nativeProcess(2); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readDiscreteReq) THEN + RETURN; +END_IF + +//Do request processing +readDiscreteReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqReadDiscrete/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqReadDiscrete/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readDiscreteReq <> 0 THEN + __DELETE(readDiscreteReq); + readDiscreteReq := 0; +END_IF +readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs); +recreateRequest := readDiscreteReq; + +(* atMbReqReadHolding/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqReadHolding/processRequest -- Implementation *) +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)); + nativeProcess(3); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readHoldReq) THEN + RETURN; +END_IF + +//Do request processing +readHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + // udiReplyTimeout := 1 * 1000 * 1000, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqReadHolding/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqReadHolding/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readHoldReq <> 0 THEN + __DELETE(readHoldReq); + readHoldReq := 0; +END_IF +readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters); +recreateRequest := readHoldReq; + +(* atMbReqReadInput/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqReadInput/processRequest -- Implementation *) +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)); + nativeProcess(4); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readInputReq) THEN + RETURN; +END_IF + +//Do request processing +readInputReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqReadInput/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqReadInput/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readInputReq <> 0 THEN + __DELETE(readInputReq); + readInputReq := 0; +END_IF +readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters); +recreateRequest := readInputReq; + +(* atMbRequestStats/resetStats -- Interface *) +METHOD resetStats +VAR_INPUT +END_VAR + + +(* atMbRequestStats/resetStats -- Implementation *) +doneCnt := 0; +doneCntRst := 0; +errorCnt := 0; +errorCntRst := 0; + +(* atMbRequestStats/setClientOffline -- Interface *) +METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR + + +(* atMbRequestStats/setClientOffline -- Implementation *) +nativeResponseSeen := FALSE; +doneCntRst := 0; +online := FALSE; +offline := TRUE; + +(* atMbRequestStats/setToString -- Interface *) +METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR + + +(* atMbRequestStats/setToString -- Implementation *) +toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt)))); + +(* atMbRequestStats/execute -- Interface *) +PROPERTY execute : BOOL + +(* atMbRequestStats/reqBusy -- Interface *) +PROPERTY reqBusy : BOOL + +(* atMbRequestStats/reqDone -- Interface *) +PROPERTY reqDone : BOOL + +(* atMbRequestStats/reqError -- Interface *) +PROPERTY reqError : BOOL + +(* atMbReqWriteCoil/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqWriteCoil/processRequest -- Implementation *) +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 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 + client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(15); + RETURN; +END_IF; +IF NOT setupClient(writeCoilsReq) THEN + RETURN; +END_IF + +//Do request processing +writeCoilsReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqWriteCoil/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqWriteCoil/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeCoilsReq <> 0 THEN + __DELETE(writeCoilsReq); + writeCoilsReq := 0; +END_IF +writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils); +recreateRequest := writeCoilsReq; + +(* atMbReqWriteCoil/writeNow -- Interface *) +METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + +(* atMbReqWriteCoil/writeNow -- Implementation *) +nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + +(* atMbReqWriteHolding/processRequest -- Interface *) +METHOD processRequest + + +(* atMbReqWriteHolding/processRequest -- Implementation *) +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 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 + client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(16); + RETURN; +END_IF; +IF NOT setupClient(writeHoldReq) THEN + RETURN; +END_IF + +//Do request processing +writeHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + +(* atMbReqWriteHolding/recreateRequest -- Interface *) +METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + +(* atMbReqWriteHolding/recreateRequest -- Implementation *) +IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeHoldReq <> 0 THEN + __DELETE(writeHoldReq); + writeHoldReq := 0; +END_IF +writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters); +recreateRequest := writeHoldReq; + +(* atMbReqWriteHolding/writeNow -- Interface *) +METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + +(* atMbReqWriteHolding/writeNow -- Implementation *) +nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + +(* activeIndex/Get -- Interface *) +VAR + i: DINT; //Loop variable +END_VAR + + +(* activeIndex/Get -- Implementation *) +IF nativeMode THEN + activeIndex := nativeActiveIndex; + RETURN; +END_IF; +// ===== Test for any requests executing ===== +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN + activeIndex := i; + RETURN; + END_IF +END_FOR +activeIndex := -1; + + +(* activeIndex/Set -- Interface *) +VAR +END_VAR + + +(* activeIndex/Set -- Implementation *) +// set request to active, which is how the active index gets calculated. +requests[activeIndex]^.stats.execute := TRUE; + +(* activeReqCnt/Get -- Interface *) +VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + +(* activeReqCnt/Get -- Implementation *) +cnt := 0; +IF requestCnt = 0 THEN + activeReqCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.active THEN + cnt := cnt + 1; + END_IF +END_FOR; +activeReqCnt := cnt; + +(* allRequestsComplete/Get -- Interface *) +VAR + i: UINT; +END_VAR + + + + + +(* allRequestsComplete/Get -- Implementation *) +IF requestCnt = 0 THEN + allRequestsComplete := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN + allRequestsComplete := FALSE; + RETURN; + END_IF +END_FOR; +allRequestsComplete := TRUE; + +(* onlineCnt/Get -- Interface *) +VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + +(* onlineCnt/Get -- Implementation *) +cnt := 0; +IF requestCnt = 0 THEN + onlineCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.online THEN + cnt := cnt + 1; + END_IF +END_FOR; +onlineCnt := cnt; + +(* requestExecuting/Get -- Interface *) +VAR + i: DINT; //Loop variable +END_VAR + + +(* requestExecuting/Get -- Implementation *) +// ===== Test for any requests executing ===== +requestExecuting := activeIndex <> -1; + +(* toString/Get -- Interface *) + + +(* toString/Get -- Implementation *) +//Calculation for tostring +IF onlineCnt = requestCnt THEN + toString := atMbClientToString.allOk; +ELSIF onlineCnt = 0 THEN + toString := atMbClientToString.noneOk; +ELSE + toString := atMbClientToString.someOk; +END_IF + +(* mbsClient/Get -- Interface *) +VAR +END_VAR + + +(* mbsClient/Get -- Implementation *) +IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN + RETURN; +END_IF + +mbsClient REF= client.client; + +(* replyTimeout/Get -- Interface *) +VAR +END_VAR + + +(* replyTimeout/Get -- Implementation *) + +IF in_timeout > 0 THEN + //Explicitly set timout + replyTimeout := ANY_TO_UDINT((in_timeout) * 1000); +ELSE + // If client timeout is set, use that. + IF client.in_timeout <> T#0MS THEN + replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000; + ELSIF client.dataRate <> 0 THEN + //Data transmision time * 1.5 to get the timeout. Result is in ms + //8 byte request, then 255 byte reply = 2630 bits. + replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000); + ELSE + //In case client datarate = zero + replyTimeout := 1000000; //1 second? + END_IF + + +END_IF + + + +(* execute/Get -- Interface *) +VAR +END_VAR + + +(* execute/Get -- Implementation *) +execute := _execute; + +(* execute/Set -- Interface *) +VAR +END_VAR + + +(* execute/Set -- Implementation *) +IF reqDone OR reqError THEN + _execute := FALSE; + RETURN; +END_IF +_execute := execute; + +(* reqBusy/Get -- Interface *) +VAR +END_VAR + + +(* reqBusy/Get -- Implementation *) +reqBusy := _reqBusy; + +(* reqBusy/Set -- Interface *) +VAR +END_VAR + + +(* reqBusy/Set -- Implementation *) +_reqBusy := reqBusy; + +(* reqDone/Get -- Interface *) +VAR +END_VAR + + +(* reqDone/Get -- Implementation *) +reqDone := _reqDone; + +(* reqDone/Set -- Interface *) +VAR +END_VAR + + +(* reqDone/Set -- Implementation *) +_reqDone := reqDone; + +IF (execute AND reqDone) THEN + // ===== Request has just completed succesfully ===== + //Update last valid duration + lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + IF lastValidDuration > longestValidDuration THEN + longestValidDuration := lastValidDuration; + END_IF + //Counters + doneCnt := doneCnt + 1; + doneCntRst := doneCntRst + 1; + errorCntRst := 0; + + //Update error info + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + + execute := FALSE; + setToString(); +END_IF + +(* reqError/Get -- Interface *) +VAR +END_VAR + + +(* reqError/Get -- Implementation *) +reqError := _reqError; + +(* reqError/Set -- Interface *) +VAR +END_VAR + + +(* reqError/Set -- Implementation *) +_reqError := reqError; + +IF (execute AND reqError) THEN + // ===== Request has just completed with error ===== + // Counters + errorCnt := errorCnt + 1; + errorCntRst := errorCntRst + 1; + doneCntRst := 0; + //Turn off execute + execute := FALSE; + + //Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. + IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + timeoutCnt := timeoutCnt + 1; + lastExceptionCode := atMBExceptionCodes.TIMEOUT; + END_IF + setToString(); +END_IF + +(* atMbClient/nativeCall -- Interface *) +METHOD nativeCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR + +(* atMbClient/nativeCall -- Implementation *) +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); + +(* atMbClient/nativeCycle -- Interface *) +METHOD nativeCycle +VAR + j : DINT; + best : DINT; + priority : INT; + age : UDINT; + rc : UDINT; +END_VAR + +(* atMbClient/nativeCycle -- Implementation *) +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; + +(* atMbClientTCP/nativeRelease -- Interface *) +METHOD nativeRelease : BOOL +VAR + j : DINT; +END_VAR + +(* atMbClientTCP/nativeRelease -- Implementation *) +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; + +(* atMbClientTCP/release -- Interface *) +METHOD release : BOOL + +(* atMbClientTCP/release -- Implementation *) +in_enable := FALSE; +release := nativeRelease(); + +(* atMbClientTCP/FB_exit -- Interface *) +METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR + +(* atMbClientTCP/FB_exit -- Implementation *) +FB_exit := nativeRelease(); + +(* atMbClientTCP/FB_reinit -- Interface *) +METHOD FB_reinit : BOOL +VAR + j : DINT; +END_VAR + +(* atMbClientTCP/FB_reinit -- Implementation *) +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; + +(* atMbReqBase/invalidateNativeData -- Interface *) +METHOD PUBLIC invalidateNativeData + +(* atMbReqBase/invalidateNativeData -- Implementation *) +out_validData := FALSE; +out_stale := TRUE; + +(* atMbReqBase/nativeFinish -- Interface *) +METHOD nativeFinish +VAR_INPUT + result : UDINT; +END_VAR +VAR + mapped : ModbusFB.Error; + ex : ModbusFB.ExceptionCodes; + duration : UDINT; +END_VAR + +(* atMbReqBase/nativeFinish -- Implementation *) +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(); + +(* atMbReqBase/nativeProcess -- Interface *) +METHOD nativeProcess +VAR_INPUT + functionCode : BYTE; +END_VAR + +(* atMbReqBase/nativeProcess -- Implementation *) +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; + +(* atMbReqBase/FB_reinit -- Interface *) +METHOD FB_reinit : BOOL + +(* atMbReqBase/FB_reinit -- Implementation *) +nativeInFlight := FALSE; +stats.execute := FALSE; +stats.reqBusy := FALSE; +stats.reqDone := FALSE; +stats.reqError := FALSE; +FB_reinit := TRUE; diff --git a/compat/AtomModbus-Compatible-DispatchFix.export b/compat/AtomModbus-Compatible-DispatchFix.export new file mode 100644 index 0000000..cd8367c --- /dev/null +++ b/compat/AtomModbus-Compatible-DispatchFix.export @@ -0,0 +1,5531 @@ + + + + + + + True + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + 00000000-0000-0000-0000-000000000000 + 6-Comms + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + e59dc300-e37a-4c37-997b-021563304d7e + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590379670 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + e59dc300-e37a-4c37-997b-021563304d7e + + atomLibrary + + -1 + + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + 00000000-0000-0000-0000-000000000000 + ModbusMaster + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590449813 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + atomLibrary + 6-Comms + + -1 + + + False + + f228f401-6c74-4359-8ff1-b15b675180a4 + 00000000-0000-0000-0000-000000000000 + atMbClientToString + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591030946 + + + + + {attribute 'qualified_only'} +{attribute 'strict'} +TYPE atMbClientToString : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE + + f228f401-6c74-4359-8ff1-b15b675180a4_Decl_LineIds + + + 11 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7 + 00000000-0000-0000-0000-000000000000 + atMBExceptionCodes + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590519807 + + + + + {attribute 'qualified_only'} +// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes. +TYPE atMBExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7_Decl_LineIds + + + 78 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + 00000000-0000-0000-0000-000000000000 + atMbClient + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639251309599709549 + + + None + + + IF nativeMode THEN + nativeCycle(); + RETURN; +END_IF; +//Modbus connection +//Connects to a modbus slave +//2024-11-23 ATM Cleaned up code +//2024-12-23 CWD Add statistics +//2025-06-18 CWD Add Get priorities and interval working together +//2026-03-26 ATM Clean up code, more testing +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +// ===== Main function call ===== + +IF (in_resetStats) THEN + resetStats(); + in_resetStats := FALSE; +END_IF + +// ===== Connect and disconnect logic ===== + +//Timer to trigger a connection if not connected for to long +reconnectTimer( + IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError, + PT := RECONNECT_DELAY +); + +//Timer to trigger a disconnection if connect fails +disconnectTimer( + IN:=client.xConnect AND NOT client.xConnected, + PT:=DISCONNECT_DELAY +); + +//All requests offline timer +allRequestsOfflineTON( + IN := allRequestsOffline AND client.xConnected, + PT := ALL_OFFLINE_DELAY +); + +//Destroy Client delay timer +recreateClientDly( + IN:=destroyClient, + PT:=RECREATE_CLIENT_DELAY +); + +IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN + //All requests are offline + //Connection attempt failed so disconnect + //If Client has error then disconnect + connect := FALSE; + recreateTest := FALSE; + destroyClient := TRUE; + setAllRequestsOffline(); +ELSIF reconnectTimer.Q AND NOT destroyClient THEN +//If not connected then attempt connect + connect := TRUE; + connectCnt := connectCnt + 1; //for debugging +END_IF; + + +IF recreateClientDly.Q THEN + destroyClient := FALSE; + recreateClient(); +END_IF; + +// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect). +forceConnect( + in := connect <> client.xConnect, + pt := FORCE_RECONNECT_DELAY +); + +// Only flip status of xConnect if no current request is active. +IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN + client.xConnect := connect; +END_IF + +// Moved this lower down, so xConnect happens one scan sooner. +client(); + +// If there are no requests, dont neeed to even do anything. +IF requestCnt = 0 THEN + numMsgSent := 0; + numMsgReply := 0; + allRequestsOnline := 0; + allRequestsOffline := 0; + RETURN; +END_IF + + +requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary + +// ===== Execute next request ===== +IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN + + //Find the lowest priority of overdue requests + nextIdx := -1; + priorityLowest := PRIORITY_NONE; + highestTimeSinceRun := 0; + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF (requests[i]^.in_priorityCurrent < priorityLowest + OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun)) + AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN + priorityLowest := requests[i]^.in_priorityCurrent; + highestTimeSinceRun := requests[i]^.stats.sinceLastExec; + nextIdx := i; + END_IF + END_FOR + + //Decriment all request priorities by one. + IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1; + IF requests[i]^.in_priorityCurrent < 0 THEN + requests[i]^.in_priorityCurrent := 0; + END_IF + END_FOR + END_IF + + IF nextIdx >= 0 THEN + activeIndex := nextIdx; + END_IF + END_IF + +processAllRequests(); + +allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt); +allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0); + +// ===== Client statistics ===== +//Get a sum of done and error counts from all requests +reqErrSum := 0; +reqDoneSum := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt; + reqErrSum := reqErrSum + requests[i]^.stats.errorCnt; + + //copy stats to array for debugging + reqStats[i] := ADR(requests[i]^.stats); +END_FOR + + + +//Every time the tx message counter changes increment msg count and add last transaction time +IF client.udiNumMsgSent <> msgCountLast THEN + msgCountLast := client.udiNumMsgSent; + msgTimeSum := msgTimeSum + client.udiLastTransactionTime; + msgCountSum := msgCountSum + 1; +END_IF + +//Every 1 second compute statistics +pulse(); +IF pulse.T_Sec_OS THEN + msgLastSecondCnt := msgCountSum; + msgCountSum := 0; + msgLastSecondTime := msgTimeSum; + msgTimeSum := 0; +END_IF + +numMsgSent := client.udiNumMsgSent; +numMsgReply := client.udiNumMsgReply; + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + //{attribute 'symbol' := 'none'} + reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests +END_VAR +VAR + {attribute 'symbol' := 'none'} + datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits + {attribute 'symbol' := 'none'} + client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set. + {attribute 'symbol' := 'none'} + connectCnt: DINT; //Number of connection attempts + {attribute 'symbol' := 'none'} + initDone: BOOL; + {attribute 'symbol' := 'none'} + disconnectTimer: TON; //Connecting but not connected delay + {attribute 'symbol' := 'none'} + reconnectTimer: TON; //Not connecting and not connected delay + {attribute 'symbol' := 'none'} + msgCountLast: UDINT; //Last message count + {attribute 'symbol' := 'none'} + msgCountSum: UDINT; //Message count accumulator + {attribute 'symbol' := 'none'} + msgTimeSum: UDINT; //Amount of time for messsages accumulator + {attribute 'symbol' := 'none'} + pulse: atPulse; //Pulse generator + {attribute 'symbol' := 'none'} + requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct + {attribute 'symbol' := 'none'} + requestCnt: UINT; //Number of active requests + {attribute 'symbol' := 'none'} + reqDoneSum: UDINT; //Count of done result on all requests + {attribute 'symbol' := 'none'} + reqErrSum: UDINT; //Count of error result on all requests + {attribute 'symbol' := 'none'} + i: DINT; //Loop variable + {attribute 'symbol' := 'none'} + nextIdx: DINT := -1; //Index of the active request + {attribute 'symbol' := 'none'} + priorityLowest: INT; //lowest request priority + {attribute 'symbol' := 'none'} + highestTimeSinceRun: UDINT; //lowest request priority + {attribute 'symbol' := 'none'} + allRequestsOfflineTON: TON; //All requests are offline delay + {attribute 'symbol' := 'none'} + requestNotExecutingTON: TON; //Delay after there are no requests execting + {attribute 'symbol' := 'none'} + recreateClientDly: TON; //Delay to make new client after force fail + {attribute 'symbol' := 'none'} + allRequestsOffline: BOOL; //All requests are offline + {attribute 'symbol' := 'none'} + connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true. + {attribute 'symbol' := 'none'} + forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect. + test1: REAL; + test2: REAL; + destroyClient: BOOL; + recreateTest: BOOL; +END_VAR +VAR CONSTANT + {attribute 'symbol' := 'none'} + NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay + {attribute 'symbol' := 'none'} + RECONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + DISCONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + RECREATE_CLIENT_DELAY: TIME := T#1000MS; + {attribute 'symbol' := 'none'} + ALL_OFFLINE_DELAY: TIME := T#2S; + {attribute 'symbol' := 'none'} + FORCE_RECONNECT_DELAY: TIME := T#1S; + {attribute 'symbol' := 'none'} + PRIORITY_NONE: INT := 32767; +END_VAR + +VAR +nativeMode : BOOL; +nativeHandle : UDINT; +nativeIP : DWORD; +nativePort : UINT; +nativeNowUs : SYSTIME; +nativeNowMs : ULINT; +nativeStatus : ARRAY[0..23] OF UDINT; +nativeActiveIndex : DINT := -1; +nativeSelected : POINTER TO atMbReqBase; +nativeResultReady : BOOL; +nativeFinishedIndex : DINT := -1; +nativeCallResult : UDINT; +nativeWindowStart : ULINT; +nativePrevConnected : BOOL; +nativeEnable : BOOL := TRUE; +nativeConnectTimeout : UDINT := 2000; +nativeRetryDelay : UDINT := 1000; +nativeHoldUncertainWrites : BOOL := FALSE; +END_VAR +VAR_INPUT +{attribute 'symbol' := 'none'} +nativeIO : ARRAY[0..1999] OF WORD; +{attribute 'symbol' := 'none'} +nativeActiveRequest : POINTER TO atMbReqBase; +{attribute 'symbol' := 'none'} +nativeArgUnit : BYTE; +{attribute 'symbol' := 'none'} +nativeArgFunction : BYTE; +{attribute 'symbol' := 'none'} +nativeArgAddress : UINT; +{attribute 'symbol' := 'none'} +nativeArgQuantity : UINT; +{attribute 'symbol' := 'none'} +nativeArgTimeout : UDINT := 1000; +{attribute 'symbol' := 'none'} +nativeSent : UDINT; +{attribute 'symbol' := 'none'} +nativeReplied : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowCount : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowTime : UDINT; +END_VAR + +VAR_OUTPUT +out_connected : BOOL; +out_connectionState : UDINT; +out_lastNativeError : UDINT; +out_socketError : UDINT; +out_reconnectCount : UDINT; +out_registrationFull : BOOL; +END_VAR + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Decl_LineIds + + + 3369 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + 00000000-0000-0000-0000-000000000000 + atMbClientRTU + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649591897 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-13 CWD Fix problem with not connecting properly on power up +//2024-11-15 ATM Move from structure to defaults. +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +dataRate := in_baudRate; + + +// ===== Initialization ===== +// if the param changes, force a reinit +IF _client^.iPort <> in_port + OR _client^.dwBaudrate <> in_baudRate + OR _client^.byDataBits <> in_dataBits + OR _client^.eParity <> in_parity + OR _client^.eStopBits <> in_stopBits THEN + initDone := FALSE; +END_IF; + +IF NOT initDone THEN + initDone := TRUE; + + //Other configuration parameters + _client^.iPort := in_port; + _client^.dwBaudrate := in_baudRate; + _client^.byDataBits := in_dataBits; + _client^.eParity := in_parity; + _client^.eStopBits := in_stopBits; + _client^.eDTRcontrol := 0; + _client^.eRTScontrol := 0; + _client^.eRTUASCII := 0; + _client^.udiLogOptions := ModbusFB.LoggingOptions.All; + _client^.xConnect:=FALSE; + +END_IF + +SUPER^(); + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_port: BYTE := 0; //0=None, 1,2,3,4 + {attribute 'symbol' := 'none'} + in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200 + {attribute 'symbol' := 'none'} + in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even + {attribute 'symbol' := 'none'} + in_dataBits: BYTE := 8; //8=8 + {attribute 'symbol' := 'none'} + in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientSerial; +END_VAR + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Decl_LineIds + + + 607 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + 00000000-0000-0000-0000-000000000000 + atMbClientTCP + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649747183 + + + None + + + 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^(); + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientTCP; +END_VAR +VAR_INPUT +in_enable : BOOL := TRUE; +in_connectTimeout : TIME := T#2S; +in_reconnectDelay : TIME := T#1S; +in_holdUncertainWrites : BOOL := FALSE; +END_VAR + +VAR +nativeEndpointIP : DWORD; +nativeEndpointPort : UINT; +nativeConfigConnect : UDINT; +nativeConfigRetry : UDINT; +END_VAR + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Decl_LineIds + + + 1168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + 00000000-0000-0000-0000-000000000000 + atMbReqBase + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649837310 + + + None + + + //Modbus request +//Makes a request using the start and quantity, puts the result into data +//2024-06-27 CWD Initial create +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage + +IF NOT __ISVALIDREF(client) THEN + RETURN; +END_IF + + +//Add this request to the clients request array +client.addRequest(THIS); + +//fault logic +//Fault if error +//errTrig(CLK := stats.reqError); +//IF errTrig.Q THEN +// out_fault := TRUE; +//END_IF +//not faulted if a succesfull request +//doneTrig(CLK := stats.reqDone); +// also clear fault if not active +//IF doneTrig.Q OR NOT stats.active THEN +// out_fault := FALSE; +//END_IF + +IF in_quantity <= dataSize THEN + stats.reqLength := in_quantity; +ELSE + stats.reqLength := dataSize; +END_IF; + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbReqBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO atMbClient; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: atMbRequestStats; //Request stats + +END_VAR +VAR_OUTPUT + //{attribute 'symbol' := 'none'} + //out_fault: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataSize: UINT; + //{attribute 'symbol' := 'none'} + //quantUsed: UINT; + //{attribute 'symbol' := 'none'} + //doneTrig: R_TRIG; //Succesful request trigger + //{attribute 'symbol' := 'none'} + //errTrig: R_TRIG; //Error request result trigger + + {attribute 'symbol' := 'none'} + request: REFERENCE TO ModbusFB.ClientRequest;; +END_VAR + + +VAR +nativeFunction : BYTE; +nativeCount : UINT; +nativeOffset : UINT; +nativeInFlight : BOOL; +nativeReadComplete : BOOL; +nativeCopyAllowed : BOOL; +nativeWriteHold : BOOL; +nativeStartMs : ULINT; +nativeLoop : DINT; +nativeRC : UDINT; +END_VAR + +VAR_OUTPUT +out_nativeError : UDINT; +out_socketError : UDINT; +out_writeUncertain : BOOL; +out_validData : BOOL; +out_dataAgeMs : ULINT; +out_stale : BOOL := TRUE; +out_parameterError : BOOL; +END_VAR + +VAR_INPUT +in_staleAfter : TIME := T#2S; +END_VAR + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Decl_LineIds + + + 3210 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + 00000000-0000-0000-0000-000000000000 + atMbReqReadCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146913881 + + + None + + + 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; +//Modbus request input registers +//2024-11-16 CWD Added read coil +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils; +END_VAR + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Decl_LineIds + + + 193 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 1a3278f4-a658-407c-968a-21aa53361562 + 00000000-0000-0000-0000-000000000000 + atMbReqReadDiscrete + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147014015 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 1a3278f4-a658-407c-968a-21aa53361562_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs; +END_VAR + + 1a3278f4-a658-407c-968a-21aa53361562_Decl_LineIds + + + 184 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 73b10819-9112-4811-9583-e2735d2b13ea + 00000000-0000-0000-0000-000000000000 + atMbReqReadHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147099228 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 73b10819-9112-4811-9583-e2735d2b13ea_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters; +END_VAR + + 73b10819-9112-4811-9583-e2735d2b13ea_Decl_LineIds + + + 173 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + 00000000-0000-0000-0000-000000000000 + atMbReqReadInput + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147179217 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters; +END_VAR + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Decl_LineIds + + + 167 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + 00000000-0000-0000-0000-000000000000 + atMbRequestStats + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147425423 + + + None + + + SysTimeGetUs(TSSus); + +IF (reqLength = 0) THEN + active := FALSE; + doneCnt := 0; + errorCnt := 0; + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + execute := FALSE; + online := FALSE; + offline := FALSE; + reqDone := FALSE; + reqBusy := FALSE; + reqError := FALSE; + sinceLastExec := 0; + RETURN; +END_IF +active := TRUE; + +inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn); +reqDoneLight := inDoneDeb.out_val; + +inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn); +reqBusyLight := inBusyDeb.out_val; + +inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn); +reqErrorLight := inErrDeb.out_val; + +//Debounced execute indicator for debbugging +executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn); +executeLight := executeDeb.out_val; + + +//Set online status +IF nativeResponseSeen OR doneCntRst >=3 THEN + online := TRUE; + offline := FALSE; +END_IF + +//Set online status +IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN + online := FALSE; + offline := TRUE; +END_IF + +//Record start time in ms on execute rising edge +executeTrig(CLK := execute); +IF executeTrig.Q THEN + interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + lastReqStart := TSSus; +END_IF + +//Offline count +offlineTrig(CLK := NOT online); +IF offlineTrig.Q THEN + offlineCnt := offlineCnt + 1; +END_IF + +//Calculate ms since last execute +//If the last request start is more then 1 minute ago then set it for 1 minute +IF TSSus >= lastReqStart THEN + IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF; +ELSE lastReqStart := TSSus; +END_IF; +sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK atMbRequestStats +VAR_INPUT + lastErrorId: modbusFB.Error; //Last error id + lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + inDoneDeb: atDebounce; + {attribute 'symbol' := 'none'} + inErrDeb: atDebounce; + {attribute 'symbol' := 'none'} + inBusyDeb: atDebounce; + {attribute 'symbol' := 'none'} + executeDeb: atDebounce; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastReqStart: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + TSSus: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + lightOn: TIME:= T#100MS; //Light on time for debug indicators +END_VAR +VAR_INPUT +nativeResponseSeen : BOOL; +nativeLastSuccessMs : ULINT; +END_VAR + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Decl_LineIds + + + 286 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + 00000000-0000-0000-0000-000000000000 + atMbReqWriteCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147259331 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + f6ee860b-0951-41d0-93d8-6436c681d8f1_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils; +END_VAR + + f6ee860b-0951-41d0-93d8-6436c681d8f1_Decl_LineIds + + + 206 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bb7e18ba-2018-4681-875e-de1f291019ef + 00000000-0000-0000-0000-000000000000 + atMbReqWriteHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147339348 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + bb7e18ba-2018-4681-875e-de1f291019ef_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters; +END_VAR + + bb7e18ba-2018-4681-875e-de1f291019ef_Decl_LineIds + + + 164 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9499c60e-03a2-49d0-b086-43990a018e20 + 00000000-0000-0000-0000-000000000000 + To_IPAddress + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590489804 + + + None + + + //Convert parameters to array +To_IPAddress[0] := byte1; +To_IPAddress[1] := byte2; +To_IPAddress[2] := byte3; +To_IPAddress[3] := byte4; + + + + 9499c60e-03a2-49d0-b086-43990a018e20_Impl_LineIds + + + + + FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR + + 9499c60e-03a2-49d0-b086-43990a018e20_Decl_LineIds + + + 168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9e99e368-853f-485b-911f-d9a2a4e46434 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + addRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146468437 + + + + + IF request = 0 THEN RETURN; END_IF; +IF requestCnt > 0 THEN + FOR i := 0 TO UINT_TO_DINT(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; + 9e99e368-853f-485b-911f-d9a2a4e46434_Impl_LineIds + + + + + //Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO atMbReqBase; +END_VAR +VAR + i: UINT := 0; + +END_VAR + + 9e99e368-853f-485b-911f-d9a2a4e46434_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 685f7c69-c46a-4b34-b5e2-fb848c593f3e + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + processAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148036583 + + + + + 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]^.processRequest(); + END_IF; +END_FOR; + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Impl_LineIds + + + + + METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + i: DINT; // Loop bstisnlr +END_VAR + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 39769d9e-08c1-472d-82ef-25c66891a065 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148056585 + + + + + 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]^.recreateRequest(); + END_IF; +END_FOR; + 39769d9e-08c1-472d-82ef-25c66891a065_Impl_LineIds + + + + + METHOD PROTECTED recreateAllRequests +VAR_INPUT +END_VAR + +VAR + i : DINT; +END_VAR + + 39769d9e-08c1-472d-82ef-25c66891a065_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648119824 + + + + + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Impl_LineIds + + + + + METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + a7e27967-573f-4f9f-86e7-dd569b8cd943 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; +connectCnt := 0; +val := ADR(client.udiNumMsgSent); +val^ := 0; +val := ADR(client.udiNumMsgReply); +val^ := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.stats.resetStats(); +END_FOR; + a7e27967-573f-4f9f-86e7-dd569b8cd943_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR +VAR + i: DINT; + val: POINTER TO UDINT; +END_VAR + + + + + a7e27967-573f-4f9f-86e7-dd569b8cd943_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + ca1dfc70-ad93-43de-886d-39e56897aa6d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setAllRequestsOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148106585 + + + + + 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]^.stats.setClientOffline(); + END_IF; +END_FOR; + ca1dfc70-ad93-43de-886d-39e56897aa6d_Impl_LineIds + + + + + METHOD PROTECTED setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + i: DINT; +END_VAR + + + + + ca1dfc70-ad93-43de-886d-39e56897aa6d_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + d12c9bf2-320f-4e95-badb-68a2357c5783 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648239962 + + + + + IF nativeMode THEN + setupClient := nativeHandle <> 0; + RETURN; +END_IF; +// Only create request if doesnt exist +IF NOT __ISVALIDREF(client) THEN + recreateClient(); + IF NOT __ISVALIDREF(client) THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +setupClient := TRUE; + d12c9bf2-320f-4e95-badb-68a2357c5783_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT +END_VAR + + d12c9bf2-320f-4e95-badb-68a2357c5783_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeIndex + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590625089 + + + + + PROPERTY activeIndex : DINT + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeReqCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502146293051 + + + + + PROPERTY PRIVATE activeReqCnt : UINT + 456b4953-57c9-4f76-ab5b-0db456dd9b58_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + allRequestsComplete + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502147921016 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL + 4d9d378a-f256-4f23-b835-539ee9e287c3_Decl_LineIds + + + 2 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + onlineCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590755417 + + + + + {attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online + 89742962-f9b6-46c2-b9a5-b7e298f8495d_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 27292866-0bc0-4848-a965-64c51a0bf960 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + requestExecuting + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590845466 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing + 27292866-0bc0-4848-a965-64c51a0bf960_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4950a3a1-f54f-434d-9252-b61a6cb4663d + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649617058 + + + + + old_client_id := _client; +_client := __NEW(ModbusFB.ClientSerial); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Impl_LineIds + + + + + METHOD recreateClient +VAR_INPUT + old_client_id: POINTER TO ModbusFB.ClientSerial; +END_VAR + + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Decl_LineIds + + + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + e8c6c80b-7bdb-4108-987e-4af107de4377 + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + toString + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650142720 + + + + + {attribute 'symbol' := 'none'} +PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working + e8c6c80b-7bdb-4108-987e-4af107de4377_Decl_LineIds + + + 3 + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + 79dfe91d-6dbc-46b0-866c-4b880e44e02b + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649757233 + + + + + nativeRelease(); +// Next scan creates a fresh native context with current endpoint settings. + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Impl_LineIds + + + + + METHOD recreateClient +VAR +END_VAR + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + busy + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148176670 + + + + + stats.reqBusy := in; + +IF NOT in THEN + RETURN; +END_IF; + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Impl_LineIds + + + + + METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +VAR + executeTime: ULINT; + replyTimeoutTime: ULINT; +END_VAR + + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 5350a8fa-8297-42db-b185-fcb966e45684 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + completeRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148196661 + + + + + done(request.xDone); +err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException); +busy(in := request.xBusy); +stats(); + +IF (atGVL.reset) THEN + // request.AbortModel(); + request.ResetModel(TRUE); +END_IF + 5350a8fa-8297-42db-b185-fcb966e45684_Impl_LineIds + + + + + METHOD completeRequest +VAR_INPUT +END_VAR + + 5350a8fa-8297-42db-b185-fcb966e45684_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + done + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148206667 + + + + + stats.reqDone := in; + +IF NOT in THEN + RETURN; +END_IF + +//Set priority for next request +in_priorityCurrent := in_prioritySetpoint; + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Impl_LineIds + + + + + METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + err + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148226656 + + + + + stats.reqError := in; + +IF NOT in THEN + RETURN; +END_IF + + +//Update error info +stats.lastErrorId := errorId; +stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode); + +//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. +//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + //Since there was no response from the device set the priority to higher number priority +// in_priorityCurrent := in_prioritySetPoint * 5; +//END_IF + +//Set priority for next request, priority gets higher after each error result +priorityMultiplier := UDINT_TO_INT(stats.errorCntRst); +IF priorityMultiplier > 3 THEN + priorityMultiplier := 3; +END_IF +// set multiplier to exponential retry. +in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier; +//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier; + +// NO idea what Reset Model does, hoping it fixes comms issues +// request.ResetModel(TRUE); // I dont think this helped + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Impl_LineIds + + + + + METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: modbusFB.Error; //modbusFB error ID + exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 109d8e00-bf23-4306-a8d5-764aa6a03235 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148246723 + + + + + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Impl_LineIds + + + + + METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148266663 + + + + + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Impl_LineIds + + + + + METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + db40c392-b461-4198-851d-85857ed98ccf + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148276644 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + setupClient := FALSE; // Native path has no ModbusFB request object. + RETURN; +END_IF; +// Only create request if doesnt exist +IF in_request = 0 THEN + in_request := recreateRequest(); + IF in_request = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +IF NOT __ISVALIDREF(mbsClient) THEN + setupClient := FALSE; + RETURN; +END_IF + +request REF= in_request^; + +setupClient := TRUE; +RETURN; + db40c392-b461-4198-851d-85857ed98ccf_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT + in_request: POINTER TO ModbusFB.ClientRequest; +END_VAR + + db40c392-b461-4198-851d-85857ed98ccf_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 6159c037-e399-466d-8108-0c4f6570f9b2 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + mbsClient + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650298004 + + + + + PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client + 6159c037-e399-466d-8108-0c4f6570f9b2_Decl_LineIds + + + 1 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + replyTimeout + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591196333 + + + + + //Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT + 4f361885-f95c-4244-84d6-8c3b6b9b8a18_Decl_LineIds + + + 2 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + ea5277e9-2ae8-417f-a88d-b571902c7d71 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699321592 + + + + + 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)); + nativeProcess(1); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readCoilReq) THEN + RETURN; +END_IF + +//Do request processing +readCoilReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + ea5277e9-2ae8-417f-a88d-b571902c7d71_Impl_LineIds + + + + + METHOD processRequest + + ea5277e9-2ae8-417f-a88d-b571902c7d71_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148331930 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readCoilReq <> 0 THEN + __DELETE(readCoilReq); + readCoilReq := 0; +END_IF +readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils); +recreateRequest := readCoilReq; + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 9df094f9-a173-4520-816f-7badcbb1bd34 + 1a3278f4-a658-407c-968a-21aa53361562 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699461941 + + + + + 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)); + nativeProcess(2); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readDiscreteReq) THEN + RETURN; +END_IF + +//Do request processing +readDiscreteReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 9df094f9-a173-4520-816f-7badcbb1bd34_Impl_LineIds + + + + + METHOD processRequest + + 9df094f9-a173-4520-816f-7badcbb1bd34_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + d34866d4-1134-4650-b3d6-2e19d2fd09d1 + 1a3278f4-a658-407c-968a-21aa53361562 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148362063 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readDiscreteReq <> 0 THEN + __DELETE(readDiscreteReq); + readDiscreteReq := 0; +END_IF +readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs); +recreateRequest := readDiscreteReq; + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + 7b978176-d206-4509-b9d8-7908d33a9a74 + 73b10819-9112-4811-9583-e2735d2b13ea + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148402056 + + + + + 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)); + nativeProcess(3); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readHoldReq) THEN + RETURN; +END_IF + +//Do request processing +readHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + // udiReplyTimeout := 1 * 1000 * 1000, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 7b978176-d206-4509-b9d8-7908d33a9a74_Impl_LineIds + + + + + METHOD processRequest + + 7b978176-d206-4509-b9d8-7908d33a9a74_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83 + 73b10819-9112-4811-9583-e2735d2b13ea + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148422060 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readHoldReq <> 0 THEN + __DELETE(readHoldReq); + readHoldReq := 0; +END_IF +readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters); +recreateRequest := readHoldReq; + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 1004c837-fbef-45ee-986d-977343f4bd8f + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148442038 + + + + + 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)); + nativeProcess(4); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readInputReq) THEN + RETURN; +END_IF + +//Do request processing +readInputReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 1004c837-fbef-45ee-986d-977343f4bd8f_Impl_LineIds + + + + + METHOD processRequest + + 1004c837-fbef-45ee-986d-977343f4bd8f_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605 + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148462074 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readInputReq <> 0 THEN + __DELETE(readInputReq); + readInputReq := 0; +END_IF +readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters); +recreateRequest := readInputReq; + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148542250 + + + + + doneCnt := 0; +doneCntRst := 0; +errorCnt := 0; +errorCntRst := 0; + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + b1508d1f-b7a7-40b5-b74f-3091afc5b834 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setClientOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148552305 + + + + + nativeResponseSeen := FALSE; +doneCntRst := 0; +online := FALSE; +offline := TRUE; + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Impl_LineIds + + + + + METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR + + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setToString + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148572279 + + + + + toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt)))); + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Impl_LineIds + + + + + METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + execute + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043399503 + + + + + PROPERTY execute : BOOL + 89434b6a-7e3b-4095-9220-0a6fc3789a25_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqBusy + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043449644 + + + + + PROPERTY reqBusy : BOOL + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqDone + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043519638 + + + + + PROPERTY reqDone : BOOL + 80965ce1-9b6a-44d3-b272-a5c739ccbc70_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqError + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043604994 + + + + + PROPERTY reqError : BOOL + 30645d91-91c6-42eb-8399-c20f91ca0d3e_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + fbb41e61-f245-4f4e-bcd0-218ede01fcda + f6ee860b-0951-41d0-93d8-6436c681d8f1 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148472030 + + + + + 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 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 + client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(15); + RETURN; +END_IF; +IF NOT setupClient(writeCoilsReq) THEN + RETURN; +END_IF + +//Do request processing +writeCoilsReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Impl_LineIds + + + + + METHOD processRequest + + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + a861d6e2-2501-43dc-bb37-34509923450c + f6ee860b-0951-41d0-93d8-6436c681d8f1 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148502269 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeCoilsReq <> 0 THEN + __DELETE(writeCoilsReq); + writeCoilsReq := 0; +END_IF +writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils); +recreateRequest := writeCoilsReq; + a861d6e2-2501-43dc-bb37-34509923450c_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + a861d6e2-2501-43dc-bb37-34509923450c_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac + f6ee860b-0951-41d0-93d8-6436c681d8f1 + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591456658 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278 + bb7e18ba-2018-4681-875e-de1f291019ef + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148512269 + + + + + 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 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 + client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(16); + RETURN; +END_IF; +IF NOT setupClient(writeHoldReq) THEN + RETURN; +END_IF + +//Do request processing +writeHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Impl_LineIds + + + + + METHOD processRequest + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + bd7ff184-1747-404c-9fa0-1b728376c927 + bb7e18ba-2018-4681-875e-de1f291019ef + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148532258 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeHoldReq <> 0 THEN + __DELETE(writeHoldReq); + writeHoldReq := 0; +END_IF +writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters); +recreateRequest := writeHoldReq; + bd7ff184-1747-404c-9fa0-1b728376c927_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + bd7ff184-1747-404c-9fa0-1b728376c927_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 5f9160f9-c4a4-4f65-a74c-2838020397f3 + bb7e18ba-2018-4681-875e-de1f291019ef + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591496600 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590675071 + + + + + IF nativeMode THEN + activeIndex := nativeActiveIndex; + RETURN; +END_IF; +// ===== Test for any requests executing ===== +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN + activeIndex := i; + RETURN; + END_IF +END_FOR +activeIndex := -1; + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Decl_LineIds + + + True + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + 3b82f264-e41e-461d-9beb-263305a22a8d + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590705101 + + + + + // set request to active, which is how the active index gets calculated. +requests[activeIndex]^.stats.execute := TRUE; + 3b82f264-e41e-461d-9beb-263305a22a8d_Impl_LineIds + + + + + VAR +END_VAR + + 3b82f264-e41e-461d-9beb-263305a22a8d_Decl_LineIds + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + cc024b23-e5fe-4958-ad80-513feea6aeab + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146383170 + + + + + cnt := 0; +IF requestCnt = 0 THEN + activeReqCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.active THEN + cnt := cnt + 1; + END_IF +END_FOR; +activeReqCnt := cnt; + cc024b23-e5fe-4958-ad80-513feea6aeab_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + cc024b23-e5fe-4958-ad80-513feea6aeab_Decl_LineIds + + + True + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeReqCnt + + -1 + + + False + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e + 4d9d378a-f256-4f23-b835-539ee9e287c3 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147990941 + + + + + IF requestCnt = 0 THEN + allRequestsComplete := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN + allRequestsComplete := FALSE; + RETURN; + END_IF +END_FOR; +allRequestsComplete := TRUE; + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Impl_LineIds + + + + + VAR + i: UINT; +END_VAR + + + + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Decl_LineIds + + + True + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + allRequestsComplete + + -1 + + + False + + dceb6b17-86bf-4543-9652-2fd2b943953c + 89742962-f9b6-46c2-b9a5-b7e298f8495d + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146538436 + + + + + cnt := 0; +IF requestCnt = 0 THEN + onlineCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.online THEN + cnt := cnt + 1; + END_IF +END_FOR; +onlineCnt := cnt; + dceb6b17-86bf-4543-9652-2fd2b943953c_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + dceb6b17-86bf-4543-9652-2fd2b943953c_Decl_LineIds + + + True + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + onlineCnt + + -1 + + + False + + 0006696f-29e2-441e-a325-e3481d025cec + 27292866-0bc0-4848-a965-64c51a0bf960 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590890993 + + + + + // ===== Test for any requests executing ===== +requestExecuting := activeIndex <> -1; + 0006696f-29e2-441e-a325-e3481d025cec_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0006696f-29e2-441e-a325-e3481d025cec_Decl_LineIds + + + True + + 27292866-0bc0-4848-a965-64c51a0bf960 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + requestExecuting + + -1 + + + False + + 3d4da579-672d-4758-9b0f-e055a08e0da3 + e8c6c80b-7bdb-4108-987e-4af107de4377 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650278010 + + + + + //Calculation for tostring +IF onlineCnt = requestCnt THEN + toString := atMbClientToString.allOk; +ELSIF onlineCnt = 0 THEN + toString := atMbClientToString.noneOk; +ELSE + toString := atMbClientToString.someOk; +END_IF + 3d4da579-672d-4758-9b0f-e055a08e0da3_Impl_LineIds + + + + + + 3d4da579-672d-4758-9b0f-e055a08e0da3_Decl_LineIds + + + True + + e8c6c80b-7bdb-4108-987e-4af107de4377 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + toString + + -1 + + + False + + 4895a5bf-c631-4f8c-bee7-addb0b769548 + 6159c037-e399-466d-8108-0c4f6570f9b2 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650328003 + + + + + IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN + RETURN; +END_IF + +mbsClient REF= client.client; + 4895a5bf-c631-4f8c-bee7-addb0b769548_Impl_LineIds + + + + + VAR +END_VAR + + 4895a5bf-c631-4f8c-bee7-addb0b769548_Decl_LineIds + + + True + + 6159c037-e399-466d-8108-0c4f6570f9b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + mbsClient + + -1 + + + False + + f54a4cd6-8540-4892-a914-4aac4f8d9018 + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591226460 + + + + + +IF in_timeout > 0 THEN + //Explicitly set timout + replyTimeout := ANY_TO_UDINT((in_timeout) * 1000); +ELSE + // If client timeout is set, use that. + IF client.in_timeout <> T#0MS THEN + replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000; + ELSIF client.dataRate <> 0 THEN + //Data transmision time * 1.5 to get the timeout. Result is in ms + //8 byte request, then 255 byte reply = 2630 bits. + replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000); + ELSE + //In case client datarate = zero + replyTimeout := 1000000; //1 second? + END_IF + + +END_IF + + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Impl_LineIds + + + + + VAR +END_VAR + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Decl_LineIds + + + True + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + replyTimeout + + -1 + + + False + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7 + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043419500 + + + + + execute := _execute; + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Impl_LineIds + + + + + VAR +END_VAR + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Decl_LineIds + + + True + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + 8ff5fbe7-c047-436d-923c-942d0d66494c + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043439650 + + + + + IF reqDone OR reqError THEN + _execute := FALSE; + RETURN; +END_IF +_execute := execute; + 8ff5fbe7-c047-436d-923c-942d0d66494c_Impl_LineIds + + + + + VAR +END_VAR + + 8ff5fbe7-c047-436d-923c-942d0d66494c_Decl_LineIds + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + db10f23e-d96b-4fd5-aec0-8b530ad288e1 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043479636 + + + + + reqBusy := _reqBusy; + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Impl_LineIds + + + + + VAR +END_VAR + + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Decl_LineIds + + + True + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + a239da3a-8f75-41da-a264-f06ca07143f4 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043499661 + + + + + _reqBusy := reqBusy; + a239da3a-8f75-41da-a264-f06ca07143f4_Impl_LineIds + + + + + VAR +END_VAR + + a239da3a-8f75-41da-a264-f06ca07143f4_Decl_LineIds + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + 61db378f-6029-4712-b562-fea707e5698b + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043539625 + + + + + reqDone := _reqDone; + 61db378f-6029-4712-b562-fea707e5698b_Impl_LineIds + + + + + VAR +END_VAR + + 61db378f-6029-4712-b562-fea707e5698b_Decl_LineIds + + + True + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3 + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147505410 + + + + + _reqDone := reqDone; + +IF (execute AND reqDone) THEN + // ===== Request has just completed succesfully ===== + //Update last valid duration + lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + IF lastValidDuration > longestValidDuration THEN + longestValidDuration := lastValidDuration; + END_IF + //Counters + doneCnt := doneCnt + 1; + doneCntRst := doneCntRst + 1; + errorCntRst := 0; + + //Update error info + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + + execute := FALSE; + setToString(); +END_IF + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Impl_LineIds + + + + + VAR +END_VAR + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Decl_LineIds + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + e44e83bd-8f84-4202-9ab4-784d09cf5506 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043645000 + + + + + reqError := _reqError; + e44e83bd-8f84-4202-9ab4-784d09cf5506_Impl_LineIds + + + + + VAR +END_VAR + + e44e83bd-8f84-4202-9ab4-784d09cf5506_Decl_LineIds + + + True + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + 89b5f7bc-0c37-43f3-8984-14034b000d89 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147595513 + + + + + _reqError := reqError; + +IF (execute AND reqError) THEN + // ===== Request has just completed with error ===== + // Counters + errorCnt := errorCnt + 1; + errorCntRst := errorCntRst + 1; + doneCntRst := 0; + //Turn off execute + execute := FALSE; + + //Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. + IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + timeoutCnt := timeoutCnt + 1; + lastExceptionCode := atMBExceptionCodes.TIMEOUT; + END_IF + setToString(); +END_IF + 89b5f7bc-0c37-43f3-8984-14034b000d89_Impl_LineIds + + + + + VAR +END_VAR + + 89b5f7bc-0c37-43f3-8984-14034b000d89_Decl_LineIds + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + f0be8440-a307-5b71-82c8-af27d8852e42 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCall + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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); + f0be8440-a307-5b71-82c8-af27d8852e42_Impl_LineIds + + + + + METHOD nativeCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR + f0be8440-a307-5b71-82c8-af27d8852e42_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 1233447d-8796-56cf-9291-c959801d2a63 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCycle + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 1233447d-8796-56cf-9291-c959801d2a63_Impl_LineIds + + + + + METHOD nativeCycle +VAR + j : DINT; + best : DINT; + priority : INT; + age : UDINT; + rc : UDINT; +END_VAR + 1233447d-8796-56cf-9291-c959801d2a63_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + c08712ba-3cf5-54e9-89a1-76a1c3183975 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + nativeRelease + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + c08712ba-3cf5-54e9-89a1-76a1c3183975_Impl_LineIds + + + + + METHOD nativeRelease : BOOL +VAR + j : DINT; +END_VAR + c08712ba-3cf5-54e9-89a1-76a1c3183975_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + release + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + in_enable := FALSE; +release := nativeRelease(); + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Impl_LineIds + + + + + METHOD release : BOOL + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_exit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + FB_exit := nativeRelease(); + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Impl_LineIds + + + + + METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 7568f58a-e795-5d22-a622-3f070deb32b0 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 7568f58a-e795-5d22-a622-3f070deb32b0_Impl_LineIds + + + + + METHOD FB_reinit : BOOL +VAR + j : DINT; +END_VAR + 7568f58a-e795-5d22-a622-3f070deb32b0_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 28ccddd0-4531-5168-aa3e-ac56dddcb68d + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + invalidateNativeData + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + out_validData := FALSE; +out_stale := TRUE; + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Impl_LineIds + + + + + METHOD PUBLIC invalidateNativeData + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeFinish + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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(); + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Impl_LineIds + + + + + METHOD nativeFinish +VAR_INPUT + result : UDINT; +END_VAR +VAR + mapped : ModbusFB.Error; + ex : ModbusFB.ExceptionCodes; + duration : UDINT; +END_VAR + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + fd4b7cdb-3c7f-5110-af32-341f71321132 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeProcess + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + fd4b7cdb-3c7f-5110-af32-341f71321132_Impl_LineIds + + + + + METHOD nativeProcess +VAR_INPUT + functionCode : BYTE; +END_VAR + fd4b7cdb-3c7f-5110-af32-341f71321132_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 9ee1081f-903d-595c-ab0c-17c64f4fbe75 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + nativeInFlight := FALSE; +stats.execute := FALSE; +stats.reqBusy := FALSE; +stats.reqDone := FALSE; +stats.reqError := FALSE; +FB_reinit := TRUE; + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Impl_LineIds + + + + + METHOD FB_reinit : BOOL + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + CODESYS V3.5 SP21 Patch 4 + + + \ No newline at end of file diff --git a/compat/AtomModbus-Compatible-Fixed.export b/compat/AtomModbus-Compatible-Fixed.export new file mode 100644 index 0000000..16494ef --- /dev/null +++ b/compat/AtomModbus-Compatible-Fixed.export @@ -0,0 +1,5519 @@ + + + + + + + True + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + 00000000-0000-0000-0000-000000000000 + 6-Comms + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + e59dc300-e37a-4c37-997b-021563304d7e + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590379670 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + e59dc300-e37a-4c37-997b-021563304d7e + + atomLibrary + + -1 + + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + 00000000-0000-0000-0000-000000000000 + ModbusMaster + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590449813 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + atomLibrary + 6-Comms + + -1 + + + False + + f228f401-6c74-4359-8ff1-b15b675180a4 + 00000000-0000-0000-0000-000000000000 + atMbClientToString + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591030946 + + + + + {attribute 'qualified_only'} +{attribute 'strict'} +TYPE atMbClientToString : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE + + f228f401-6c74-4359-8ff1-b15b675180a4_Decl_LineIds + + + 11 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7 + 00000000-0000-0000-0000-000000000000 + atMBExceptionCodes + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590519807 + + + + + {attribute 'qualified_only'} +// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes. +TYPE atMBExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7_Decl_LineIds + + + 78 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + 00000000-0000-0000-0000-000000000000 + atMbClient + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639251309599709549 + + + None + + + IF nativeMode THEN + nativeCycle(); + RETURN; +END_IF; +//Modbus connection +//Connects to a modbus slave +//2024-11-23 ATM Cleaned up code +//2024-12-23 CWD Add statistics +//2025-06-18 CWD Add Get priorities and interval working together +//2026-03-26 ATM Clean up code, more testing +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +// ===== Main function call ===== + +IF (in_resetStats) THEN + resetStats(); + in_resetStats := FALSE; +END_IF + +// ===== Connect and disconnect logic ===== + +//Timer to trigger a connection if not connected for to long +reconnectTimer( + IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError, + PT := RECONNECT_DELAY +); + +//Timer to trigger a disconnection if connect fails +disconnectTimer( + IN:=client.xConnect AND NOT client.xConnected, + PT:=DISCONNECT_DELAY +); + +//All requests offline timer +allRequestsOfflineTON( + IN := allRequestsOffline AND client.xConnected, + PT := ALL_OFFLINE_DELAY +); + +//Destroy Client delay timer +recreateClientDly( + IN:=destroyClient, + PT:=RECREATE_CLIENT_DELAY +); + +IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN + //All requests are offline + //Connection attempt failed so disconnect + //If Client has error then disconnect + connect := FALSE; + recreateTest := FALSE; + destroyClient := TRUE; + setAllRequestsOffline(); +ELSIF reconnectTimer.Q AND NOT destroyClient THEN +//If not connected then attempt connect + connect := TRUE; + connectCnt := connectCnt + 1; //for debugging +END_IF; + + +IF recreateClientDly.Q THEN + destroyClient := FALSE; + recreateClient(); +END_IF; + +// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect). +forceConnect( + in := connect <> client.xConnect, + pt := FORCE_RECONNECT_DELAY +); + +// Only flip status of xConnect if no current request is active. +IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN + client.xConnect := connect; +END_IF + +// Moved this lower down, so xConnect happens one scan sooner. +client(); + +// If there are no requests, dont neeed to even do anything. +IF requestCnt = 0 THEN + numMsgSent := 0; + numMsgReply := 0; + allRequestsOnline := 0; + allRequestsOffline := 0; + RETURN; +END_IF + + +requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary + +// ===== Execute next request ===== +IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN + + //Find the lowest priority of overdue requests + nextIdx := -1; + priorityLowest := PRIORITY_NONE; + highestTimeSinceRun := 0; + FOR i := 0 TO requestCnt - 1 DO + IF (requests[i]^.in_priorityCurrent < priorityLowest + OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun)) + AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN + priorityLowest := requests[i]^.in_priorityCurrent; + highestTimeSinceRun := requests[i]^.stats.sinceLastExec; + nextIdx := i; + END_IF + END_FOR + + //Decriment all request priorities by one. + IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority + FOR i := 0 TO requestCnt - 1 DO + requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1; + IF requests[i]^.in_priorityCurrent < 0 THEN + requests[i]^.in_priorityCurrent := 0; + END_IF + END_FOR + END_IF + + IF nextIdx >= 0 THEN + activeIndex := nextIdx; + END_IF + END_IF + +processAllRequests(); + +allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt); +allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0); + +// ===== Client statistics ===== +//Get a sum of done and error counts from all requests +reqErrSum := 0; +reqDoneSum := 0; +FOR i := 0 TO requestCnt -1 DO + reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt; + reqErrSum := reqErrSum + requests[i]^.stats.errorCnt; + + //copy stats to array for debugging + reqStats[i] := ADR(requests[i]^.stats); +END_FOR + + + +//Every time the tx message counter changes increment msg count and add last transaction time +IF client.udiNumMsgSent <> msgCountLast THEN + msgCountLast := client.udiNumMsgSent; + msgTimeSum := msgTimeSum + client.udiLastTransactionTime; + msgCountSum := msgCountSum + 1; +END_IF + +//Every 1 second compute statistics +pulse(); +IF pulse.T_Sec_OS THEN + msgLastSecondCnt := msgCountSum; + msgCountSum := 0; + msgLastSecondTime := msgTimeSum; + msgTimeSum := 0; +END_IF + +numMsgSent := client.udiNumMsgSent; +numMsgReply := client.udiNumMsgReply; + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + //{attribute 'symbol' := 'none'} + reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests +END_VAR +VAR + {attribute 'symbol' := 'none'} + datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits + {attribute 'symbol' := 'none'} + client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set. + {attribute 'symbol' := 'none'} + connectCnt: DINT; //Number of connection attempts + {attribute 'symbol' := 'none'} + initDone: BOOL; + {attribute 'symbol' := 'none'} + disconnectTimer: TON; //Connecting but not connected delay + {attribute 'symbol' := 'none'} + reconnectTimer: TON; //Not connecting and not connected delay + {attribute 'symbol' := 'none'} + msgCountLast: UDINT; //Last message count + {attribute 'symbol' := 'none'} + msgCountSum: UDINT; //Message count accumulator + {attribute 'symbol' := 'none'} + msgTimeSum: UDINT; //Amount of time for messsages accumulator + {attribute 'symbol' := 'none'} + pulse: atPulse; //Pulse generator + {attribute 'symbol' := 'none'} + requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct + {attribute 'symbol' := 'none'} + requestCnt: UINT; //Number of active requests + {attribute 'symbol' := 'none'} + reqDoneSum: UDINT; //Count of done result on all requests + {attribute 'symbol' := 'none'} + reqErrSum: UDINT; //Count of error result on all requests + {attribute 'symbol' := 'none'} + i: DINT; //Loop variable + {attribute 'symbol' := 'none'} + nextIdx: DINT := -1; //Index of the active request + {attribute 'symbol' := 'none'} + priorityLowest: INT; //lowest request priority + {attribute 'symbol' := 'none'} + highestTimeSinceRun: UDINT; //lowest request priority + {attribute 'symbol' := 'none'} + allRequestsOfflineTON: TON; //All requests are offline delay + {attribute 'symbol' := 'none'} + requestNotExecutingTON: TON; //Delay after there are no requests execting + {attribute 'symbol' := 'none'} + recreateClientDly: TON; //Delay to make new client after force fail + {attribute 'symbol' := 'none'} + allRequestsOffline: BOOL; //All requests are offline + {attribute 'symbol' := 'none'} + connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true. + {attribute 'symbol' := 'none'} + forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect. + test1: REAL; + test2: REAL; + destroyClient: BOOL; + recreateTest: BOOL; +END_VAR +VAR CONSTANT + {attribute 'symbol' := 'none'} + NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay + {attribute 'symbol' := 'none'} + RECONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + DISCONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + RECREATE_CLIENT_DELAY: TIME := T#1000MS; + {attribute 'symbol' := 'none'} + ALL_OFFLINE_DELAY: TIME := T#2S; + {attribute 'symbol' := 'none'} + FORCE_RECONNECT_DELAY: TIME := T#1S; + {attribute 'symbol' := 'none'} + PRIORITY_NONE: INT := 32767; +END_VAR + +VAR +nativeMode : BOOL; +nativeHandle : UDINT; +nativeIP : DWORD; +nativePort : UINT; +nativeNowUs : SYSTIME; +nativeNowMs : ULINT; +nativeStatus : ARRAY[0..23] OF UDINT; +nativeActiveIndex : DINT := -1; +nativeSelected : POINTER TO atMbReqBase; +nativeResultReady : BOOL; +nativeFinishedIndex : DINT := -1; +nativeCallResult : UDINT; +nativeWindowStart : ULINT; +nativePrevConnected : BOOL; +nativeEnable : BOOL := TRUE; +nativeConnectTimeout : UDINT := 2000; +nativeRetryDelay : UDINT := 1000; +nativeHoldUncertainWrites : BOOL := FALSE; +END_VAR +VAR_INPUT +{attribute 'symbol' := 'none'} +nativeIO : ARRAY[0..1999] OF WORD; +{attribute 'symbol' := 'none'} +nativeActiveRequest : POINTER TO atMbReqBase; +{attribute 'symbol' := 'none'} +nativeArgUnit : BYTE; +{attribute 'symbol' := 'none'} +nativeArgFunction : BYTE; +{attribute 'symbol' := 'none'} +nativeArgAddress : UINT; +{attribute 'symbol' := 'none'} +nativeArgQuantity : UINT; +{attribute 'symbol' := 'none'} +nativeArgTimeout : UDINT := 1000; +{attribute 'symbol' := 'none'} +nativeSent : UDINT; +{attribute 'symbol' := 'none'} +nativeReplied : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowCount : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowTime : UDINT; +END_VAR + +VAR_OUTPUT +out_connected : BOOL; +out_connectionState : UDINT; +out_lastNativeError : UDINT; +out_socketError : UDINT; +out_reconnectCount : UDINT; +out_registrationFull : BOOL; +END_VAR + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Decl_LineIds + + + 3369 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + 00000000-0000-0000-0000-000000000000 + atMbClientRTU + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649591897 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-13 CWD Fix problem with not connecting properly on power up +//2024-11-15 ATM Move from structure to defaults. +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +dataRate := in_baudRate; + + +// ===== Initialization ===== +// if the param changes, force a reinit +IF _client^.iPort <> in_port + OR _client^.dwBaudrate <> in_baudRate + OR _client^.byDataBits <> in_dataBits + OR _client^.eParity <> in_parity + OR _client^.eStopBits <> in_stopBits THEN + initDone := FALSE; +END_IF; + +IF NOT initDone THEN + initDone := TRUE; + + //Other configuration parameters + _client^.iPort := in_port; + _client^.dwBaudrate := in_baudRate; + _client^.byDataBits := in_dataBits; + _client^.eParity := in_parity; + _client^.eStopBits := in_stopBits; + _client^.eDTRcontrol := 0; + _client^.eRTScontrol := 0; + _client^.eRTUASCII := 0; + _client^.udiLogOptions := ModbusFB.LoggingOptions.All; + _client^.xConnect:=FALSE; + +END_IF + +SUPER^(); + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_port: BYTE := 0; //0=None, 1,2,3,4 + {attribute 'symbol' := 'none'} + in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200 + {attribute 'symbol' := 'none'} + in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even + {attribute 'symbol' := 'none'} + in_dataBits: BYTE := 8; //8=8 + {attribute 'symbol' := 'none'} + in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientSerial; +END_VAR + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Decl_LineIds + + + 607 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + 00000000-0000-0000-0000-000000000000 + atMbClientTCP + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649747183 + + + None + + + 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^(); + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientTCP; +END_VAR +VAR_INPUT +in_enable : BOOL := TRUE; +in_connectTimeout : TIME := T#2S; +in_reconnectDelay : TIME := T#1S; +in_holdUncertainWrites : BOOL := FALSE; +END_VAR + +VAR +nativeEndpointIP : DWORD; +nativeEndpointPort : UINT; +nativeConfigConnect : UDINT; +nativeConfigRetry : UDINT; +END_VAR + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Decl_LineIds + + + 1168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + 00000000-0000-0000-0000-000000000000 + atMbReqBase + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649837310 + + + None + + + //Modbus request +//Makes a request using the start and quantity, puts the result into data +//2024-06-27 CWD Initial create +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage + +IF NOT __ISVALIDREF(client) THEN + RETURN; +END_IF + + +//Add this request to the clients request array +client.addRequest(THIS); + +//fault logic +//Fault if error +//errTrig(CLK := stats.reqError); +//IF errTrig.Q THEN +// out_fault := TRUE; +//END_IF +//not faulted if a succesfull request +//doneTrig(CLK := stats.reqDone); +// also clear fault if not active +//IF doneTrig.Q OR NOT stats.active THEN +// out_fault := FALSE; +//END_IF + +IF in_quantity <= dataSize THEN + stats.reqLength := in_quantity; +ELSE + stats.reqLength := dataSize; +END_IF; + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbReqBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO atMbClient; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: atMbRequestStats; //Request stats + +END_VAR +VAR_OUTPUT + //{attribute 'symbol' := 'none'} + //out_fault: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataSize: UINT; + //{attribute 'symbol' := 'none'} + //quantUsed: UINT; + //{attribute 'symbol' := 'none'} + //doneTrig: R_TRIG; //Succesful request trigger + //{attribute 'symbol' := 'none'} + //errTrig: R_TRIG; //Error request result trigger + + {attribute 'symbol' := 'none'} + request: REFERENCE TO ModbusFB.ClientRequest;; +END_VAR + + +VAR +nativeFunction : BYTE; +nativeCount : UINT; +nativeOffset : UINT; +nativeInFlight : BOOL; +nativeReadComplete : BOOL; +nativeCopyAllowed : BOOL; +nativeWriteHold : BOOL; +nativeStartMs : ULINT; +nativeLoop : DINT; +nativeRC : UDINT; +END_VAR + +VAR_OUTPUT +out_nativeError : UDINT; +out_socketError : UDINT; +out_writeUncertain : BOOL; +out_validData : BOOL; +out_dataAgeMs : ULINT; +out_stale : BOOL := TRUE; +out_parameterError : BOOL; +END_VAR + +VAR_INPUT +in_staleAfter : TIME := T#2S; +END_VAR + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Decl_LineIds + + + 3210 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + 00000000-0000-0000-0000-000000000000 + atMbReqReadCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146913881 + + + None + + + 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; +//Modbus request input registers +//2024-11-16 CWD Added read coil +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils; +END_VAR + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Decl_LineIds + + + 193 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 1a3278f4-a658-407c-968a-21aa53361562 + 00000000-0000-0000-0000-000000000000 + atMbReqReadDiscrete + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147014015 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 1a3278f4-a658-407c-968a-21aa53361562_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs; +END_VAR + + 1a3278f4-a658-407c-968a-21aa53361562_Decl_LineIds + + + 184 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 73b10819-9112-4811-9583-e2735d2b13ea + 00000000-0000-0000-0000-000000000000 + atMbReqReadHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147099228 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 73b10819-9112-4811-9583-e2735d2b13ea_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters; +END_VAR + + 73b10819-9112-4811-9583-e2735d2b13ea_Decl_LineIds + + + 173 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + 00000000-0000-0000-0000-000000000000 + atMbReqReadInput + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147179217 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters; +END_VAR + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Decl_LineIds + + + 167 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + 00000000-0000-0000-0000-000000000000 + atMbRequestStats + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147425423 + + + None + + + SysTimeGetUs(TSSus); + +IF (reqLength = 0) THEN + active := FALSE; + doneCnt := 0; + errorCnt := 0; + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + execute := FALSE; + online := FALSE; + offline := FALSE; + reqDone := FALSE; + reqBusy := FALSE; + reqError := FALSE; + sinceLastExec := 0; + RETURN; +END_IF +active := TRUE; + +inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn); +reqDoneLight := inDoneDeb.out_val; + +inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn); +reqBusyLight := inBusyDeb.out_val; + +inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn); +reqErrorLight := inErrDeb.out_val; + +//Debounced execute indicator for debbugging +executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn); +executeLight := executeDeb.out_val; + + +//Set online status +IF nativeResponseSeen OR doneCntRst >=3 THEN + online := TRUE; + offline := FALSE; +END_IF + +//Set online status +IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN + online := FALSE; + offline := TRUE; +END_IF + +//Record start time in ms on execute rising edge +executeTrig(CLK := execute); +IF executeTrig.Q THEN + interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + lastReqStart := TSSus; +END_IF + +//Offline count +offlineTrig(CLK := NOT online); +IF offlineTrig.Q THEN + offlineCnt := offlineCnt + 1; +END_IF + +//Calculate ms since last execute +//If the last request start is more then 1 minute ago then set it for 1 minute +IF TSSus >= lastReqStart THEN + IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF; +ELSE lastReqStart := TSSus; +END_IF; +sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK atMbRequestStats +VAR_INPUT + lastErrorId: modbusFB.Error; //Last error id + lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + inDoneDeb: atDebounce; + {attribute 'symbol' := 'none'} + inErrDeb: atDebounce; + {attribute 'symbol' := 'none'} + inBusyDeb: atDebounce; + {attribute 'symbol' := 'none'} + executeDeb: atDebounce; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastReqStart: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + TSSus: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + lightOn: TIME:= T#100MS; //Light on time for debug indicators +END_VAR +VAR_INPUT +nativeResponseSeen : BOOL; +nativeLastSuccessMs : ULINT; +END_VAR + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Decl_LineIds + + + 286 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + 00000000-0000-0000-0000-000000000000 + atMbReqWriteCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147259331 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + f6ee860b-0951-41d0-93d8-6436c681d8f1_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils; +END_VAR + + f6ee860b-0951-41d0-93d8-6436c681d8f1_Decl_LineIds + + + 206 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bb7e18ba-2018-4681-875e-de1f291019ef + 00000000-0000-0000-0000-000000000000 + atMbReqWriteHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147339348 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + bb7e18ba-2018-4681-875e-de1f291019ef_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters; +END_VAR + + bb7e18ba-2018-4681-875e-de1f291019ef_Decl_LineIds + + + 164 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9499c60e-03a2-49d0-b086-43990a018e20 + 00000000-0000-0000-0000-000000000000 + To_IPAddress + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590489804 + + + None + + + //Convert parameters to array +To_IPAddress[0] := byte1; +To_IPAddress[1] := byte2; +To_IPAddress[2] := byte3; +To_IPAddress[3] := byte4; + + + + 9499c60e-03a2-49d0-b086-43990a018e20_Impl_LineIds + + + + + FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR + + 9499c60e-03a2-49d0-b086-43990a018e20_Decl_LineIds + + + 168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9e99e368-853f-485b-911f-d9a2a4e46434 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + addRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146468437 + + + + + 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; + 9e99e368-853f-485b-911f-d9a2a4e46434_Impl_LineIds + + + + + //Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO atMbReqBase; +END_VAR +VAR + i: UINT := 0; + +END_VAR + + 9e99e368-853f-485b-911f-d9a2a4e46434_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 685f7c69-c46a-4b34-b5e2-fb848c593f3e + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + processAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148036583 + + + + + FOR i := 0 TO requestCnt -1 DO + requests[i]^.processRequest(); +END_FOR; + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Impl_LineIds + + + + + METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + i: UINT; // Loop bstisnlr +END_VAR + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 39769d9e-08c1-472d-82ef-25c66891a065 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148056585 + + + + + FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + requests[i]^.recreateRequest(); +END_FOR; + 39769d9e-08c1-472d-82ef-25c66891a065_Impl_LineIds + + + + + METHOD PROTECTED recreateAllRequests +VAR_INPUT +END_VAR + + 39769d9e-08c1-472d-82ef-25c66891a065_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648119824 + + + + + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Impl_LineIds + + + + + METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + a7e27967-573f-4f9f-86e7-dd569b8cd943 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; +connectCnt := 0; +val := ADR(client.udiNumMsgSent); +val^ := 0; +val := ADR(client.udiNumMsgReply); +val^ := 0; +FOR i := 0 TO requestCnt -1 DO + requests[i]^.stats.resetStats(); +END_FOR; + a7e27967-573f-4f9f-86e7-dd569b8cd943_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR +VAR + i: DINT; + val: POINTER TO UDINT; +END_VAR + + + + + a7e27967-573f-4f9f-86e7-dd569b8cd943_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + ca1dfc70-ad93-43de-886d-39e56897aa6d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setAllRequestsOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148106585 + + + + + FOR i := 0 TO requestCnt -1 DO + requests[i]^.stats.setClientOffline(); +END_FOR; + ca1dfc70-ad93-43de-886d-39e56897aa6d_Impl_LineIds + + + + + METHOD PROTECTED setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + i: DINT; +END_VAR + + + + + ca1dfc70-ad93-43de-886d-39e56897aa6d_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + d12c9bf2-320f-4e95-badb-68a2357c5783 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648239962 + + + + + IF nativeMode THEN + setupClient := nativeHandle <> 0; + RETURN; +END_IF; +// Only create request if doesnt exist +IF NOT __ISVALIDREF(client) THEN + recreateClient(); + IF NOT __ISVALIDREF(client) THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +setupClient := TRUE; + d12c9bf2-320f-4e95-badb-68a2357c5783_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT +END_VAR + + d12c9bf2-320f-4e95-badb-68a2357c5783_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeIndex + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590625089 + + + + + PROPERTY activeIndex : DINT + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeReqCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502146293051 + + + + + PROPERTY PRIVATE activeReqCnt : UINT + 456b4953-57c9-4f76-ab5b-0db456dd9b58_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + allRequestsComplete + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502147921016 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL + 4d9d378a-f256-4f23-b835-539ee9e287c3_Decl_LineIds + + + 2 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + onlineCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590755417 + + + + + {attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online + 89742962-f9b6-46c2-b9a5-b7e298f8495d_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 27292866-0bc0-4848-a965-64c51a0bf960 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + requestExecuting + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590845466 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing + 27292866-0bc0-4848-a965-64c51a0bf960_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4950a3a1-f54f-434d-9252-b61a6cb4663d + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649617058 + + + + + old_client_id := _client; +_client := __NEW(ModbusFB.ClientSerial); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Impl_LineIds + + + + + METHOD recreateClient +VAR_INPUT + old_client_id: POINTER TO ModbusFB.ClientSerial; +END_VAR + + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Decl_LineIds + + + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + e8c6c80b-7bdb-4108-987e-4af107de4377 + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + toString + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650142720 + + + + + {attribute 'symbol' := 'none'} +PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working + e8c6c80b-7bdb-4108-987e-4af107de4377_Decl_LineIds + + + 3 + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + 79dfe91d-6dbc-46b0-866c-4b880e44e02b + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649757233 + + + + + nativeRelease(); +// Next scan creates a fresh native context with current endpoint settings. + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Impl_LineIds + + + + + METHOD recreateClient +VAR +END_VAR + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + busy + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148176670 + + + + + stats.reqBusy := in; + +IF NOT in THEN + RETURN; +END_IF; + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Impl_LineIds + + + + + METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +VAR + executeTime: ULINT; + replyTimeoutTime: ULINT; +END_VAR + + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 5350a8fa-8297-42db-b185-fcb966e45684 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + completeRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148196661 + + + + + done(request.xDone); +err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException); +busy(in := request.xBusy); +stats(); + +IF (atGVL.reset) THEN + // request.AbortModel(); + request.ResetModel(TRUE); +END_IF + 5350a8fa-8297-42db-b185-fcb966e45684_Impl_LineIds + + + + + METHOD completeRequest +VAR_INPUT +END_VAR + + 5350a8fa-8297-42db-b185-fcb966e45684_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + done + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148206667 + + + + + stats.reqDone := in; + +IF NOT in THEN + RETURN; +END_IF + +//Set priority for next request +in_priorityCurrent := in_prioritySetpoint; + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Impl_LineIds + + + + + METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + err + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148226656 + + + + + stats.reqError := in; + +IF NOT in THEN + RETURN; +END_IF + + +//Update error info +stats.lastErrorId := errorId; +stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode); + +//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. +//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + //Since there was no response from the device set the priority to higher number priority +// in_priorityCurrent := in_prioritySetPoint * 5; +//END_IF + +//Set priority for next request, priority gets higher after each error result +priorityMultiplier := UDINT_TO_INT(stats.errorCntRst); +IF priorityMultiplier > 3 THEN + priorityMultiplier := 3; +END_IF +// set multiplier to exponential retry. +in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier; +//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier; + +// NO idea what Reset Model does, hoping it fixes comms issues +// request.ResetModel(TRUE); // I dont think this helped + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Impl_LineIds + + + + + METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: modbusFB.Error; //modbusFB error ID + exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 109d8e00-bf23-4306-a8d5-764aa6a03235 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148246723 + + + + + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Impl_LineIds + + + + + METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148266663 + + + + + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Impl_LineIds + + + + + METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + db40c392-b461-4198-851d-85857ed98ccf + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148276644 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + setupClient := FALSE; // Native path has no ModbusFB request object. + RETURN; +END_IF; +// Only create request if doesnt exist +IF in_request = 0 THEN + in_request := recreateRequest(); + IF in_request = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +IF NOT __ISVALIDREF(mbsClient) THEN + setupClient := FALSE; + RETURN; +END_IF + +request REF= in_request^; + +setupClient := TRUE; +RETURN; + db40c392-b461-4198-851d-85857ed98ccf_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT + in_request: POINTER TO ModbusFB.ClientRequest; +END_VAR + + db40c392-b461-4198-851d-85857ed98ccf_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 6159c037-e399-466d-8108-0c4f6570f9b2 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + mbsClient + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650298004 + + + + + PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client + 6159c037-e399-466d-8108-0c4f6570f9b2_Decl_LineIds + + + 1 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + replyTimeout + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591196333 + + + + + //Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT + 4f361885-f95c-4244-84d6-8c3b6b9b8a18_Decl_LineIds + + + 2 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + ea5277e9-2ae8-417f-a88d-b571902c7d71 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699321592 + + + + + 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)); + nativeProcess(1); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readCoilReq) THEN + RETURN; +END_IF + +//Do request processing +readCoilReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + ea5277e9-2ae8-417f-a88d-b571902c7d71_Impl_LineIds + + + + + METHOD processRequest + + ea5277e9-2ae8-417f-a88d-b571902c7d71_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148331930 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readCoilReq <> 0 THEN + __DELETE(readCoilReq); + readCoilReq := 0; +END_IF +readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils); +recreateRequest := readCoilReq; + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 9df094f9-a173-4520-816f-7badcbb1bd34 + 1a3278f4-a658-407c-968a-21aa53361562 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699461941 + + + + + 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)); + nativeProcess(2); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readDiscreteReq) THEN + RETURN; +END_IF + +//Do request processing +readDiscreteReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 9df094f9-a173-4520-816f-7badcbb1bd34_Impl_LineIds + + + + + METHOD processRequest + + 9df094f9-a173-4520-816f-7badcbb1bd34_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + d34866d4-1134-4650-b3d6-2e19d2fd09d1 + 1a3278f4-a658-407c-968a-21aa53361562 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148362063 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readDiscreteReq <> 0 THEN + __DELETE(readDiscreteReq); + readDiscreteReq := 0; +END_IF +readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs); +recreateRequest := readDiscreteReq; + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + 7b978176-d206-4509-b9d8-7908d33a9a74 + 73b10819-9112-4811-9583-e2735d2b13ea + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148402056 + + + + + 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)); + nativeProcess(3); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readHoldReq) THEN + RETURN; +END_IF + +//Do request processing +readHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + // udiReplyTimeout := 1 * 1000 * 1000, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 7b978176-d206-4509-b9d8-7908d33a9a74_Impl_LineIds + + + + + METHOD processRequest + + 7b978176-d206-4509-b9d8-7908d33a9a74_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83 + 73b10819-9112-4811-9583-e2735d2b13ea + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148422060 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readHoldReq <> 0 THEN + __DELETE(readHoldReq); + readHoldReq := 0; +END_IF +readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters); +recreateRequest := readHoldReq; + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 1004c837-fbef-45ee-986d-977343f4bd8f + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148442038 + + + + + 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)); + nativeProcess(4); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readInputReq) THEN + RETURN; +END_IF + +//Do request processing +readInputReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 1004c837-fbef-45ee-986d-977343f4bd8f_Impl_LineIds + + + + + METHOD processRequest + + 1004c837-fbef-45ee-986d-977343f4bd8f_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605 + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148462074 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readInputReq <> 0 THEN + __DELETE(readInputReq); + readInputReq := 0; +END_IF +readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters); +recreateRequest := readInputReq; + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148542250 + + + + + doneCnt := 0; +doneCntRst := 0; +errorCnt := 0; +errorCntRst := 0; + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + b1508d1f-b7a7-40b5-b74f-3091afc5b834 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setClientOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148552305 + + + + + nativeResponseSeen := FALSE; +doneCntRst := 0; +online := FALSE; +offline := TRUE; + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Impl_LineIds + + + + + METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR + + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setToString + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148572279 + + + + + toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt)))); + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Impl_LineIds + + + + + METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + execute + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043399503 + + + + + PROPERTY execute : BOOL + 89434b6a-7e3b-4095-9220-0a6fc3789a25_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqBusy + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043449644 + + + + + PROPERTY reqBusy : BOOL + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqDone + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043519638 + + + + + PROPERTY reqDone : BOOL + 80965ce1-9b6a-44d3-b272-a5c739ccbc70_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqError + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043604994 + + + + + PROPERTY reqError : BOOL + 30645d91-91c6-42eb-8399-c20f91ca0d3e_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + fbb41e61-f245-4f4e-bcd0-218ede01fcda + f6ee860b-0951-41d0-93d8-6436c681d8f1 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148472030 + + + + + 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 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 + client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(15); + RETURN; +END_IF; +IF NOT setupClient(writeCoilsReq) THEN + RETURN; +END_IF + +//Do request processing +writeCoilsReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Impl_LineIds + + + + + METHOD processRequest + + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + a861d6e2-2501-43dc-bb37-34509923450c + f6ee860b-0951-41d0-93d8-6436c681d8f1 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148502269 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeCoilsReq <> 0 THEN + __DELETE(writeCoilsReq); + writeCoilsReq := 0; +END_IF +writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils); +recreateRequest := writeCoilsReq; + a861d6e2-2501-43dc-bb37-34509923450c_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + a861d6e2-2501-43dc-bb37-34509923450c_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac + f6ee860b-0951-41d0-93d8-6436c681d8f1 + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591456658 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278 + bb7e18ba-2018-4681-875e-de1f291019ef + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148512269 + + + + + 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 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 + client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(16); + RETURN; +END_IF; +IF NOT setupClient(writeHoldReq) THEN + RETURN; +END_IF + +//Do request processing +writeHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Impl_LineIds + + + + + METHOD processRequest + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + bd7ff184-1747-404c-9fa0-1b728376c927 + bb7e18ba-2018-4681-875e-de1f291019ef + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148532258 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeHoldReq <> 0 THEN + __DELETE(writeHoldReq); + writeHoldReq := 0; +END_IF +writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters); +recreateRequest := writeHoldReq; + bd7ff184-1747-404c-9fa0-1b728376c927_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + bd7ff184-1747-404c-9fa0-1b728376c927_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 5f9160f9-c4a4-4f65-a74c-2838020397f3 + bb7e18ba-2018-4681-875e-de1f291019ef + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591496600 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590675071 + + + + + IF nativeMode THEN + activeIndex := nativeActiveIndex; + RETURN; +END_IF; +// ===== Test for any requests executing ===== +FOR i := 0 TO requestCnt -1 DO + IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN + activeIndex := i; + RETURN; + END_IF +END_FOR +activeIndex := -1; + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Decl_LineIds + + + True + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + 3b82f264-e41e-461d-9beb-263305a22a8d + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590705101 + + + + + // set request to active, which is how the active index gets calculated. +requests[activeIndex]^.stats.execute := TRUE; + 3b82f264-e41e-461d-9beb-263305a22a8d_Impl_LineIds + + + + + VAR +END_VAR + + 3b82f264-e41e-461d-9beb-263305a22a8d_Decl_LineIds + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + cc024b23-e5fe-4958-ad80-513feea6aeab + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146383170 + + + + + cnt := 0; +IF requestCnt = 0 THEN + activeReqCnt := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.active THEN + cnt := cnt + 1; + END_IF +END_FOR; +activeReqCnt := cnt; + cc024b23-e5fe-4958-ad80-513feea6aeab_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + cc024b23-e5fe-4958-ad80-513feea6aeab_Decl_LineIds + + + True + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeReqCnt + + -1 + + + False + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e + 4d9d378a-f256-4f23-b835-539ee9e287c3 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147990941 + + + + + IF requestCnt = 0 THEN + allRequestsComplete := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN + allRequestsComplete := FALSE; + RETURN; + END_IF +END_FOR; +allRequestsComplete := TRUE; + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Impl_LineIds + + + + + VAR + i: UINT; +END_VAR + + + + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Decl_LineIds + + + True + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + allRequestsComplete + + -1 + + + False + + dceb6b17-86bf-4543-9652-2fd2b943953c + 89742962-f9b6-46c2-b9a5-b7e298f8495d + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146538436 + + + + + cnt := 0; +IF requestCnt = 0 THEN + onlineCnt := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.online THEN + cnt := cnt + 1; + END_IF +END_FOR; +onlineCnt := cnt; + dceb6b17-86bf-4543-9652-2fd2b943953c_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + dceb6b17-86bf-4543-9652-2fd2b943953c_Decl_LineIds + + + True + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + onlineCnt + + -1 + + + False + + 0006696f-29e2-441e-a325-e3481d025cec + 27292866-0bc0-4848-a965-64c51a0bf960 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590890993 + + + + + // ===== Test for any requests executing ===== +requestExecuting := activeIndex <> -1; + 0006696f-29e2-441e-a325-e3481d025cec_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0006696f-29e2-441e-a325-e3481d025cec_Decl_LineIds + + + True + + 27292866-0bc0-4848-a965-64c51a0bf960 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + requestExecuting + + -1 + + + False + + 3d4da579-672d-4758-9b0f-e055a08e0da3 + e8c6c80b-7bdb-4108-987e-4af107de4377 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650278010 + + + + + //Calculation for tostring +IF onlineCnt = requestCnt THEN + toString := atMbClientToString.allOk; +ELSIF onlineCnt = 0 THEN + toString := atMbClientToString.noneOk; +ELSE + toString := atMbClientToString.someOk; +END_IF + 3d4da579-672d-4758-9b0f-e055a08e0da3_Impl_LineIds + + + + + + 3d4da579-672d-4758-9b0f-e055a08e0da3_Decl_LineIds + + + True + + e8c6c80b-7bdb-4108-987e-4af107de4377 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + toString + + -1 + + + False + + 4895a5bf-c631-4f8c-bee7-addb0b769548 + 6159c037-e399-466d-8108-0c4f6570f9b2 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650328003 + + + + + IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN + RETURN; +END_IF + +mbsClient REF= client.client; + 4895a5bf-c631-4f8c-bee7-addb0b769548_Impl_LineIds + + + + + VAR +END_VAR + + 4895a5bf-c631-4f8c-bee7-addb0b769548_Decl_LineIds + + + True + + 6159c037-e399-466d-8108-0c4f6570f9b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + mbsClient + + -1 + + + False + + f54a4cd6-8540-4892-a914-4aac4f8d9018 + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591226460 + + + + + +IF in_timeout > 0 THEN + //Explicitly set timout + replyTimeout := ANY_TO_UDINT((in_timeout) * 1000); +ELSE + // If client timeout is set, use that. + IF client.in_timeout <> T#0MS THEN + replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000; + ELSIF client.dataRate <> 0 THEN + //Data transmision time * 1.5 to get the timeout. Result is in ms + //8 byte request, then 255 byte reply = 2630 bits. + replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000); + ELSE + //In case client datarate = zero + replyTimeout := 1000000; //1 second? + END_IF + + +END_IF + + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Impl_LineIds + + + + + VAR +END_VAR + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Decl_LineIds + + + True + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + replyTimeout + + -1 + + + False + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7 + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043419500 + + + + + execute := _execute; + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Impl_LineIds + + + + + VAR +END_VAR + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Decl_LineIds + + + True + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + 8ff5fbe7-c047-436d-923c-942d0d66494c + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043439650 + + + + + IF reqDone OR reqError THEN + _execute := FALSE; + RETURN; +END_IF +_execute := execute; + 8ff5fbe7-c047-436d-923c-942d0d66494c_Impl_LineIds + + + + + VAR +END_VAR + + 8ff5fbe7-c047-436d-923c-942d0d66494c_Decl_LineIds + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + db10f23e-d96b-4fd5-aec0-8b530ad288e1 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043479636 + + + + + reqBusy := _reqBusy; + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Impl_LineIds + + + + + VAR +END_VAR + + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Decl_LineIds + + + True + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + a239da3a-8f75-41da-a264-f06ca07143f4 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043499661 + + + + + _reqBusy := reqBusy; + a239da3a-8f75-41da-a264-f06ca07143f4_Impl_LineIds + + + + + VAR +END_VAR + + a239da3a-8f75-41da-a264-f06ca07143f4_Decl_LineIds + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + 61db378f-6029-4712-b562-fea707e5698b + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043539625 + + + + + reqDone := _reqDone; + 61db378f-6029-4712-b562-fea707e5698b_Impl_LineIds + + + + + VAR +END_VAR + + 61db378f-6029-4712-b562-fea707e5698b_Decl_LineIds + + + True + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3 + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147505410 + + + + + _reqDone := reqDone; + +IF (execute AND reqDone) THEN + // ===== Request has just completed succesfully ===== + //Update last valid duration + lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + IF lastValidDuration > longestValidDuration THEN + longestValidDuration := lastValidDuration; + END_IF + //Counters + doneCnt := doneCnt + 1; + doneCntRst := doneCntRst + 1; + errorCntRst := 0; + + //Update error info + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + + execute := FALSE; + setToString(); +END_IF + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Impl_LineIds + + + + + VAR +END_VAR + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Decl_LineIds + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + e44e83bd-8f84-4202-9ab4-784d09cf5506 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043645000 + + + + + reqError := _reqError; + e44e83bd-8f84-4202-9ab4-784d09cf5506_Impl_LineIds + + + + + VAR +END_VAR + + e44e83bd-8f84-4202-9ab4-784d09cf5506_Decl_LineIds + + + True + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + 89b5f7bc-0c37-43f3-8984-14034b000d89 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147595513 + + + + + _reqError := reqError; + +IF (execute AND reqError) THEN + // ===== Request has just completed with error ===== + // Counters + errorCnt := errorCnt + 1; + errorCntRst := errorCntRst + 1; + doneCntRst := 0; + //Turn off execute + execute := FALSE; + + //Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. + IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + timeoutCnt := timeoutCnt + 1; + lastExceptionCode := atMBExceptionCodes.TIMEOUT; + END_IF + setToString(); +END_IF + 89b5f7bc-0c37-43f3-8984-14034b000d89_Impl_LineIds + + + + + VAR +END_VAR + + 89b5f7bc-0c37-43f3-8984-14034b000d89_Decl_LineIds + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + f0be8440-a307-5b71-82c8-af27d8852e42 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCall + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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); + f0be8440-a307-5b71-82c8-af27d8852e42_Impl_LineIds + + + + + METHOD nativeCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR + f0be8440-a307-5b71-82c8-af27d8852e42_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 1233447d-8796-56cf-9291-c959801d2a63 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCycle + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 1233447d-8796-56cf-9291-c959801d2a63_Impl_LineIds + + + + + METHOD nativeCycle +VAR + j : DINT; + best : DINT; + priority : INT; + age : UDINT; + rc : UDINT; +END_VAR + 1233447d-8796-56cf-9291-c959801d2a63_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + c08712ba-3cf5-54e9-89a1-76a1c3183975 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + nativeRelease + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + c08712ba-3cf5-54e9-89a1-76a1c3183975_Impl_LineIds + + + + + METHOD nativeRelease : BOOL +VAR + j : DINT; +END_VAR + c08712ba-3cf5-54e9-89a1-76a1c3183975_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + release + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + in_enable := FALSE; +release := nativeRelease(); + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Impl_LineIds + + + + + METHOD release : BOOL + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_exit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + FB_exit := nativeRelease(); + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Impl_LineIds + + + + + METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 7568f58a-e795-5d22-a622-3f070deb32b0 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 7568f58a-e795-5d22-a622-3f070deb32b0_Impl_LineIds + + + + + METHOD FB_reinit : BOOL +VAR + j : DINT; +END_VAR + 7568f58a-e795-5d22-a622-3f070deb32b0_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 28ccddd0-4531-5168-aa3e-ac56dddcb68d + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + invalidateNativeData + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + out_validData := FALSE; +out_stale := TRUE; + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Impl_LineIds + + + + + METHOD PUBLIC invalidateNativeData + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeFinish + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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(); + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Impl_LineIds + + + + + METHOD nativeFinish +VAR_INPUT + result : UDINT; +END_VAR +VAR + mapped : ModbusFB.Error; + ex : ModbusFB.ExceptionCodes; + duration : UDINT; +END_VAR + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + fd4b7cdb-3c7f-5110-af32-341f71321132 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeProcess + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + fd4b7cdb-3c7f-5110-af32-341f71321132_Impl_LineIds + + + + + METHOD nativeProcess +VAR_INPUT + functionCode : BYTE; +END_VAR + fd4b7cdb-3c7f-5110-af32-341f71321132_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 9ee1081f-903d-595c-ab0c-17c64f4fbe75 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + nativeInFlight := FALSE; +stats.execute := FALSE; +stats.reqBusy := FALSE; +stats.reqDone := FALSE; +stats.reqError := FALSE; +FB_reinit := TRUE; + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Impl_LineIds + + + + + METHOD FB_reinit : BOOL + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + CODESYS V3.5 SP21 Patch 4 + + + \ No newline at end of file diff --git a/compat/AtomModbus-Compatible.export b/compat/AtomModbus-Compatible.export new file mode 100644 index 0000000..cd8367c --- /dev/null +++ b/compat/AtomModbus-Compatible.export @@ -0,0 +1,5531 @@ + + + + + + + True + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + 00000000-0000-0000-0000-000000000000 + 6-Comms + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + e59dc300-e37a-4c37-997b-021563304d7e + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590379670 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + e59dc300-e37a-4c37-997b-021563304d7e + + atomLibrary + + -1 + + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + 00000000-0000-0000-0000-000000000000 + ModbusMaster + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590449813 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + atomLibrary + 6-Comms + + -1 + + + False + + f228f401-6c74-4359-8ff1-b15b675180a4 + 00000000-0000-0000-0000-000000000000 + atMbClientToString + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591030946 + + + + + {attribute 'qualified_only'} +{attribute 'strict'} +TYPE atMbClientToString : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE + + f228f401-6c74-4359-8ff1-b15b675180a4_Decl_LineIds + + + 11 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7 + 00000000-0000-0000-0000-000000000000 + atMBExceptionCodes + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590519807 + + + + + {attribute 'qualified_only'} +// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes. +TYPE atMBExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7_Decl_LineIds + + + 78 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + 00000000-0000-0000-0000-000000000000 + atMbClient + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639251309599709549 + + + None + + + IF nativeMode THEN + nativeCycle(); + RETURN; +END_IF; +//Modbus connection +//Connects to a modbus slave +//2024-11-23 ATM Cleaned up code +//2024-12-23 CWD Add statistics +//2025-06-18 CWD Add Get priorities and interval working together +//2026-03-26 ATM Clean up code, more testing +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +// ===== Main function call ===== + +IF (in_resetStats) THEN + resetStats(); + in_resetStats := FALSE; +END_IF + +// ===== Connect and disconnect logic ===== + +//Timer to trigger a connection if not connected for to long +reconnectTimer( + IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError, + PT := RECONNECT_DELAY +); + +//Timer to trigger a disconnection if connect fails +disconnectTimer( + IN:=client.xConnect AND NOT client.xConnected, + PT:=DISCONNECT_DELAY +); + +//All requests offline timer +allRequestsOfflineTON( + IN := allRequestsOffline AND client.xConnected, + PT := ALL_OFFLINE_DELAY +); + +//Destroy Client delay timer +recreateClientDly( + IN:=destroyClient, + PT:=RECREATE_CLIENT_DELAY +); + +IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN + //All requests are offline + //Connection attempt failed so disconnect + //If Client has error then disconnect + connect := FALSE; + recreateTest := FALSE; + destroyClient := TRUE; + setAllRequestsOffline(); +ELSIF reconnectTimer.Q AND NOT destroyClient THEN +//If not connected then attempt connect + connect := TRUE; + connectCnt := connectCnt + 1; //for debugging +END_IF; + + +IF recreateClientDly.Q THEN + destroyClient := FALSE; + recreateClient(); +END_IF; + +// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect). +forceConnect( + in := connect <> client.xConnect, + pt := FORCE_RECONNECT_DELAY +); + +// Only flip status of xConnect if no current request is active. +IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN + client.xConnect := connect; +END_IF + +// Moved this lower down, so xConnect happens one scan sooner. +client(); + +// If there are no requests, dont neeed to even do anything. +IF requestCnt = 0 THEN + numMsgSent := 0; + numMsgReply := 0; + allRequestsOnline := 0; + allRequestsOffline := 0; + RETURN; +END_IF + + +requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary + +// ===== Execute next request ===== +IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN + + //Find the lowest priority of overdue requests + nextIdx := -1; + priorityLowest := PRIORITY_NONE; + highestTimeSinceRun := 0; + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF (requests[i]^.in_priorityCurrent < priorityLowest + OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun)) + AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN + priorityLowest := requests[i]^.in_priorityCurrent; + highestTimeSinceRun := requests[i]^.stats.sinceLastExec; + nextIdx := i; + END_IF + END_FOR + + //Decriment all request priorities by one. + IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority + FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1; + IF requests[i]^.in_priorityCurrent < 0 THEN + requests[i]^.in_priorityCurrent := 0; + END_IF + END_FOR + END_IF + + IF nextIdx >= 0 THEN + activeIndex := nextIdx; + END_IF + END_IF + +processAllRequests(); + +allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt); +allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0); + +// ===== Client statistics ===== +//Get a sum of done and error counts from all requests +reqErrSum := 0; +reqDoneSum := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt; + reqErrSum := reqErrSum + requests[i]^.stats.errorCnt; + + //copy stats to array for debugging + reqStats[i] := ADR(requests[i]^.stats); +END_FOR + + + +//Every time the tx message counter changes increment msg count and add last transaction time +IF client.udiNumMsgSent <> msgCountLast THEN + msgCountLast := client.udiNumMsgSent; + msgTimeSum := msgTimeSum + client.udiLastTransactionTime; + msgCountSum := msgCountSum + 1; +END_IF + +//Every 1 second compute statistics +pulse(); +IF pulse.T_Sec_OS THEN + msgLastSecondCnt := msgCountSum; + msgCountSum := 0; + msgLastSecondTime := msgTimeSum; + msgTimeSum := 0; +END_IF + +numMsgSent := client.udiNumMsgSent; +numMsgReply := client.udiNumMsgReply; + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + //{attribute 'symbol' := 'none'} + reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests +END_VAR +VAR + {attribute 'symbol' := 'none'} + datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits + {attribute 'symbol' := 'none'} + client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set. + {attribute 'symbol' := 'none'} + connectCnt: DINT; //Number of connection attempts + {attribute 'symbol' := 'none'} + initDone: BOOL; + {attribute 'symbol' := 'none'} + disconnectTimer: TON; //Connecting but not connected delay + {attribute 'symbol' := 'none'} + reconnectTimer: TON; //Not connecting and not connected delay + {attribute 'symbol' := 'none'} + msgCountLast: UDINT; //Last message count + {attribute 'symbol' := 'none'} + msgCountSum: UDINT; //Message count accumulator + {attribute 'symbol' := 'none'} + msgTimeSum: UDINT; //Amount of time for messsages accumulator + {attribute 'symbol' := 'none'} + pulse: atPulse; //Pulse generator + {attribute 'symbol' := 'none'} + requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct + {attribute 'symbol' := 'none'} + requestCnt: UINT; //Number of active requests + {attribute 'symbol' := 'none'} + reqDoneSum: UDINT; //Count of done result on all requests + {attribute 'symbol' := 'none'} + reqErrSum: UDINT; //Count of error result on all requests + {attribute 'symbol' := 'none'} + i: DINT; //Loop variable + {attribute 'symbol' := 'none'} + nextIdx: DINT := -1; //Index of the active request + {attribute 'symbol' := 'none'} + priorityLowest: INT; //lowest request priority + {attribute 'symbol' := 'none'} + highestTimeSinceRun: UDINT; //lowest request priority + {attribute 'symbol' := 'none'} + allRequestsOfflineTON: TON; //All requests are offline delay + {attribute 'symbol' := 'none'} + requestNotExecutingTON: TON; //Delay after there are no requests execting + {attribute 'symbol' := 'none'} + recreateClientDly: TON; //Delay to make new client after force fail + {attribute 'symbol' := 'none'} + allRequestsOffline: BOOL; //All requests are offline + {attribute 'symbol' := 'none'} + connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true. + {attribute 'symbol' := 'none'} + forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect. + test1: REAL; + test2: REAL; + destroyClient: BOOL; + recreateTest: BOOL; +END_VAR +VAR CONSTANT + {attribute 'symbol' := 'none'} + NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay + {attribute 'symbol' := 'none'} + RECONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + DISCONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + RECREATE_CLIENT_DELAY: TIME := T#1000MS; + {attribute 'symbol' := 'none'} + ALL_OFFLINE_DELAY: TIME := T#2S; + {attribute 'symbol' := 'none'} + FORCE_RECONNECT_DELAY: TIME := T#1S; + {attribute 'symbol' := 'none'} + PRIORITY_NONE: INT := 32767; +END_VAR + +VAR +nativeMode : BOOL; +nativeHandle : UDINT; +nativeIP : DWORD; +nativePort : UINT; +nativeNowUs : SYSTIME; +nativeNowMs : ULINT; +nativeStatus : ARRAY[0..23] OF UDINT; +nativeActiveIndex : DINT := -1; +nativeSelected : POINTER TO atMbReqBase; +nativeResultReady : BOOL; +nativeFinishedIndex : DINT := -1; +nativeCallResult : UDINT; +nativeWindowStart : ULINT; +nativePrevConnected : BOOL; +nativeEnable : BOOL := TRUE; +nativeConnectTimeout : UDINT := 2000; +nativeRetryDelay : UDINT := 1000; +nativeHoldUncertainWrites : BOOL := FALSE; +END_VAR +VAR_INPUT +{attribute 'symbol' := 'none'} +nativeIO : ARRAY[0..1999] OF WORD; +{attribute 'symbol' := 'none'} +nativeActiveRequest : POINTER TO atMbReqBase; +{attribute 'symbol' := 'none'} +nativeArgUnit : BYTE; +{attribute 'symbol' := 'none'} +nativeArgFunction : BYTE; +{attribute 'symbol' := 'none'} +nativeArgAddress : UINT; +{attribute 'symbol' := 'none'} +nativeArgQuantity : UINT; +{attribute 'symbol' := 'none'} +nativeArgTimeout : UDINT := 1000; +{attribute 'symbol' := 'none'} +nativeSent : UDINT; +{attribute 'symbol' := 'none'} +nativeReplied : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowCount : UDINT; +{attribute 'symbol' := 'none'} +nativeWindowTime : UDINT; +END_VAR + +VAR_OUTPUT +out_connected : BOOL; +out_connectionState : UDINT; +out_lastNativeError : UDINT; +out_socketError : UDINT; +out_reconnectCount : UDINT; +out_registrationFull : BOOL; +END_VAR + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Decl_LineIds + + + 3369 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + 00000000-0000-0000-0000-000000000000 + atMbClientRTU + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649591897 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-13 CWD Fix problem with not connecting properly on power up +//2024-11-15 ATM Move from structure to defaults. +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +dataRate := in_baudRate; + + +// ===== Initialization ===== +// if the param changes, force a reinit +IF _client^.iPort <> in_port + OR _client^.dwBaudrate <> in_baudRate + OR _client^.byDataBits <> in_dataBits + OR _client^.eParity <> in_parity + OR _client^.eStopBits <> in_stopBits THEN + initDone := FALSE; +END_IF; + +IF NOT initDone THEN + initDone := TRUE; + + //Other configuration parameters + _client^.iPort := in_port; + _client^.dwBaudrate := in_baudRate; + _client^.byDataBits := in_dataBits; + _client^.eParity := in_parity; + _client^.eStopBits := in_stopBits; + _client^.eDTRcontrol := 0; + _client^.eRTScontrol := 0; + _client^.eRTUASCII := 0; + _client^.udiLogOptions := ModbusFB.LoggingOptions.All; + _client^.xConnect:=FALSE; + +END_IF + +SUPER^(); + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_port: BYTE := 0; //0=None, 1,2,3,4 + {attribute 'symbol' := 'none'} + in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200 + {attribute 'symbol' := 'none'} + in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even + {attribute 'symbol' := 'none'} + in_dataBits: BYTE := 8; //8=8 + {attribute 'symbol' := 'none'} + in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientSerial; +END_VAR + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Decl_LineIds + + + 607 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + 00000000-0000-0000-0000-000000000000 + atMbClientTCP + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649747183 + + + None + + + 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^(); + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientTCP; +END_VAR +VAR_INPUT +in_enable : BOOL := TRUE; +in_connectTimeout : TIME := T#2S; +in_reconnectDelay : TIME := T#1S; +in_holdUncertainWrites : BOOL := FALSE; +END_VAR + +VAR +nativeEndpointIP : DWORD; +nativeEndpointPort : UINT; +nativeConfigConnect : UDINT; +nativeConfigRetry : UDINT; +END_VAR + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Decl_LineIds + + + 1168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + 00000000-0000-0000-0000-000000000000 + atMbReqBase + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649837310 + + + None + + + //Modbus request +//Makes a request using the start and quantity, puts the result into data +//2024-06-27 CWD Initial create +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage + +IF NOT __ISVALIDREF(client) THEN + RETURN; +END_IF + + +//Add this request to the clients request array +client.addRequest(THIS); + +//fault logic +//Fault if error +//errTrig(CLK := stats.reqError); +//IF errTrig.Q THEN +// out_fault := TRUE; +//END_IF +//not faulted if a succesfull request +//doneTrig(CLK := stats.reqDone); +// also clear fault if not active +//IF doneTrig.Q OR NOT stats.active THEN +// out_fault := FALSE; +//END_IF + +IF in_quantity <= dataSize THEN + stats.reqLength := in_quantity; +ELSE + stats.reqLength := dataSize; +END_IF; + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbReqBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO atMbClient; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: atMbRequestStats; //Request stats + +END_VAR +VAR_OUTPUT + //{attribute 'symbol' := 'none'} + //out_fault: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataSize: UINT; + //{attribute 'symbol' := 'none'} + //quantUsed: UINT; + //{attribute 'symbol' := 'none'} + //doneTrig: R_TRIG; //Succesful request trigger + //{attribute 'symbol' := 'none'} + //errTrig: R_TRIG; //Error request result trigger + + {attribute 'symbol' := 'none'} + request: REFERENCE TO ModbusFB.ClientRequest;; +END_VAR + + +VAR +nativeFunction : BYTE; +nativeCount : UINT; +nativeOffset : UINT; +nativeInFlight : BOOL; +nativeReadComplete : BOOL; +nativeCopyAllowed : BOOL; +nativeWriteHold : BOOL; +nativeStartMs : ULINT; +nativeLoop : DINT; +nativeRC : UDINT; +END_VAR + +VAR_OUTPUT +out_nativeError : UDINT; +out_socketError : UDINT; +out_writeUncertain : BOOL; +out_validData : BOOL; +out_dataAgeMs : ULINT; +out_stale : BOOL := TRUE; +out_parameterError : BOOL; +END_VAR + +VAR_INPUT +in_staleAfter : TIME := T#2S; +END_VAR + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Decl_LineIds + + + 3210 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + 00000000-0000-0000-0000-000000000000 + atMbReqReadCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146913881 + + + None + + + 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; +//Modbus request input registers +//2024-11-16 CWD Added read coil +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils; +END_VAR + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Decl_LineIds + + + 193 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 1a3278f4-a658-407c-968a-21aa53361562 + 00000000-0000-0000-0000-000000000000 + atMbReqReadDiscrete + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147014015 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 1a3278f4-a658-407c-968a-21aa53361562_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs; +END_VAR + + 1a3278f4-a658-407c-968a-21aa53361562_Decl_LineIds + + + 184 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 73b10819-9112-4811-9583-e2735d2b13ea + 00000000-0000-0000-0000-000000000000 + atMbReqReadHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147099228 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 73b10819-9112-4811-9583-e2735d2b13ea_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters; +END_VAR + + 73b10819-9112-4811-9583-e2735d2b13ea_Decl_LineIds + + + 173 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + 00000000-0000-0000-0000-000000000000 + atMbReqReadInput + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147179217 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters; +END_VAR + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Decl_LineIds + + + 167 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + 00000000-0000-0000-0000-000000000000 + atMbRequestStats + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147425423 + + + None + + + SysTimeGetUs(TSSus); + +IF (reqLength = 0) THEN + active := FALSE; + doneCnt := 0; + errorCnt := 0; + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + execute := FALSE; + online := FALSE; + offline := FALSE; + reqDone := FALSE; + reqBusy := FALSE; + reqError := FALSE; + sinceLastExec := 0; + RETURN; +END_IF +active := TRUE; + +inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn); +reqDoneLight := inDoneDeb.out_val; + +inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn); +reqBusyLight := inBusyDeb.out_val; + +inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn); +reqErrorLight := inErrDeb.out_val; + +//Debounced execute indicator for debbugging +executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn); +executeLight := executeDeb.out_val; + + +//Set online status +IF nativeResponseSeen OR doneCntRst >=3 THEN + online := TRUE; + offline := FALSE; +END_IF + +//Set online status +IF NOT nativeResponseSeen AND errorCntRst >= 3 THEN + online := FALSE; + offline := TRUE; +END_IF + +//Record start time in ms on execute rising edge +executeTrig(CLK := execute); +IF executeTrig.Q THEN + interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + lastReqStart := TSSus; +END_IF + +//Offline count +offlineTrig(CLK := NOT online); +IF offlineTrig.Q THEN + offlineCnt := offlineCnt + 1; +END_IF + +//Calculate ms since last execute +//If the last request start is more then 1 minute ago then set it for 1 minute +IF TSSus >= lastReqStart THEN + IF TSSus - lastReqStart > 60000000 THEN lastReqStart := TSSus - 60000000; END_IF; +ELSE lastReqStart := TSSus; +END_IF; +sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK atMbRequestStats +VAR_INPUT + lastErrorId: modbusFB.Error; //Last error id + lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + inDoneDeb: atDebounce; + {attribute 'symbol' := 'none'} + inErrDeb: atDebounce; + {attribute 'symbol' := 'none'} + inBusyDeb: atDebounce; + {attribute 'symbol' := 'none'} + executeDeb: atDebounce; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastReqStart: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + TSSus: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + lightOn: TIME:= T#100MS; //Light on time for debug indicators +END_VAR +VAR_INPUT +nativeResponseSeen : BOOL; +nativeLastSuccessMs : ULINT; +END_VAR + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Decl_LineIds + + + 286 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + 00000000-0000-0000-0000-000000000000 + atMbReqWriteCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147259331 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + f6ee860b-0951-41d0-93d8-6436c681d8f1_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils; +END_VAR + + f6ee860b-0951-41d0-93d8-6436c681d8f1_Decl_LineIds + + + 206 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bb7e18ba-2018-4681-875e-de1f291019ef + 00000000-0000-0000-0000-000000000000 + atMbReqWriteHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147339348 + + + None + + + 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; +//Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + bb7e18ba-2018-4681-875e-de1f291019ef_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters; +END_VAR + + bb7e18ba-2018-4681-875e-de1f291019ef_Decl_LineIds + + + 164 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9499c60e-03a2-49d0-b086-43990a018e20 + 00000000-0000-0000-0000-000000000000 + To_IPAddress + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590489804 + + + None + + + //Convert parameters to array +To_IPAddress[0] := byte1; +To_IPAddress[1] := byte2; +To_IPAddress[2] := byte3; +To_IPAddress[3] := byte4; + + + + 9499c60e-03a2-49d0-b086-43990a018e20_Impl_LineIds + + + + + FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR + + 9499c60e-03a2-49d0-b086-43990a018e20_Decl_LineIds + + + 168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9e99e368-853f-485b-911f-d9a2a4e46434 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + addRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146468437 + + + + + IF request = 0 THEN RETURN; END_IF; +IF requestCnt > 0 THEN + FOR i := 0 TO UINT_TO_DINT(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; + 9e99e368-853f-485b-911f-d9a2a4e46434_Impl_LineIds + + + + + //Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO atMbReqBase; +END_VAR +VAR + i: UINT := 0; + +END_VAR + + 9e99e368-853f-485b-911f-d9a2a4e46434_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 685f7c69-c46a-4b34-b5e2-fb848c593f3e + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + processAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148036583 + + + + + 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]^.processRequest(); + END_IF; +END_FOR; + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Impl_LineIds + + + + + METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + i: DINT; // Loop bstisnlr +END_VAR + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 39769d9e-08c1-472d-82ef-25c66891a065 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148056585 + + + + + 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]^.recreateRequest(); + END_IF; +END_FOR; + 39769d9e-08c1-472d-82ef-25c66891a065_Impl_LineIds + + + + + METHOD PROTECTED recreateAllRequests +VAR_INPUT +END_VAR + +VAR + i : DINT; +END_VAR + + 39769d9e-08c1-472d-82ef-25c66891a065_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648119824 + + + + + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Impl_LineIds + + + + + METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + a7e27967-573f-4f9f-86e7-dd569b8cd943 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; +connectCnt := 0; +val := ADR(client.udiNumMsgSent); +val^ := 0; +val := ADR(client.udiNumMsgReply); +val^ := 0; +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + requests[i]^.stats.resetStats(); +END_FOR; + a7e27967-573f-4f9f-86e7-dd569b8cd943_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR +VAR + i: DINT; + val: POINTER TO UDINT; +END_VAR + + + + + a7e27967-573f-4f9f-86e7-dd569b8cd943_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + ca1dfc70-ad93-43de-886d-39e56897aa6d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setAllRequestsOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148106585 + + + + + 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]^.stats.setClientOffline(); + END_IF; +END_FOR; + ca1dfc70-ad93-43de-886d-39e56897aa6d_Impl_LineIds + + + + + METHOD PROTECTED setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + i: DINT; +END_VAR + + + + + ca1dfc70-ad93-43de-886d-39e56897aa6d_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + d12c9bf2-320f-4e95-badb-68a2357c5783 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648239962 + + + + + IF nativeMode THEN + setupClient := nativeHandle <> 0; + RETURN; +END_IF; +// Only create request if doesnt exist +IF NOT __ISVALIDREF(client) THEN + recreateClient(); + IF NOT __ISVALIDREF(client) THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +setupClient := TRUE; + d12c9bf2-320f-4e95-badb-68a2357c5783_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT +END_VAR + + d12c9bf2-320f-4e95-badb-68a2357c5783_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeIndex + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590625089 + + + + + PROPERTY activeIndex : DINT + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeReqCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502146293051 + + + + + PROPERTY PRIVATE activeReqCnt : UINT + 456b4953-57c9-4f76-ab5b-0db456dd9b58_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + allRequestsComplete + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502147921016 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL + 4d9d378a-f256-4f23-b835-539ee9e287c3_Decl_LineIds + + + 2 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + onlineCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590755417 + + + + + {attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online + 89742962-f9b6-46c2-b9a5-b7e298f8495d_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 27292866-0bc0-4848-a965-64c51a0bf960 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + requestExecuting + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590845466 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing + 27292866-0bc0-4848-a965-64c51a0bf960_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4950a3a1-f54f-434d-9252-b61a6cb4663d + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649617058 + + + + + old_client_id := _client; +_client := __NEW(ModbusFB.ClientSerial); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Impl_LineIds + + + + + METHOD recreateClient +VAR_INPUT + old_client_id: POINTER TO ModbusFB.ClientSerial; +END_VAR + + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Decl_LineIds + + + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + e8c6c80b-7bdb-4108-987e-4af107de4377 + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + toString + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650142720 + + + + + {attribute 'symbol' := 'none'} +PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working + e8c6c80b-7bdb-4108-987e-4af107de4377_Decl_LineIds + + + 3 + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + 79dfe91d-6dbc-46b0-866c-4b880e44e02b + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649757233 + + + + + nativeRelease(); +// Next scan creates a fresh native context with current endpoint settings. + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Impl_LineIds + + + + + METHOD recreateClient +VAR +END_VAR + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + busy + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148176670 + + + + + stats.reqBusy := in; + +IF NOT in THEN + RETURN; +END_IF; + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Impl_LineIds + + + + + METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +VAR + executeTime: ULINT; + replyTimeoutTime: ULINT; +END_VAR + + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 5350a8fa-8297-42db-b185-fcb966e45684 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + completeRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148196661 + + + + + done(request.xDone); +err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException); +busy(in := request.xBusy); +stats(); + +IF (atGVL.reset) THEN + // request.AbortModel(); + request.ResetModel(TRUE); +END_IF + 5350a8fa-8297-42db-b185-fcb966e45684_Impl_LineIds + + + + + METHOD completeRequest +VAR_INPUT +END_VAR + + 5350a8fa-8297-42db-b185-fcb966e45684_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + done + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148206667 + + + + + stats.reqDone := in; + +IF NOT in THEN + RETURN; +END_IF + +//Set priority for next request +in_priorityCurrent := in_prioritySetpoint; + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Impl_LineIds + + + + + METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + err + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148226656 + + + + + stats.reqError := in; + +IF NOT in THEN + RETURN; +END_IF + + +//Update error info +stats.lastErrorId := errorId; +stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode); + +//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. +//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + //Since there was no response from the device set the priority to higher number priority +// in_priorityCurrent := in_prioritySetPoint * 5; +//END_IF + +//Set priority for next request, priority gets higher after each error result +priorityMultiplier := UDINT_TO_INT(stats.errorCntRst); +IF priorityMultiplier > 3 THEN + priorityMultiplier := 3; +END_IF +// set multiplier to exponential retry. +in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier; +//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier; + +// NO idea what Reset Model does, hoping it fixes comms issues +// request.ResetModel(TRUE); // I dont think this helped + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Impl_LineIds + + + + + METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: modbusFB.Error; //modbusFB error ID + exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 109d8e00-bf23-4306-a8d5-764aa6a03235 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148246723 + + + + + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Impl_LineIds + + + + + METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148266663 + + + + + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Impl_LineIds + + + + + METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + db40c392-b461-4198-851d-85857ed98ccf + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148276644 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + setupClient := FALSE; // Native path has no ModbusFB request object. + RETURN; +END_IF; +// Only create request if doesnt exist +IF in_request = 0 THEN + in_request := recreateRequest(); + IF in_request = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +IF NOT __ISVALIDREF(mbsClient) THEN + setupClient := FALSE; + RETURN; +END_IF + +request REF= in_request^; + +setupClient := TRUE; +RETURN; + db40c392-b461-4198-851d-85857ed98ccf_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT + in_request: POINTER TO ModbusFB.ClientRequest; +END_VAR + + db40c392-b461-4198-851d-85857ed98ccf_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 6159c037-e399-466d-8108-0c4f6570f9b2 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + mbsClient + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650298004 + + + + + PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client + 6159c037-e399-466d-8108-0c4f6570f9b2_Decl_LineIds + + + 1 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + replyTimeout + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591196333 + + + + + //Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT + 4f361885-f95c-4244-84d6-8c3b6b9b8a18_Decl_LineIds + + + 2 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + ea5277e9-2ae8-417f-a88d-b571902c7d71 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699321592 + + + + + 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)); + nativeProcess(1); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readCoilReq) THEN + RETURN; +END_IF + +//Do request processing +readCoilReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + ea5277e9-2ae8-417f-a88d-b571902c7d71_Impl_LineIds + + + + + METHOD processRequest + + ea5277e9-2ae8-417f-a88d-b571902c7d71_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148331930 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readCoilReq <> 0 THEN + __DELETE(readCoilReq); + readCoilReq := 0; +END_IF +readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils); +recreateRequest := readCoilReq; + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 9df094f9-a173-4520-816f-7badcbb1bd34 + 1a3278f4-a658-407c-968a-21aa53361562 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699461941 + + + + + 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)); + nativeProcess(2); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := client.nativeIO[nativeLoop] <> 0; + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readDiscreteReq) THEN + RETURN; +END_IF + +//Do request processing +readDiscreteReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 9df094f9-a173-4520-816f-7badcbb1bd34_Impl_LineIds + + + + + METHOD processRequest + + 9df094f9-a173-4520-816f-7badcbb1bd34_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + d34866d4-1134-4650-b3d6-2e19d2fd09d1 + 1a3278f4-a658-407c-968a-21aa53361562 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148362063 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readDiscreteReq <> 0 THEN + __DELETE(readDiscreteReq); + readDiscreteReq := 0; +END_IF +readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs); +recreateRequest := readDiscreteReq; + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + 7b978176-d206-4509-b9d8-7908d33a9a74 + 73b10819-9112-4811-9583-e2735d2b13ea + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148402056 + + + + + 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)); + nativeProcess(3); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readHoldReq) THEN + RETURN; +END_IF + +//Do request processing +readHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + // udiReplyTimeout := 1 * 1000 * 1000, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 7b978176-d206-4509-b9d8-7908d33a9a74_Impl_LineIds + + + + + METHOD processRequest + + 7b978176-d206-4509-b9d8-7908d33a9a74_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83 + 73b10819-9112-4811-9583-e2735d2b13ea + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148422060 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readHoldReq <> 0 THEN + __DELETE(readHoldReq); + readHoldReq := 0; +END_IF +readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters); +recreateRequest := readHoldReq; + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 1004c837-fbef-45ee-986d-977343f4bd8f + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148442038 + + + + + 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)); + nativeProcess(4); + IF nativeReadComplete AND nativeCopyAllowed THEN + FOR nativeLoop := 0 TO UINT_TO_DINT(nativeCount)-1 DO + data[UINT_TO_DINT(nativeOffset)+nativeLoop] := WORD_TO_UINT(client.nativeIO[nativeLoop]); + END_FOR; + END_IF; + RETURN; +END_IF; +IF NOT setupClient(readInputReq) THEN + RETURN; +END_IF + +//Do request processing +readInputReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 1004c837-fbef-45ee-986d-977343f4bd8f_Impl_LineIds + + + + + METHOD processRequest + + 1004c837-fbef-45ee-986d-977343f4bd8f_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605 + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148462074 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF readInputReq <> 0 THEN + __DELETE(readInputReq); + readInputReq := 0; +END_IF +readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters); +recreateRequest := readInputReq; + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148542250 + + + + + doneCnt := 0; +doneCntRst := 0; +errorCnt := 0; +errorCntRst := 0; + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + b1508d1f-b7a7-40b5-b74f-3091afc5b834 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setClientOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148552305 + + + + + nativeResponseSeen := FALSE; +doneCntRst := 0; +online := FALSE; +offline := TRUE; + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Impl_LineIds + + + + + METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR + + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setToString + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148572279 + + + + + toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt)))); + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Impl_LineIds + + + + + METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + execute + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043399503 + + + + + PROPERTY execute : BOOL + 89434b6a-7e3b-4095-9220-0a6fc3789a25_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqBusy + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043449644 + + + + + PROPERTY reqBusy : BOOL + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqDone + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043519638 + + + + + PROPERTY reqDone : BOOL + 80965ce1-9b6a-44d3-b272-a5c739ccbc70_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqError + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043604994 + + + + + PROPERTY reqError : BOOL + 30645d91-91c6-42eb-8399-c20f91ca0d3e_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + fbb41e61-f245-4f4e-bcd0-218ede01fcda + f6ee860b-0951-41d0-93d8-6436c681d8f1 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148472030 + + + + + 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 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 + client.nativeIO[nativeLoop] := BOOL_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(15); + RETURN; +END_IF; +IF NOT setupClient(writeCoilsReq) THEN + RETURN; +END_IF + +//Do request processing +writeCoilsReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Impl_LineIds + + + + + METHOD processRequest + + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + a861d6e2-2501-43dc-bb37-34509923450c + f6ee860b-0951-41d0-93d8-6436c681d8f1 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148502269 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeCoilsReq <> 0 THEN + __DELETE(writeCoilsReq); + writeCoilsReq := 0; +END_IF +writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils); +recreateRequest := writeCoilsReq; + a861d6e2-2501-43dc-bb37-34509923450c_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + a861d6e2-2501-43dc-bb37-34509923450c_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac + f6ee860b-0951-41d0-93d8-6436c681d8f1 + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591456658 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278 + bb7e18ba-2018-4681-875e-de1f291019ef + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148512269 + + + + + 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 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 + client.nativeIO[nativeLoop] := UINT_TO_WORD(data[UINT_TO_DINT(in_dataOffset)+nativeLoop]); + END_FOR; + END_IF; + nativeProcess(16); + RETURN; +END_IF; +IF NOT setupClient(writeHoldReq) THEN + RETURN; +END_IF + +//Do request processing +writeHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Impl_LineIds + + + + + METHOD processRequest + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + bd7ff184-1747-404c-9fa0-1b728376c927 + bb7e18ba-2018-4681-875e-de1f291019ef + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148532258 + + + + + IF __ISVALIDREF(client) AND_THEN client.nativeMode THEN + IF client.nativeActiveRequest = THIS THEN client.recreateClient(); END_IF; + nativeInFlight := FALSE; + recreateRequest := 0; + RETURN; +END_IF; +IF writeHoldReq <> 0 THEN + __DELETE(writeHoldReq); + writeHoldReq := 0; +END_IF +writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters); +recreateRequest := writeHoldReq; + bd7ff184-1747-404c-9fa0-1b728376c927_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + bd7ff184-1747-404c-9fa0-1b728376c927_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 5f9160f9-c4a4-4f65-a74c-2838020397f3 + bb7e18ba-2018-4681-875e-de1f291019ef + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591496600 + + + + + nativeWriteHold := FALSE; +out_writeUncertain := FALSE; +in_priorityCurrent := 0; + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590675071 + + + + + IF nativeMode THEN + activeIndex := nativeActiveIndex; + RETURN; +END_IF; +// ===== Test for any requests executing ===== +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN + activeIndex := i; + RETURN; + END_IF +END_FOR +activeIndex := -1; + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Decl_LineIds + + + True + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + 3b82f264-e41e-461d-9beb-263305a22a8d + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590705101 + + + + + // set request to active, which is how the active index gets calculated. +requests[activeIndex]^.stats.execute := TRUE; + 3b82f264-e41e-461d-9beb-263305a22a8d_Impl_LineIds + + + + + VAR +END_VAR + + 3b82f264-e41e-461d-9beb-263305a22a8d_Decl_LineIds + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + cc024b23-e5fe-4958-ad80-513feea6aeab + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146383170 + + + + + cnt := 0; +IF requestCnt = 0 THEN + activeReqCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.active THEN + cnt := cnt + 1; + END_IF +END_FOR; +activeReqCnt := cnt; + cc024b23-e5fe-4958-ad80-513feea6aeab_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + cc024b23-e5fe-4958-ad80-513feea6aeab_Decl_LineIds + + + True + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeReqCnt + + -1 + + + False + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e + 4d9d378a-f256-4f23-b835-539ee9e287c3 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147990941 + + + + + IF requestCnt = 0 THEN + allRequestsComplete := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN + allRequestsComplete := FALSE; + RETURN; + END_IF +END_FOR; +allRequestsComplete := TRUE; + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Impl_LineIds + + + + + VAR + i: UINT; +END_VAR + + + + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Decl_LineIds + + + True + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + allRequestsComplete + + -1 + + + False + + dceb6b17-86bf-4543-9652-2fd2b943953c + 89742962-f9b6-46c2-b9a5-b7e298f8495d + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146538436 + + + + + cnt := 0; +IF requestCnt = 0 THEN + onlineCnt := 0; + RETURN; +END_IF +FOR i := 0 TO UINT_TO_DINT(requestCnt)-1 DO + //Test for any offline request + IF requests[i]^.stats.online THEN + cnt := cnt + 1; + END_IF +END_FOR; +onlineCnt := cnt; + dceb6b17-86bf-4543-9652-2fd2b943953c_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + dceb6b17-86bf-4543-9652-2fd2b943953c_Decl_LineIds + + + True + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + onlineCnt + + -1 + + + False + + 0006696f-29e2-441e-a325-e3481d025cec + 27292866-0bc0-4848-a965-64c51a0bf960 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590890993 + + + + + // ===== Test for any requests executing ===== +requestExecuting := activeIndex <> -1; + 0006696f-29e2-441e-a325-e3481d025cec_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0006696f-29e2-441e-a325-e3481d025cec_Decl_LineIds + + + True + + 27292866-0bc0-4848-a965-64c51a0bf960 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + requestExecuting + + -1 + + + False + + 3d4da579-672d-4758-9b0f-e055a08e0da3 + e8c6c80b-7bdb-4108-987e-4af107de4377 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650278010 + + + + + //Calculation for tostring +IF onlineCnt = requestCnt THEN + toString := atMbClientToString.allOk; +ELSIF onlineCnt = 0 THEN + toString := atMbClientToString.noneOk; +ELSE + toString := atMbClientToString.someOk; +END_IF + 3d4da579-672d-4758-9b0f-e055a08e0da3_Impl_LineIds + + + + + + 3d4da579-672d-4758-9b0f-e055a08e0da3_Decl_LineIds + + + True + + e8c6c80b-7bdb-4108-987e-4af107de4377 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + toString + + -1 + + + False + + 4895a5bf-c631-4f8c-bee7-addb0b769548 + 6159c037-e399-466d-8108-0c4f6570f9b2 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650328003 + + + + + IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN + RETURN; +END_IF + +mbsClient REF= client.client; + 4895a5bf-c631-4f8c-bee7-addb0b769548_Impl_LineIds + + + + + VAR +END_VAR + + 4895a5bf-c631-4f8c-bee7-addb0b769548_Decl_LineIds + + + True + + 6159c037-e399-466d-8108-0c4f6570f9b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + mbsClient + + -1 + + + False + + f54a4cd6-8540-4892-a914-4aac4f8d9018 + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591226460 + + + + + +IF in_timeout > 0 THEN + //Explicitly set timout + replyTimeout := ANY_TO_UDINT((in_timeout) * 1000); +ELSE + // If client timeout is set, use that. + IF client.in_timeout <> T#0MS THEN + replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000; + ELSIF client.dataRate <> 0 THEN + //Data transmision time * 1.5 to get the timeout. Result is in ms + //8 byte request, then 255 byte reply = 2630 bits. + replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000); + ELSE + //In case client datarate = zero + replyTimeout := 1000000; //1 second? + END_IF + + +END_IF + + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Impl_LineIds + + + + + VAR +END_VAR + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Decl_LineIds + + + True + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + replyTimeout + + -1 + + + False + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7 + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043419500 + + + + + execute := _execute; + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Impl_LineIds + + + + + VAR +END_VAR + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Decl_LineIds + + + True + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + 8ff5fbe7-c047-436d-923c-942d0d66494c + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043439650 + + + + + IF reqDone OR reqError THEN + _execute := FALSE; + RETURN; +END_IF +_execute := execute; + 8ff5fbe7-c047-436d-923c-942d0d66494c_Impl_LineIds + + + + + VAR +END_VAR + + 8ff5fbe7-c047-436d-923c-942d0d66494c_Decl_LineIds + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + db10f23e-d96b-4fd5-aec0-8b530ad288e1 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043479636 + + + + + reqBusy := _reqBusy; + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Impl_LineIds + + + + + VAR +END_VAR + + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Decl_LineIds + + + True + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + a239da3a-8f75-41da-a264-f06ca07143f4 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043499661 + + + + + _reqBusy := reqBusy; + a239da3a-8f75-41da-a264-f06ca07143f4_Impl_LineIds + + + + + VAR +END_VAR + + a239da3a-8f75-41da-a264-f06ca07143f4_Decl_LineIds + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + 61db378f-6029-4712-b562-fea707e5698b + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043539625 + + + + + reqDone := _reqDone; + 61db378f-6029-4712-b562-fea707e5698b_Impl_LineIds + + + + + VAR +END_VAR + + 61db378f-6029-4712-b562-fea707e5698b_Decl_LineIds + + + True + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3 + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147505410 + + + + + _reqDone := reqDone; + +IF (execute AND reqDone) THEN + // ===== Request has just completed succesfully ===== + //Update last valid duration + lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + IF lastValidDuration > longestValidDuration THEN + longestValidDuration := lastValidDuration; + END_IF + //Counters + doneCnt := doneCnt + 1; + doneCntRst := doneCntRst + 1; + errorCntRst := 0; + + //Update error info + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + + execute := FALSE; + setToString(); +END_IF + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Impl_LineIds + + + + + VAR +END_VAR + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Decl_LineIds + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + e44e83bd-8f84-4202-9ab4-784d09cf5506 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043645000 + + + + + reqError := _reqError; + e44e83bd-8f84-4202-9ab4-784d09cf5506_Impl_LineIds + + + + + VAR +END_VAR + + e44e83bd-8f84-4202-9ab4-784d09cf5506_Decl_LineIds + + + True + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + 89b5f7bc-0c37-43f3-8984-14034b000d89 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147595513 + + + + + _reqError := reqError; + +IF (execute AND reqError) THEN + // ===== Request has just completed with error ===== + // Counters + errorCnt := errorCnt + 1; + errorCntRst := errorCntRst + 1; + doneCntRst := 0; + //Turn off execute + execute := FALSE; + + //Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. + IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + timeoutCnt := timeoutCnt + 1; + lastExceptionCode := atMBExceptionCodes.TIMEOUT; + END_IF + setToString(); +END_IF + 89b5f7bc-0c37-43f3-8984-14034b000d89_Impl_LineIds + + + + + VAR +END_VAR + + 89b5f7bc-0c37-43f3-8984-14034b000d89_Decl_LineIds + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + f0be8440-a307-5b71-82c8-af27d8852e42 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCall + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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); + f0be8440-a307-5b71-82c8-af27d8852e42_Impl_LineIds + + + + + METHOD nativeCall : UDINT +VAR_INPUT + command : UDINT; +END_VAR + f0be8440-a307-5b71-82c8-af27d8852e42_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 1233447d-8796-56cf-9291-c959801d2a63 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + nativeCycle + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 1233447d-8796-56cf-9291-c959801d2a63_Impl_LineIds + + + + + METHOD nativeCycle +VAR + j : DINT; + best : DINT; + priority : INT; + age : UDINT; + rc : UDINT; +END_VAR + 1233447d-8796-56cf-9291-c959801d2a63_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + c08712ba-3cf5-54e9-89a1-76a1c3183975 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + nativeRelease + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + c08712ba-3cf5-54e9-89a1-76a1c3183975_Impl_LineIds + + + + + METHOD nativeRelease : BOOL +VAR + j : DINT; +END_VAR + c08712ba-3cf5-54e9-89a1-76a1c3183975_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + release + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + in_enable := FALSE; +release := nativeRelease(); + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Impl_LineIds + + + + + METHOD release : BOOL + 2a6d360f-01c4-5b27-81fa-337b6b33a1b6_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_exit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + FB_exit := nativeRelease(); + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Impl_LineIds + + + + + METHOD FB_exit : BOOL +VAR_INPUT + bInCopyCode : BOOL; +END_VAR + b2fda51f-6f2a-5b12-bc9c-2985dd3a44b4_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 7568f58a-e795-5d22-a622-3f070deb32b0 + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + 7568f58a-e795-5d22-a622-3f070deb32b0_Impl_LineIds + + + + + METHOD FB_reinit : BOOL +VAR + j : DINT; +END_VAR + 7568f58a-e795-5d22-a622-3f070deb32b0_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + 28ccddd0-4531-5168-aa3e-ac56dddcb68d + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + invalidateNativeData + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + out_validData := FALSE; +out_stale := TRUE; + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Impl_LineIds + + + + + METHOD PUBLIC invalidateNativeData + 28ccddd0-4531-5168-aa3e-ac56dddcb68d_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeFinish + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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(); + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Impl_LineIds + + + + + METHOD nativeFinish +VAR_INPUT + result : UDINT; +END_VAR +VAR + mapped : ModbusFB.Error; + ex : ModbusFB.ExceptionCodes; + duration : UDINT; +END_VAR + 1c14b96c-a77e-5cd3-b853-c18bf9b5ea87_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + fd4b7cdb-3c7f-5110-af32-341f71321132 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + nativeProcess + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + 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; + fd4b7cdb-3c7f-5110-af32-341f71321132_Impl_LineIds + + + + + METHOD nativeProcess +VAR_INPUT + functionCode : BYTE; +END_VAR + fd4b7cdb-3c7f-5110-af32-341f71321132_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 9ee1081f-903d-595c-ab0c-17c64f4fbe75 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + FB_reinit + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + nativeInFlight := FALSE; +stats.execute := FALSE; +stats.reqBusy := FALSE; +stats.reqDone := FALSE; +stats.reqError := FALSE; +FB_reinit := TRUE; + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Impl_LineIds + + + + + METHOD FB_reinit : BOOL + 9ee1081f-903d-595c-ab0c-17c64f4fbe75_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + CODESYS V3.5 SP21 Patch 4 + + + \ No newline at end of file diff --git a/compat/Original_HomeCodesys.export b/compat/Original_HomeCodesys.export new file mode 100644 index 0000000..0d3afae --- /dev/null +++ b/compat/Original_HomeCodesys.export @@ -0,0 +1,4646 @@ + + + + + + + True + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + 00000000-0000-0000-0000-000000000000 + 6-Comms + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + e59dc300-e37a-4c37-997b-021563304d7e + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590379670 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + e59dc300-e37a-4c37-997b-021563304d7e + + atomLibrary + + -1 + + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + 00000000-0000-0000-0000-000000000000 + ModbusMaster + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + + + + + + + 738bea1e-99bb-4f04-90bb-a7a567e74e3a + + 639034841590449813 + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + 98ed6c72-1d8a-444d-b9fb-44a679624f78 + + atomLibrary + 6-Comms + + -1 + + + False + + f228f401-6c74-4359-8ff1-b15b675180a4 + 00000000-0000-0000-0000-000000000000 + atMbClientToString + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591030946 + + + + + {attribute 'qualified_only'} +{attribute 'strict'} +TYPE atMbClientToString : +( + noneOk := 0, + someOk := 1, + allOk := 2 +); +END_TYPE + + f228f401-6c74-4359-8ff1-b15b675180a4_Decl_LineIds + + + 11 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7 + 00000000-0000-0000-0000-000000000000 + atMBExceptionCodes + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 2db5746d-d284-4425-9f7f-2663a34b0ebc + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590519807 + + + + + {attribute 'qualified_only'} +// {attribute 'strict'} NOT strict so can set with modbusFB.ExceptionCodes. +TYPE atMBExceptionCodes : +( + // Local / transport-level (non-Modbus) result + TIMEOUT := -1, // Communication timeout / no response + + // Modbus exception codes (returned in exception response PDU) + RESPONSE_SUCCESS := 16#00, // Everything OK (no exception) + + ILLEGAL_FUNCTION := 16#01, // Function code not supported by slave + ILLEGAL_DATA_ADDRESS := 16#02, // Data address not valid for slave (out of range / not mapped) + ILLEGAL_DATA_VALUE := 16#03, // Data value not valid (bad length/value/etc.) + SLAVE_DEVICE_FAILURE := 16#04, // Unrecoverable error while performing the requested action + + ACKNOWLEDGE := 16#05, // Request accepted; processing started (use to avoid master timeout) + SLAVE_DEVICE_BUSY := 16#06, // Slave busy (e.g. long operation in progress); retry later + + MEMORY_PARITY_ERROR := 16#08, // Memory parity error (special case; historically for FC 20/21) + + GATEWAY_PATH_UNAVAILABLE := 16#0A, // Gateway problem: path unavailable / misconfigured / overloaded + GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND := 16#0B // Gateway problem: target device did not respond +); +END_TYPE + + 43f52a7a-cd45-4c0a-8652-9b18c69324d7_Decl_LineIds + + + 78 + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + 00000000-0000-0000-0000-000000000000 + atMbClient + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639251309599709549 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-11-23 ATM Cleaned up code +//2024-12-23 CWD Add statistics +//2025-06-18 CWD Add Get priorities and interval working together +//2026-03-26 ATM Clean up code, more testing +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +// ===== Main function call ===== + +IF (in_resetStats) THEN + resetStats(); + in_resetStats := FALSE; +END_IF + +// ===== Connect and disconnect logic ===== + +//Timer to trigger a connection if not connected for to long +reconnectTimer( + IN := NOT client.xConnect AND NOT client.xConnected AND NOT client.xError, + PT := RECONNECT_DELAY +); + +//Timer to trigger a disconnection if connect fails +disconnectTimer( + IN:=client.xConnect AND NOT client.xConnected, + PT:=DISCONNECT_DELAY +); + +//All requests offline timer +allRequestsOfflineTON( + IN := allRequestsOffline AND client.xConnected, + PT := ALL_OFFLINE_DELAY +); + +//Destroy Client delay timer +recreateClientDly( + IN:=destroyClient, + PT:=RECREATE_CLIENT_DELAY +); + +IF disconnectTimer.Q OR allRequestsOfflineTON.Q OR client.xError OR recreateTest THEN + //All requests are offline + //Connection attempt failed so disconnect + //If Client has error then disconnect + connect := FALSE; + recreateTest := FALSE; + destroyClient := TRUE; + setAllRequestsOffline(); +ELSIF reconnectTimer.Q AND NOT destroyClient THEN +//If not connected then attempt connect + connect := TRUE; + connectCnt := connectCnt + 1; //for debugging +END_IF; + + +IF recreateClientDly.Q THEN + destroyClient := FALSE; + recreateClient(); +END_IF; + +// If some reason gets stuck at busy, (This shouldnt happen, but force a reconnect). +forceConnect( + in := connect <> client.xConnect, + pt := FORCE_RECONNECT_DELAY +); + +// Only flip status of xConnect if no current request is active. +IF (connect <> client.xConnect) AND_THEN (allRequestsComplete OR forceConnect.Q) THEN + client.xConnect := connect; +END_IF + +// Moved this lower down, so xConnect happens one scan sooner. +client(); + +// If there are no requests, dont neeed to even do anything. +IF requestCnt = 0 THEN + numMsgSent := 0; + numMsgReply := 0; + allRequestsOnline := 0; + allRequestsOffline := 0; + RETURN; +END_IF + + +requestNotExecutingTON(IN := NOT requestExecuting, PT := NEXT_REQUEST_DELAY); //Only delaying 1 ms. This may not do much, was put in during testing, but now it almost seems unnecesary + +// ===== Execute next request ===== +IF connect AND client.xConnect AND client.xConnected AND (requestNotExecutingTON.Q OR NEXT_REQUEST_DELAY = T#0MS) THEN + + //Find the lowest priority of overdue requests + nextIdx := -1; + priorityLowest := PRIORITY_NONE; + highestTimeSinceRun := 0; + FOR i := 0 TO requestCnt - 1 DO + IF (requests[i]^.in_priorityCurrent < priorityLowest + OR (requests[i]^.in_priorityCurrent = priorityLowest AND requests[i]^.stats.sinceLastExec > highestTimeSinceRun)) + AND requests[i]^.stats.sinceLastExec > TIME_TO_UDINT(requests[i]^.in_minDelay) THEN + priorityLowest := requests[i]^.in_priorityCurrent; + highestTimeSinceRun := requests[i]^.stats.sinceLastExec; + nextIdx := i; + END_IF + END_FOR + + //Decriment all request priorities by one. + IF priorityLowest < PRIORITY_NONE AND priorityLowest > 0 THEN //Only if priority + FOR i := 0 TO requestCnt - 1 DO + requests[i]^.in_priorityCurrent := requests[i]^.in_priorityCurrent - 1; + IF requests[i]^.in_priorityCurrent < 0 THEN + requests[i]^.in_priorityCurrent := 0; + END_IF + END_FOR + END_IF + + IF nextIdx >= 0 THEN + activeIndex := nextIdx; + END_IF + END_IF + +processAllRequests(); + +allRequestsOnline := (activeReqCnt > 0) AND (onlineCnt = activeReqCnt); +allRequestsOffline := (activeReqCnt > 0) AND (onlineCnt = 0); + +// ===== Client statistics ===== +//Get a sum of done and error counts from all requests +reqErrSum := 0; +reqDoneSum := 0; +FOR i := 0 TO requestCnt -1 DO + reqDoneSum := reqDoneSum + requests[i]^.stats.doneCnt; + reqErrSum := reqErrSum + requests[i]^.stats.errorCnt; + + //copy stats to array for debugging + reqStats[i] := ADR(requests[i]^.stats); +END_FOR + + + +//Every time the tx message counter changes increment msg count and add last transaction time +IF client.udiNumMsgSent <> msgCountLast THEN + msgCountLast := client.udiNumMsgSent; + msgTimeSum := msgTimeSum + client.udiLastTransactionTime; + msgCountSum := msgCountSum + 1; +END_IF + +//Every 1 second compute statistics +pulse(); +IF pulse.T_Sec_OS THEN + msgLastSecondCnt := msgCountSum; + msgCountSum := 0; + msgLastSecondTime := msgTimeSum; + msgTimeSum := 0; +END_IF + +numMsgSent := client.udiNumMsgSent; +numMsgReply := client.udiNumMsgReply; + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_timeout: TIME := T#0MS; // If this is set, this will be used on requests, otherwise it will be calculated from in_dataRate. + {attribute 'symbol' := 'none'} + in_resetStats: BOOL; // Set this bool to true to reset statistics. +END_VAR +VAR_OUTPUT + {attribute 'symbol' := 'none'} + msgLastSecondCnt: UDINT; //Messages in the last second + {attribute 'symbol' := 'none'} + msgLastSecondTime: UDINT; //Total amount of time for all messages sent last second in ms + {attribute 'symbol' := 'none'} + numMsgSent: UDINT; + {attribute 'symbol' := 'none'} + numMsgReply: UDINT; + {attribute 'symbol' := 'none'} + allRequestsOnline: BOOL; //All requests are working + //{attribute 'symbol' := 'none'} + reqStats: ARRAY[0..99] OF POINTER TO atMbRequestStats; // For debugging modbus requests +END_VAR +VAR + {attribute 'symbol' := 'none'} + datarate: DINT := 0; //Bits per second data rate. Used to calculate reply timeout. Based on amount of time to transmit holding register read for max amount of registers. Default 154705 for Modbus TCP = 17ms to transmit 2650 bits + {attribute 'symbol' := 'none'} + client: REFERENCE TO ModbusFB.Client; // This is generated internally, no need to set. + {attribute 'symbol' := 'none'} + connectCnt: DINT; //Number of connection attempts + {attribute 'symbol' := 'none'} + initDone: BOOL; + {attribute 'symbol' := 'none'} + disconnectTimer: TON; //Connecting but not connected delay + {attribute 'symbol' := 'none'} + reconnectTimer: TON; //Not connecting and not connected delay + {attribute 'symbol' := 'none'} + msgCountLast: UDINT; //Last message count + {attribute 'symbol' := 'none'} + msgCountSum: UDINT; //Message count accumulator + {attribute 'symbol' := 'none'} + msgTimeSum: UDINT; //Amount of time for messsages accumulator + {attribute 'symbol' := 'none'} + pulse: atPulse; //Pulse generator + {attribute 'symbol' := 'none'} + requests: ARRAY[0..99] OF POINTER TO atMbReqBase; //list of request to direct + {attribute 'symbol' := 'none'} + requestCnt: UINT; //Number of active requests + {attribute 'symbol' := 'none'} + reqDoneSum: UDINT; //Count of done result on all requests + {attribute 'symbol' := 'none'} + reqErrSum: UDINT; //Count of error result on all requests + {attribute 'symbol' := 'none'} + i: DINT; //Loop variable + {attribute 'symbol' := 'none'} + nextIdx: DINT := -1; //Index of the active request + {attribute 'symbol' := 'none'} + priorityLowest: INT; //lowest request priority + {attribute 'symbol' := 'none'} + highestTimeSinceRun: UDINT; //lowest request priority + {attribute 'symbol' := 'none'} + allRequestsOfflineTON: TON; //All requests are offline delay + {attribute 'symbol' := 'none'} + requestNotExecutingTON: TON; //Delay after there are no requests execting + {attribute 'symbol' := 'none'} + recreateClientDly: TON; //Delay to make new client after force fail + {attribute 'symbol' := 'none'} + allRequestsOffline: BOOL; //All requests are offline + {attribute 'symbol' := 'none'} + connect: BOOL; // this is more of a "Request to connect, Requet to disconnect", will drop xConnect once all requests are done, wont start requests unless this is true. + {attribute 'symbol' := 'none'} + forceConnect: TON; //If after 5 seconds, all requests are still ofline, just force a reconnect. + test1: REAL; + test2: REAL; + destroyClient: BOOL; + recreateTest: BOOL; +END_VAR +VAR CONSTANT + {attribute 'symbol' := 'none'} + NEXT_REQUEST_DELAY: TIME := T#0MS; // Dont seem to need a delay + {attribute 'symbol' := 'none'} + RECONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + DISCONNECT_DELAY: TIME := T#500MS; + {attribute 'symbol' := 'none'} + RECREATE_CLIENT_DELAY: TIME := T#1000MS; + {attribute 'symbol' := 'none'} + ALL_OFFLINE_DELAY: TIME := T#2S; + {attribute 'symbol' := 'none'} + FORCE_RECONNECT_DELAY: TIME := T#1S; + {attribute 'symbol' := 'none'} + PRIORITY_NONE: INT := 32767; +END_VAR + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa_Decl_LineIds + + + 3369 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + 00000000-0000-0000-0000-000000000000 + atMbClientRTU + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649591897 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-13 CWD Fix problem with not connecting properly on power up +//2024-11-15 ATM Move from structure to defaults. +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +dataRate := in_baudRate; + + +// ===== Initialization ===== +// if the param changes, force a reinit +IF _client^.iPort <> in_port + OR _client^.dwBaudrate <> in_baudRate + OR _client^.byDataBits <> in_dataBits + OR _client^.eParity <> in_parity + OR _client^.eStopBits <> in_stopBits THEN + initDone := FALSE; +END_IF; + +IF NOT initDone THEN + initDone := TRUE; + + //Other configuration parameters + _client^.iPort := in_port; + _client^.dwBaudrate := in_baudRate; + _client^.byDataBits := in_dataBits; + _client^.eParity := in_parity; + _client^.eStopBits := in_stopBits; + _client^.eDTRcontrol := 0; + _client^.eRTScontrol := 0; + _client^.eRTUASCII := 0; + _client^.udiLogOptions := ModbusFB.LoggingOptions.All; + _client^.xConnect:=FALSE; + +END_IF + +SUPER^(); + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientRTU EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_port: BYTE := 0; //0=None, 1,2,3,4 + {attribute 'symbol' := 'none'} + in_baudRate: UINT := 9600; //4800, 9600, 19200, 38400, 57600, 115200 + {attribute 'symbol' := 'none'} + in_parity: BYTE := 0; //0=None, 1=Odd, 2=Even + {attribute 'symbol' := 'none'} + in_dataBits: BYTE := 8; //8=8 + {attribute 'symbol' := 'none'} + in_stopBits: BYTE := 1; //1=OneStopBit, 2=One5StopBits, 3=TwoStopBits +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientSerial; +END_VAR + 17c2ab7a-4d60-48ec-966f-339ec83a75e9_Decl_LineIds + + + 607 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + 00000000-0000-0000-0000-000000000000 + atMbClientTCP + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649747183 + + + None + + + //Modbus connection +//Connects to a modbus slave +//2024-06-27 CWD Initial create +//2024-11-15 ATM Cleanup +//2024-11-18 CWD Fix problem with not connecting properly on power up +//2024-11-19 CWD More fixing on maintaining a connection +//2024-11-23 ATM Cleaned up code +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +IF NOT setupClient() THEN + RETURN; +END_IF + +//Set dataRate +//This is based on an 8 byte request and a 255 byte reply +IF (in_baudRate <> 0) THEN + dataRate := in_baudRate; +ELSE + dataRate := 105200; +END_IF + + +// ===== Initialization ===== +// if the ip address or port changes, force a reinit +IF _client^.aIPaddr[0] <> in_ipAddress[0] + OR _client^.aIPaddr[1] <> in_ipAddress[1] + OR _client^.aIPaddr[2] <> in_ipAddress[2] + OR _client^.aIPaddr[3] <> in_ipAddress[3] + OR _client^.uiPort <> in_port THEN + initDone := FALSE; +END_IF; + + +// These are outside init so that the get updated on reconnect. +IF NOT initDone THEN + initDone := TRUE; + + // Setting the values, instead of copying the array, making the above compare possible. + _client^.aIPaddr[0] := in_ipAddress[0]; + _client^.aIPaddr[1] := in_ipAddress[1]; + _client^.aIPaddr[2] := in_ipAddress[2]; + _client^.aIPaddr[3] := in_ipAddress[3]; + _client^.uiPort := in_port; + + _client^.udiLogOptions := ModbusFB.LoggingOptions.None; + _client^.tConnnectTimeOut := T#100H; + _client^.xConnect := FALSE; +END_IF; + +client REF= _client^; + +SUPER^(); + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbClientTCP EXTENDS atMbClient +VAR_INPUT + {attribute 'symbol' := 'none'} + in_ipAddress: ARRAY[0..3] OF BYTE; // TCP IP Address + {attribute 'symbol' := 'none'} + in_port: UINT := 502; // TCP port + {attribute 'symbol' := 'none'} + in_baudrate: UINT := 0; // Set this if using RS485 to TCP Converter, used to calculate timeout. +END_VAR +VAR + {attribute 'symbol' := 'none'} + _client: POINTER TO ModbusFB.ClientTCP; +END_VAR + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570_Decl_LineIds + + + 1168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + 00000000-0000-0000-0000-000000000000 + atMbReqBase + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649837310 + + + None + + + //Modbus request +//Makes a request using the start and quantity, puts the result into data +//2024-06-27 CWD Initial create +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage + +IF NOT __ISVALIDREF(client) THEN + RETURN; +END_IF + + +//Add this request to the clients request array +client.addRequest(THIS); + +//fault logic +//Fault if error +//errTrig(CLK := stats.reqError); +//IF errTrig.Q THEN +// out_fault := TRUE; +//END_IF +//not faulted if a succesfull request +//doneTrig(CLK := stats.reqDone); +// also clear fault if not active +//IF doneTrig.Q OR NOT stats.active THEN +// out_fault := FALSE; +//END_IF + +IF in_quantity <= dataSize THEN + stats.reqLength := in_quantity; +ELSE + stats.reqLength := dataSize; +END_IF; + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK ABSTRACT atMbReqBase +VAR_INPUT + {attribute 'symbol' := 'none'} + client: REFERENCE TO atMbClient; // Instance of atMBClient + {attribute 'symbol' := 'none'} + in_startAddr: UINT; + {attribute 'symbol' := 'none'} + in_quantity: UINT; + {attribute 'symbol' := 'none'} + in_dataOffset: UINT; // Offset position for data into array. + {attribute 'symbol' := 'none'} + in_unitId: BYTE := 0; + {attribute 'symbol' := 'none'} + in_timeout: SYSTIME := 0; // timeout in ms, if not set, will be 1ms less than interval. + {attribute 'symbol' := 'none'} + in_minDelay: TIME := T#10MS; // This is the minimum delay between queries, only relevent if high priority. + {attribute 'symbol' := 'none'} + in_prioritySetpoint: INT := 50; // this requests priority compared to other requests. Lower value is higher priority. + {attribute 'symbol' := 'none'} + in_priorityCurrent: INT; //Current priority. This is decrimented and the request is executed when at zero. + {attribute 'symbol' := 'none'} + stats: atMbRequestStats; //Request stats + +END_VAR +VAR_OUTPUT + //{attribute 'symbol' := 'none'} + //out_fault: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + dataSize: UINT; + //{attribute 'symbol' := 'none'} + //quantUsed: UINT; + //{attribute 'symbol' := 'none'} + //doneTrig: R_TRIG; //Succesful request trigger + //{attribute 'symbol' := 'none'} + //errTrig: R_TRIG; //Error request result trigger + + {attribute 'symbol' := 'none'} + request: REFERENCE TO ModbusFB.ClientRequest;; +END_VAR + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37_Decl_LineIds + + + 3210 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + 00000000-0000-0000-0000-000000000000 + atMbReqReadCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146913881 + + + None + + + //Modbus request input registers +//2024-11-16 CWD Added read coil +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readCoilReq: POINTER TO ModbusFB.ClientRequestReadCoils; +END_VAR + + a77a90fd-541d-4ce9-ad97-7024224bbab4_Decl_LineIds + + + 193 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 1a3278f4-a658-407c-968a-21aa53361562 + 00000000-0000-0000-0000-000000000000 + atMbReqReadDiscrete + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147014015 + + + None + + + //Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 1a3278f4-a658-407c-968a-21aa53361562_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadDiscrete EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readDiscreteReq: POINTER TO ModbusFB.ClientRequestReadDiscreteInputs; +END_VAR + + 1a3278f4-a658-407c-968a-21aa53361562_Decl_LineIds + + + 184 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 73b10819-9112-4811-9583-e2735d2b13ea + 00000000-0000-0000-0000-000000000000 + atMbReqReadHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147099228 + + + None + + + //Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + 73b10819-9112-4811-9583-e2735d2b13ea_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readHoldReq: POINTER TO ModbusFB.ClientRequestReadHoldingRegisters; +END_VAR + + 73b10819-9112-4811-9583-e2735d2b13ea_Decl_LineIds + + + 173 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + 00000000-0000-0000-0000-000000000000 + atMbReqReadInput + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147179217 + + + None + + + //Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqReadInput EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + readInputReq: POINTER TO ModbusFB.ClientRequestReadInputRegisters; +END_VAR + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866_Decl_LineIds + + + 167 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + 00000000-0000-0000-0000-000000000000 + atMbRequestStats + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147425423 + + + None + + + SysTimeGetUs(TSSus); + +IF (reqLength = 0) THEN + active := FALSE; + doneCnt := 0; + errorCnt := 0; + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + execute := FALSE; + online := FALSE; + offline := FALSE; + reqDone := FALSE; + reqBusy := FALSE; + reqError := FALSE; + sinceLastExec := 0; + RETURN; +END_IF +active := TRUE; + +inDoneDeb(in_raw := reqDone, in_delOn := T#0S, in_DelOff := lightOn); +reqDoneLight := inDoneDeb.out_val; + +inBusyDeb(in_raw := reqBusy, in_delOn := T#0S, in_DelOff := lightOn); +reqBusyLight := inBusyDeb.out_val; + +inErrDeb(in_raw := reqError, in_delOn := T#0S, in_DelOff := lightOn); +reqErrorLight := inErrDeb.out_val; + +//Debounced execute indicator for debbugging +executeDeb(in_raw := _execute, in_delOn := T#0S, in_DelOff := lightOn); +executeLight := executeDeb.out_val; + + +//Set online status +IF doneCntRst >=3 THEN + online := TRUE; + offline := FALSE; +END_IF + +//Set online status +IF errorCntRst >= 3 THEN + online := FALSE; + offline := TRUE; +END_IF + +//Record start time in ms on execute rising edge +executeTrig(CLK := execute); +IF executeTrig.Q THEN + interval := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + lastReqStart := TSSus; +END_IF + +//Offline count +offlineTrig(CLK := NOT online); +IF offlineTrig.Q THEN + offlineCnt := offlineCnt + 1; +END_IF + +//Calculate ms since last execute +//If the last request start is more then 1 minute ago then set it for 1 minute +IF lastReqStart < TSSus - 60000000 THEN + lastReqStart := TSSus - 60000000; +END_IF +sinceLastExec := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Impl_LineIds + + + + + {attribute 'monitoring_display' := 'toString'} +{attribute 'no_assign'} +FUNCTION_BLOCK atMbRequestStats +VAR_INPUT + lastErrorId: modbusFB.Error; //Last error id + lastExceptionCode: atMBExceptionCodes; //Last modbus exception code. These are the standard modbus exceptions. + lastValidDuration: UDINT; // How long last transaction took (ms) + longestValidDuration: UDINT; //Longest valid duration (ms) + interval: UDINT; // Start-to-start interval (ms) + offlineCnt: UINT; //Count of how many times this request has gone offline + doneCnt: UDINT; // Count of successful request + errorCnt: UDINT; // Count of failed requests + doneCntRst: UDINT; // Count of succesful requests, resets on error result + errorCntRst: UDINT; // Count of failed requests, resets on succesful request + sinceLastExec: UDINT; // Time since the last start (ms) + timeoutCnt: UDINT; + reqLength: UINT; //Number of registers requested in request, used to calc active. +END_VAR +VAR_OUTPUT + executeLight: BOOL; //Execute that stays on momentarily + reqDoneLight: BOOL; //Request done that stays on momentarily + reqErrorLight: BOOL; //Request error that stays on momentarily + reqBusyLight: BOOL; //Request busy that stays on momentarily + online: BOOL; //Online means the requests are being replied to normally or with modbus exceptions. Communications are ok. + offline: BOOL; //Opposite of online, but starts out as false on program start. + active: BOOL; // This is true if its actually being used, and not having a count of 0 on the request. + executeMbFb: BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + inDoneDeb: atDebounce; + {attribute 'symbol' := 'none'} + inErrDeb: atDebounce; + {attribute 'symbol' := 'none'} + inBusyDeb: atDebounce; + {attribute 'symbol' := 'none'} + executeDeb: atDebounce; + toString: STRING(20); + {attribute 'symbol' := 'none'} + executeTrig: R_TRIG; //Execute trigger + {attribute 'symbol' := 'none'} + offlineTrig: R_TRIG; //Offline trigger + {attribute 'symbol' := 'none'} + lastReqStart: SYSTIME; //time of start of last request + {attribute 'symbol' := 'none'} + TSSus: SYSTIME; + _execute: BOOL; //Execute trigger and status. Turn on to trigger request. Will turn off when request is complete + _reqDone: BOOL; //Request done + _reqBusy: BOOL; //Request busy + _reqError: BOOL; //Request error +END_VAR + +VAR CONSTANT + {attribute 'symbol' := 'none'} + lightOn: TIME:= T#100MS; //Light on time for debug indicators +END_VAR + 48b508fd-2131-484c-ba4a-20e61e66a5b2_Decl_LineIds + + + 286 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + 00000000-0000-0000-0000-000000000000 + atMbReqWriteCoil + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147259331 + + + None + + + //Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + f6ee860b-0951-41d0-93d8-6436c681d8f1_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteCoil EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF BOOL to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF BOOL; +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeCoilsReq: POINTER TO ModbusFB.ClientRequestWriteMultipleCoils; +END_VAR + + f6ee860b-0951-41d0-93d8-6436c681d8f1_Decl_LineIds + + + 206 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + bb7e18ba-2018-4681-875e-de1f291019ef + 00000000-0000-0000-0000-000000000000 + atMbReqWriteHolding + + + + 24568a24-c491-472c-a21f-ee5d33859fab + + + + 1024 + False + False + False + + False + + + + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147339348 + + + None + + + //Modbus request input registers +//2024-06-27 CWD Initial Create +//2024-10-21 CWD Set udiTimeout to 0 as this was causing problems with detecting an actual no reply timeout from the slave +//2024-11-15 ATM Moved from Work to main for easier debugging +//2024-11-23 ATM Cleaned up code, added start array position +//2025-10-28 CWD Updated to reduce symbol usage +//2026-04-03 ATM Now Client makes the requests, calling this just passes data. Dynamically allocated requests + + +//Verify data size is large enough, if not then only get as many registers that will fit. +dataSize := DINT_TO_UINT(UPPER_BOUND(data,1) - LOWER_BOUND(data,1) + 1); + +SUPER^(); + bb7e18ba-2018-4681-875e-de1f291019ef_Impl_LineIds + + + + + {attribute 'no_assign'} +FUNCTION_BLOCK atMbReqWriteHolding EXTENDS atMbReqBase +VAR_IN_OUT + // ARRAY[] OF UINT to hold received data + {attribute 'symbol' := 'none'} + data: ARRAY[*] OF UINT; +END_VAR +VAR_OUTPUT +END_VAR +VAR + {attribute 'symbol' := 'none'} + writeHoldReq: POINTER TO ModbusFB.ClientRequestWriteMultipleRegisters; +END_VAR + + bb7e18ba-2018-4681-875e-de1f291019ef_Decl_LineIds + + + 164 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9499c60e-03a2-49d0-b086-43990a018e20 + 00000000-0000-0000-0000-000000000000 + To_IPAddress + + + + 829a18f2-c514-4f6e-9634-1df173429203 + + + + + + + 21af5390-2942-461a-bf89-951aaf6999f1 + + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + + + + + + + 6f9dac99-8de1-4efc-8465-68ac443b7d08 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590489804 + + + None + + + //Convert parameters to array +To_IPAddress[0] := byte1; +To_IPAddress[1] := byte2; +To_IPAddress[2] := byte3; +To_IPAddress[3] := byte4; + + + + 9499c60e-03a2-49d0-b086-43990a018e20_Impl_LineIds + + + + + FUNCTION To_IPAddress : ARRAY[0..3] OF BYTE +VAR_INPUT + byte1: BYTE; + byte2: BYTE; + byte3: BYTE; + byte4: BYTE; +END_VAR + + 9499c60e-03a2-49d0-b086-43990a018e20_Decl_LineIds + + + 168 + Standard + + False + + 8fc52b24-7436-4d4a-8b03-aa43d42302d0 + + atomLibrary + 6-Comms + ModbusMaster + + -1 + + + False + + 9e99e368-853f-485b-911f-d9a2a4e46434 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + addRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146468437 + + + + + //Need a way to only add the request 1 time. Add a value to the requestbase to indicate its been added to the director +IF requestCnt >= UPPER_BOUND(requests,1) THEN + RETURN; +END_IF + +FOR i := 0 TO requestCnt DO + IF request = requests[i] THEN + RETURN; + END_IF +END_FOR + +requests[requestCnt] := request; +requestCnt := requestCnt + 1; + 9e99e368-853f-485b-911f-d9a2a4e46434_Impl_LineIds + + + + + //Adds the specified request to the global list +METHOD PUBLIC addRequest +VAR_INPUT + request: POINTER TO atMbReqBase; +END_VAR +VAR + i: UINT := 0; + +END_VAR + + 9e99e368-853f-485b-911f-d9a2a4e46434_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 685f7c69-c46a-4b34-b5e2-fb848c593f3e + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + processAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148036583 + + + + + FOR i := 0 TO requestCnt -1 DO + requests[i]^.processRequest(); +END_FOR; + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Impl_LineIds + + + + + METHOD processAllRequests +VAR_INPUT +END_VAR +VAR + i: UINT; // Loop bstisnlr +END_VAR + 685f7c69-c46a-4b34-b5e2-fb848c593f3e_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 39769d9e-08c1-472d-82ef-25c66891a065 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateAllRequests + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148056585 + + + + + FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + requests[i]^.recreateRequest(); +END_FOR; + 39769d9e-08c1-472d-82ef-25c66891a065_Impl_LineIds + + + + + METHOD PROTECTED recreateAllRequests +VAR_INPUT +END_VAR + + 39769d9e-08c1-472d-82ef-25c66891a065_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648119824 + + + + + + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Impl_LineIds + + + + + METHOD ABSTRACT recreateClient +VAR_INPUT +END_VAR + 60fd3bf3-849f-4af3-a1a5-18a73fd7b7fc_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + a7e27967-573f-4f9f-86e7-dd569b8cd943 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148096636 + + + + + connectCnt := 0; +val := ADR(client.udiNumMsgSent); +val^ := 0; +val := ADR(client.udiNumMsgReply); +val^ := 0; +FOR i := 0 TO requestCnt -1 DO + requests[i]^.stats.resetStats(); +END_FOR; + a7e27967-573f-4f9f-86e7-dd569b8cd943_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR +VAR + i: DINT; + val: POINTER TO UDINT; +END_VAR + + + + + a7e27967-573f-4f9f-86e7-dd569b8cd943_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + ca1dfc70-ad93-43de-886d-39e56897aa6d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setAllRequestsOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148106585 + + + + + FOR i := 0 TO requestCnt -1 DO + requests[i]^.stats.setClientOffline(); +END_FOR; + ca1dfc70-ad93-43de-886d-39e56897aa6d_Impl_LineIds + + + + + METHOD PRIVATE setAllRequestsOffline : BOOL +VAR_INPUT +END_VAR +VAR + i: DINT; +END_VAR + + + + + ca1dfc70-ad93-43de-886d-39e56897aa6d_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + d12c9bf2-320f-4e95-badb-68a2357c5783 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574648239962 + + + + + // Only create request if doesnt exist +IF NOT __ISVALIDREF(client) THEN + recreateClient(); + IF NOT __ISVALIDREF(client) THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +setupClient := TRUE; + d12c9bf2-320f-4e95-badb-68a2357c5783_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT +END_VAR + + d12c9bf2-320f-4e95-badb-68a2357c5783_Decl_LineIds + + + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeIndex + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590625089 + + + + + PROPERTY activeIndex : DINT + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + activeReqCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502146293051 + + + + + PROPERTY PRIVATE activeReqCnt : UINT + 456b4953-57c9-4f76-ab5b-0db456dd9b58_Decl_LineIds + + + 1 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + allRequestsComplete + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639108502147921016 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE allRequestsComplete : BOOL + 4d9d378a-f256-4f23-b835-539ee9e287c3_Decl_LineIds + + + 2 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + onlineCnt + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590755417 + + + + + {attribute 'symbol' := 'none'} +PROPERTY onlineCnt : DINT//Count of requests that are online + 89742962-f9b6-46c2-b9a5-b7e298f8495d_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 27292866-0bc0-4848-a965-64c51a0bf960 + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + requestExecuting + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841590845466 + + + + + {attribute 'symbol' := 'none'} +PROPERTY PRIVATE requestExecuting : BOOL //There is at least one request executing + 27292866-0bc0-4848-a965-64c51a0bf960_Decl_LineIds + + + 3 + + bc7fa96e-8ba3-4e3e-9cec-4d50ab57e6fa + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + + -1 + + + False + + 4950a3a1-f54f-434d-9252-b61a6cb4663d + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649617058 + + + + + old_client_id := _client; +_client := __NEW(ModbusFB.ClientSerial); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Impl_LineIds + + + + + METHOD recreateClient +VAR_INPUT + old_client_id: POINTER TO ModbusFB.ClientSerial; +END_VAR + + 4950a3a1-f54f-434d-9252-b61a6cb4663d_Decl_LineIds + + + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + e8c6c80b-7bdb-4108-987e-4af107de4377 + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + toString + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650142720 + + + + + {attribute 'symbol' := 'none'} +PROPERTY toString : atMbClientToString //Client request status noneOk = no requests working, someOk = some requests are working some are failing, allOk = all requests are working + e8c6c80b-7bdb-4108-987e-4af107de4377_Decl_LineIds + + + 3 + + 17c2ab7a-4d60-48ec-966f-339ec83a75e9 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + + -1 + + + False + + 79dfe91d-6dbc-46b0-866c-4b880e44e02b + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + recreateClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574649757233 + + + + + old_client_id := _client; +_client := __NEW(ModbusFB.ClientTCP); +IF _client = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; +END_IF; +client REF= _client^; +connect := FALSE; + +IF old_client_id <> 0 THEN + __DELETE(old_client_id); + old_client_id := 0; +END_IF + +recreateAllRequests(); + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Impl_LineIds + + + + + METHOD recreateClient +VAR + old_client_id: POINTER TO ModbusFB.ClientTCP; +END_VAR + + 79dfe91d-6dbc-46b0-866c-4b880e44e02b_Decl_LineIds + + + + 269ad87c-8ffe-4a28-9e12-2f2da3f7c570 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientTCP + + -1 + + + False + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + busy + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148176670 + + + + + stats.reqBusy := in; + +IF NOT in THEN + RETURN; +END_IF; + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Impl_LineIds + + + + + METHOD busy : BOOL +VAR_INPUT + in: BOOL; +END_VAR +VAR + executeTime: ULINT; + replyTimeoutTime: ULINT; +END_VAR + + + a77f3143-e2cf-48a4-bafd-a0c18a11faeb_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 5350a8fa-8297-42db-b185-fcb966e45684 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + completeRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148196661 + + + + + done(request.xDone); +err(in := request.xError, errorId := request.eErrorId, exceptionCode := request.eException); +busy(in := request.xBusy); +stats(); + +IF (atGVL.reset) THEN + // request.AbortModel(); + request.ResetModel(TRUE); +END_IF + 5350a8fa-8297-42db-b185-fcb966e45684_Impl_LineIds + + + + + METHOD completeRequest +VAR_INPUT +END_VAR + + 5350a8fa-8297-42db-b185-fcb966e45684_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + done + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148206667 + + + + + stats.reqDone := in; + +IF NOT in THEN + RETURN; +END_IF + +//Set priority for next request +in_priorityCurrent := in_prioritySetpoint; + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Impl_LineIds + + + + + METHOD done +VAR_INPUT + // Done state + in: BOOL; +END_VAR + + 3bfc4f72-6ee6-4e83-9bf3-99339b388eac_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + err + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148226656 + + + + + stats.reqError := in; + +IF NOT in THEN + RETURN; +END_IF + + +//Update error info +stats.lastErrorId := errorId; +stats.lastExceptionCode := ANY_TO_BYTE(exceptionCode); + +//Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. +//IF stats.lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + //Since there was no response from the device set the priority to higher number priority +// in_priorityCurrent := in_prioritySetPoint * 5; +//END_IF + +//Set priority for next request, priority gets higher after each error result +priorityMultiplier := UDINT_TO_INT(stats.errorCntRst); +IF priorityMultiplier > 3 THEN + priorityMultiplier := 3; +END_IF +// set multiplier to exponential retry. +in_priorityCurrent := 10 * priorityMultiplier * priorityMultiplier; +//in_priorityCurrent := in_prioritySetpoint * priorityMultiplier; + +// NO idea what Reset Model does, hoping it fixes comms issues +// request.ResetModel(TRUE); // I dont think this helped + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Impl_LineIds + + + + + METHOD err +VAR_INPUT + // Error state + in: BOOL; + errorId: modbusFB.Error; //modbusFB error ID + exceptionCode: modbusFB.ExceptionCodes; //ModbusFB exception code +END_VAR +VAR + priorityMultiplier: INT; //Raise priority when not response +END_VAR + + d7c69d5d-5192-4bf1-96fc-9ef0da66a62c_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 109d8e00-bf23-4306-a8d5-764aa6a03235 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148246723 + + + + + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Impl_LineIds + + + + + METHOD ABSTRACT processRequest +VAR_INPUT +END_VAR + + 109d8e00-bf23-4306-a8d5-764aa6a03235_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148266663 + + + + + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Impl_LineIds + + + + + METHOD ABSTRACT recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + f64e9d26-0b0d-4e83-b6b3-1cfb43d906f8_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + db40c392-b461-4198-851d-85857ed98ccf + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + setupClient + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148276644 + + + + + // Only create request if doesnt exist +IF in_request = 0 THEN + in_request := recreateRequest(); + IF in_request = 0 THEN + atGVL.dynamicAllocationFail.in_raw := TRUE; + setupClient := FALSE; + RETURN; + END_IF +END_IF + +IF NOT __ISVALIDREF(mbsClient) THEN + setupClient := FALSE; + RETURN; +END_IF + +request REF= in_request^; + +setupClient := TRUE; +RETURN; + db40c392-b461-4198-851d-85857ed98ccf_Impl_LineIds + + + + + METHOD setupClient : BOOL +VAR_INPUT + in_request: POINTER TO ModbusFB.ClientRequest; +END_VAR + + db40c392-b461-4198-851d-85857ed98ccf_Decl_LineIds + + + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 6159c037-e399-466d-8108-0c4f6570f9b2 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + mbsClient + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639207574650298004 + + + + + PROPERTY PROTECTED mbsClient : REFERENCE TO ModbusFB.Client + 6159c037-e399-466d-8108-0c4f6570f9b2_Decl_LineIds + + + 1 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + replyTimeout + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639034841591196333 + + + + + //Calculates the reply timout setting +PROPERTY PROTECTED replyTimeout : UDINT + 4f361885-f95c-4244-84d6-8c3b6b9b8a18_Decl_LineIds + + + 2 + + 3ad7cda5-e8b7-44c8-b264-165eb4cd7d37 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + + -1 + + + False + + ea5277e9-2ae8-417f-a88d-b571902c7d71 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699321592 + + + + + IF NOT setupClient(readCoilReq) THEN + RETURN; +END_IF + +//Do request processing +readCoilReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + ea5277e9-2ae8-417f-a88d-b571902c7d71_Impl_LineIds + + + + + METHOD processRequest + + ea5277e9-2ae8-417f-a88d-b571902c7d71_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3 + a77a90fd-541d-4ce9-ad97-7024224bbab4 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148331930 + + + + + IF readCoilReq <> 0 THEN + __DELETE(readCoilReq); + readCoilReq := 0; +END_IF +readCoilReq := __NEW(ModbusFB.ClientRequestReadCoils); +recreateRequest := readCoilReq; + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 2c4e80c5-f4e7-4d01-97ec-5317c7e780b3_Decl_LineIds + + + + a77a90fd-541d-4ce9-ad97-7024224bbab4 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadCoil + + -1 + + + False + + 9df094f9-a173-4520-816f-7badcbb1bd34 + 1a3278f4-a658-407c-968a-21aa53361562 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639163341699461941 + + + + + IF NOT setupClient(readDiscreteReq) THEN + RETURN; +END_IF + +//Do request processing +readDiscreteReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 9df094f9-a173-4520-816f-7badcbb1bd34_Impl_LineIds + + + + + METHOD processRequest + + 9df094f9-a173-4520-816f-7badcbb1bd34_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + d34866d4-1134-4650-b3d6-2e19d2fd09d1 + 1a3278f4-a658-407c-968a-21aa53361562 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148362063 + + + + + IF readDiscreteReq <> 0 THEN + __DELETE(readDiscreteReq); + readDiscreteReq := 0; +END_IF +readDiscreteReq := __NEW(ModbusFB.ClientRequestReadDiscreteInputs); +recreateRequest := readDiscreteReq; + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + d34866d4-1134-4650-b3d6-2e19d2fd09d1_Decl_LineIds + + + + 1a3278f4-a658-407c-968a-21aa53361562 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadDiscrete + + -1 + + + False + + 7b978176-d206-4509-b9d8-7908d33a9a74 + 73b10819-9112-4811-9583-e2735d2b13ea + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148402056 + + + + + IF NOT setupClient(readHoldReq) THEN + RETURN; +END_IF + +//Do request processing +readHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + // udiReplyTimeout := 1 * 1000 * 1000, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 7b978176-d206-4509-b9d8-7908d33a9a74_Impl_LineIds + + + + + METHOD processRequest + + 7b978176-d206-4509-b9d8-7908d33a9a74_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83 + 73b10819-9112-4811-9583-e2735d2b13ea + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148422060 + + + + + IF readHoldReq <> 0 THEN + __DELETE(readHoldReq); + readHoldReq := 0; +END_IF +readHoldReq := __NEW(ModbusFB.ClientRequestReadHoldingRegisters); +recreateRequest := readHoldReq; + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 52ffdba7-bf4a-4746-924c-8b86df6d9a83_Decl_LineIds + + + + 73b10819-9112-4811-9583-e2735d2b13ea + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadHolding + + -1 + + + False + + 1004c837-fbef-45ee-986d-977343f4bd8f + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148442038 + + + + + IF NOT setupClient(readInputReq) THEN + RETURN; +END_IF + +//Do request processing +readInputReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 1004c837-fbef-45ee-986d-977343f4bd8f_Impl_LineIds + + + + + METHOD processRequest + + 1004c837-fbef-45ee-986d-977343f4bd8f_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605 + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148462074 + + + + + IF readInputReq <> 0 THEN + __DELETE(readInputReq); + readInputReq := 0; +END_IF +readInputReq := __NEW(ModbusFB.ClientRequestReadInputRegisters); +recreateRequest := readInputReq; + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + 9903b949-e1b9-4e0b-a98f-b6f745ad3605_Decl_LineIds + + + + a5dbc9b0-a1a1-4281-a685-ec85b35bb866 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqReadInput + + -1 + + + False + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + resetStats + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148542250 + + + + + doneCnt := 0; +doneCntRst := 0; +errorCnt := 0; +errorCntRst := 0; + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Impl_LineIds + + + + + METHOD resetStats +VAR_INPUT +END_VAR + + 94cb9a26-304d-46fc-a18d-a8ce0f1931ec_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + b1508d1f-b7a7-40b5-b74f-3091afc5b834 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setClientOffline + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148552305 + + + + + doneCntRst := 0; +online := FALSE; +offline := TRUE; + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Impl_LineIds + + + + + METHOD setClientOffline : BOOL +VAR_INPUT +END_VAR + + b1508d1f-b7a7-40b5-b74f-3091afc5b834_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + setToString + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148572279 + + + + + toString := CONCAT('OK:', CONCAT(TO_STRING(doneCnt), CONCAT('/Er:', TO_STRING(errorCnt)))); + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Impl_LineIds + + + + + METHOD PRIVATE setToString : BOOL +VAR_INPUT +END_VAR + + 1c6ab99e-dc71-45a2-a024-45cf65b9af99_Decl_LineIds + + + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + execute + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043399503 + + + + + PROPERTY execute : BOOL + 89434b6a-7e3b-4095-9220-0a6fc3789a25_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqBusy + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043449644 + + + + + PROPERTY reqBusy : BOOL + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqDone + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043519638 + + + + + PROPERTY reqDone : BOOL + 80965ce1-9b6a-44d3-b272-a5c739ccbc70_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + reqError + + 5a3b8626-d3e9-4f37-98b5-66420063d91e + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + + 639085160043604994 + + + + + PROPERTY reqError : BOOL + 30645d91-91c6-42eb-8399-c20f91ca0d3e_Decl_LineIds + + + 1 + + 48b508fd-2131-484c-ba4a-20e61e66a5b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + + -1 + + + False + + fbb41e61-f245-4f4e-bcd0-218ede01fcda + f6ee860b-0951-41d0-93d8-6436c681d8f1 + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148472030 + + + + + IF NOT setupClient(writeCoilsReq) THEN + RETURN; +END_IF + +//Do request processing +writeCoilsReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Impl_LineIds + + + + + METHOD processRequest + + fbb41e61-f245-4f4e-bcd0-218ede01fcda_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + a861d6e2-2501-43dc-bb37-34509923450c + f6ee860b-0951-41d0-93d8-6436c681d8f1 + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148502269 + + + + + IF writeCoilsReq <> 0 THEN + __DELETE(writeCoilsReq); + writeCoilsReq := 0; +END_IF +writeCoilsReq := __NEW(ModbusFB.ClientRequestWriteMultipleCoils); +recreateRequest := writeCoilsReq; + a861d6e2-2501-43dc-bb37-34509923450c_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + a861d6e2-2501-43dc-bb37-34509923450c_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac + f6ee860b-0951-41d0-93d8-6436c681d8f1 + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591456658 + + + + + in_priorityCurrent := 0; + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + df9b4fd1-9d38-4a54-a7e5-264a94a756ac_Decl_LineIds + + + + f6ee860b-0951-41d0-93d8-6436c681d8f1 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteCoil + + -1 + + + False + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278 + bb7e18ba-2018-4681-875e-de1f291019ef + processRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148512269 + + + + + IF NOT setupClient(writeHoldReq) THEN + RETURN; +END_IF + +//Do request processing +writeHoldReq^( + rClient := mbsClient, + xExecute := stats.execute, + udiTimeOut := 0, + uiUnitId := in_unitId, + udiReplyTimeout := replyTimeout, + uiStartItem := in_startAddr, + uiQuantity := stats.reqLength, + pData := ADR(data[in_dataOffset]) +); + +completeRequest(); + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Impl_LineIds + + + + + METHOD processRequest + + 76621df2-a77c-4e0a-aba0-e3e4a53bd278_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + bd7ff184-1747-404c-9fa0-1b728376c927 + bb7e18ba-2018-4681-875e-de1f291019ef + recreateRequest + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502148532258 + + + + + IF writeHoldReq <> 0 THEN + __DELETE(writeHoldReq); + writeHoldReq := 0; +END_IF +writeHoldReq := __NEW(ModbusFB.ClientRequestWriteMultipleRegisters); +recreateRequest := writeHoldReq; + bd7ff184-1747-404c-9fa0-1b728376c927_Impl_LineIds + + + + + METHOD recreateRequest : POINTER TO ModbusFB.ClientRequest +VAR_INPUT +END_VAR + + bd7ff184-1747-404c-9fa0-1b728376c927_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 5f9160f9-c4a4-4f65-a74c-2838020397f3 + bb7e18ba-2018-4681-875e-de1f291019ef + writeNow + + f8a58466-d7f6-439f-bbb8-d4600e41d099 + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591496600 + + + + + in_priorityCurrent := 0; + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Impl_LineIds + + + + + METHOD writeNow : BOOL +VAR_INPUT +END_VAR + + 5f9160f9-c4a4-4f65-a74c-2838020397f3_Decl_LineIds + + + + bb7e18ba-2018-4681-875e-de1f291019ef + + atomLibrary + 6-Comms + ModbusMaster + atMbReqWriteHolding + + -1 + + + False + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590675071 + + + + + // ===== Test for any requests executing ===== +FOR i := 0 TO requestCnt -1 DO + IF requests[i]^.stats.execute OR requests[i]^.stats.reqBusy THEN + activeIndex := i; + RETURN; + END_IF +END_FOR +activeIndex := -1; + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0803920e-cc13-4b93-a08c-4d27afc1ff3c_Decl_LineIds + + + True + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + 3b82f264-e41e-461d-9beb-263305a22a8d + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590705101 + + + + + // set request to active, which is how the active index gets calculated. +requests[activeIndex]^.stats.execute := TRUE; + 3b82f264-e41e-461d-9beb-263305a22a8d_Impl_LineIds + + + + + VAR +END_VAR + + 3b82f264-e41e-461d-9beb-263305a22a8d_Decl_LineIds + + + False + + 5cc2fe1e-bdb2-429a-84e1-63e783ea3b32 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeIndex + + -1 + + + False + + cc024b23-e5fe-4958-ad80-513feea6aeab + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146383170 + + + + + cnt := 0; +IF requestCnt = 0 THEN + activeReqCnt := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.active THEN + cnt := cnt + 1; + END_IF +END_FOR; +activeReqCnt := cnt; + cc024b23-e5fe-4958-ad80-513feea6aeab_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + cc024b23-e5fe-4958-ad80-513feea6aeab_Decl_LineIds + + + True + + 456b4953-57c9-4f76-ab5b-0db456dd9b58 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + activeReqCnt + + -1 + + + False + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e + 4d9d378a-f256-4f23-b835-539ee9e287c3 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147990941 + + + + + IF requestCnt = 0 THEN + allRequestsComplete := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.execute OR requests[i]^.stats.reqDone OR requests[i]^.stats.reqBusy OR requests[i]^.stats.reqError THEN + allRequestsComplete := FALSE; + RETURN; + END_IF +END_FOR; +allRequestsComplete := TRUE; + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Impl_LineIds + + + + + VAR + i: UINT; +END_VAR + + + + + d59f404d-315d-4ea4-b16b-c8f0e8342d2e_Decl_LineIds + + + True + + 4d9d378a-f256-4f23-b835-539ee9e287c3 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + allRequestsComplete + + -1 + + + False + + dceb6b17-86bf-4543-9652-2fd2b943953c + 89742962-f9b6-46c2-b9a5-b7e298f8495d + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502146538436 + + + + + cnt := 0; +IF requestCnt = 0 THEN + onlineCnt := 0; + RETURN; +END_IF +FOR i := 0 TO requestCnt -1 DO + //Test for any offline request + IF requests[i]^.stats.online THEN + cnt := cnt + 1; + END_IF +END_FOR; +onlineCnt := cnt; + dceb6b17-86bf-4543-9652-2fd2b943953c_Impl_LineIds + + + + + VAR + i: UINT; //Loop variable + cnt: UINT; +END_VAR + + dceb6b17-86bf-4543-9652-2fd2b943953c_Decl_LineIds + + + True + + 89742962-f9b6-46c2-b9a5-b7e298f8495d + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + onlineCnt + + -1 + + + False + + 0006696f-29e2-441e-a325-e3481d025cec + 27292866-0bc0-4848-a965-64c51a0bf960 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841590890993 + + + + + // ===== Test for any requests executing ===== +requestExecuting := activeIndex <> -1; + 0006696f-29e2-441e-a325-e3481d025cec_Impl_LineIds + + + + + VAR + i: DINT; //Loop variable +END_VAR + + 0006696f-29e2-441e-a325-e3481d025cec_Decl_LineIds + + + True + + 27292866-0bc0-4848-a965-64c51a0bf960 + + atomLibrary + 6-Comms + ModbusMaster + atMbClient + requestExecuting + + -1 + + + False + + 3d4da579-672d-4758-9b0f-e055a08e0da3 + e8c6c80b-7bdb-4108-987e-4af107de4377 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650278010 + + + + + //Calculation for tostring +IF onlineCnt = requestCnt THEN + toString := atMbClientToString.allOk; +ELSIF onlineCnt = 0 THEN + toString := atMbClientToString.noneOk; +ELSE + toString := atMbClientToString.someOk; +END_IF + 3d4da579-672d-4758-9b0f-e055a08e0da3_Impl_LineIds + + + + + + 3d4da579-672d-4758-9b0f-e055a08e0da3_Decl_LineIds + + + True + + e8c6c80b-7bdb-4108-987e-4af107de4377 + + atomLibrary + 6-Comms + ModbusMaster + atMbClientRTU + toString + + -1 + + + False + + 4895a5bf-c631-4f8c-bee7-addb0b769548 + 6159c037-e399-466d-8108-0c4f6570f9b2 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639207574650328003 + + + + + IF NOT __ISVALIDREF(client) OR_ELSE NOT __ISVALIDREF(client.client) THEN + RETURN; +END_IF + +mbsClient REF= client.client; + 4895a5bf-c631-4f8c-bee7-addb0b769548_Impl_LineIds + + + + + VAR +END_VAR + + 4895a5bf-c631-4f8c-bee7-addb0b769548_Decl_LineIds + + + True + + 6159c037-e399-466d-8108-0c4f6570f9b2 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + mbsClient + + -1 + + + False + + f54a4cd6-8540-4892-a914-4aac4f8d9018 + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639034841591226460 + + + + + +IF in_timeout > 0 THEN + //Explicitly set timout + replyTimeout := ANY_TO_UDINT((in_timeout) * 1000); +ELSE + // If client timeout is set, use that. + IF client.in_timeout <> T#0MS THEN + replyTimeout := TIME_TO_UDINT(client.in_timeout) * 1000; + ELSIF client.dataRate <> 0 THEN + //Data transmision time * 1.5 to get the timeout. Result is in ms + //8 byte request, then 255 byte reply = 2630 bits. + replyTimeout := REAL_TO_UDINT(2630 / DINT_TO_REAL(client.dataRate) * 1.5 * 1000000); + ELSE + //In case client datarate = zero + replyTimeout := 1000000; //1 second? + END_IF + + +END_IF + + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Impl_LineIds + + + + + VAR +END_VAR + + f54a4cd6-8540-4892-a914-4aac4f8d9018_Decl_LineIds + + + True + + 4f361885-f95c-4244-84d6-8c3b6b9b8a18 + + atomLibrary + 6-Comms + ModbusMaster + atMbReqBase + replyTimeout + + -1 + + + False + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7 + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043419500 + + + + + execute := _execute; + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Impl_LineIds + + + + + VAR +END_VAR + + 465bf80a-0d6e-4363-ba1e-dd04aef379f7_Decl_LineIds + + + True + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + 8ff5fbe7-c047-436d-923c-942d0d66494c + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043439650 + + + + + IF reqDone OR reqError THEN + _execute := FALSE; + RETURN; +END_IF +_execute := execute; + 8ff5fbe7-c047-436d-923c-942d0d66494c_Impl_LineIds + + + + + VAR +END_VAR + + 8ff5fbe7-c047-436d-923c-942d0d66494c_Decl_LineIds + + + False + + 89434b6a-7e3b-4095-9220-0a6fc3789a25 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + execute + + -1 + + + False + + db10f23e-d96b-4fd5-aec0-8b530ad288e1 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043479636 + + + + + reqBusy := _reqBusy; + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Impl_LineIds + + + + + VAR +END_VAR + + db10f23e-d96b-4fd5-aec0-8b530ad288e1_Decl_LineIds + + + True + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + a239da3a-8f75-41da-a264-f06ca07143f4 + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043499661 + + + + + _reqBusy := reqBusy; + a239da3a-8f75-41da-a264-f06ca07143f4_Impl_LineIds + + + + + VAR +END_VAR + + a239da3a-8f75-41da-a264-f06ca07143f4_Decl_LineIds + + + False + + 13e3ae42-bdd9-49ee-b8c0-74b8339469cb + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqBusy + + -1 + + + False + + 61db378f-6029-4712-b562-fea707e5698b + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043539625 + + + + + reqDone := _reqDone; + 61db378f-6029-4712-b562-fea707e5698b_Impl_LineIds + + + + + VAR +END_VAR + + 61db378f-6029-4712-b562-fea707e5698b_Decl_LineIds + + + True + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3 + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147505410 + + + + + _reqDone := reqDone; + +IF (execute AND reqDone) THEN + // ===== Request has just completed succesfully ===== + //Update last valid duration + lastValidDuration := ULINT_TO_UDINT((TSSus - lastReqStart)/1000); + IF lastValidDuration > longestValidDuration THEN + longestValidDuration := lastValidDuration; + END_IF + //Counters + doneCnt := doneCnt + 1; + doneCntRst := doneCntRst + 1; + errorCntRst := 0; + + //Update error info + lastErrorId := modbusFB.Error.OK; + lastExceptionCode := atMBExceptionCodes.RESPONSE_SUCCESS; + + execute := FALSE; + setToString(); +END_IF + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Impl_LineIds + + + + + VAR +END_VAR + + 858ae7ab-7bd2-4c3e-9c4b-69064e0620a3_Decl_LineIds + + + False + + 80965ce1-9b6a-44d3-b272-a5c739ccbc70 + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqDone + + -1 + + + False + + e44e83bd-8f84-4202-9ab4-784d09cf5506 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Get + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639085160043645000 + + + + + reqError := _reqError; + e44e83bd-8f84-4202-9ab4-784d09cf5506_Impl_LineIds + + + + + VAR +END_VAR + + e44e83bd-8f84-4202-9ab4-784d09cf5506_Decl_LineIds + + + True + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + False + + 89b5f7bc-0c37-43f3-8984-14034b000d89 + 30645d91-91c6-42eb-8399-c20f91ca0d3e + Set + + 792f2eb6-721e-4e64-ba20-bc98351056db + + a9ed5b7e-75c5-4651-af16-d2c27e98cb94 + 3b83b776-fb25-43b8-99f2-3c507c9143fc + + 639108502147595513 + + + + + _reqError := reqError; + +IF (execute AND reqError) THEN + // ===== Request has just completed with error ===== + // Counters + errorCnt := errorCnt + 1; + errorCntRst := errorCntRst + 1; + doneCntRst := 0; + //Turn off execute + execute := FALSE; + + //Since we are here there was an exception. If the modbus response is 0, that means no modbus communication happened, so we are offline. If we would have had a succesful communication we would have got a actual modbus error. + IF lastExceptionCode = atMBExceptionCodes.RESPONSE_SUCCESS THEN + timeoutCnt := timeoutCnt + 1; + lastExceptionCode := atMBExceptionCodes.TIMEOUT; + END_IF + setToString(); +END_IF + 89b5f7bc-0c37-43f3-8984-14034b000d89_Impl_LineIds + + + + + VAR +END_VAR + + 89b5f7bc-0c37-43f3-8984-14034b000d89_Decl_LineIds + + + False + + 30645d91-91c6-42eb-8399-c20f91ca0d3e + + atomLibrary + 6-Comms + ModbusMaster + atMbRequestStats + reqError + + -1 + + + CODESYS V3.5 SP21 Patch 4 + + diff --git a/compat/README.md b/compat/README.md new file mode 100644 index 0000000..50247b8 --- /dev/null +++ b/compat/README.md @@ -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. diff --git a/component/CmpAtomModbus.c b/component/CmpAtomModbus.c new file mode 100644 index 0000000..b465ab0 --- /dev/null +++ b/component/CmpAtomModbus.c @@ -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" diff --git a/component/CmpAtomModbusItf.m4 b/component/CmpAtomModbusItf.m4 new file mode 100644 index 0000000..0f01230 --- /dev/null +++ b/component/CmpAtomModbusItf.m4 @@ -0,0 +1,70 @@ +/** + * CmpAtomModbus + * + * + * + */ + +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 + +/** + * + * 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. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * +*/ +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 **/ + diff --git a/component/CmpFrame.c b/component/CmpFrame.c new file mode 100644 index 0000000..7c1f10f --- /dev/null +++ b/component/CmpFrame.c @@ -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; +} + +/** + * Publish exported functions to the component manager, to make them available for other components. + * Use generated macro EXPORT_STMT. + * ERR_OK or error code in case of error + */ +static int CDECL ExportFunctions(void) +{ + /* Macro to export functions */ + EXPORT_STMT; + return ERR_OK; +} + +/** + * Import function pointers from other components. Use generated macro IMPORT_STMT. + * ERR_OK or error code if function name could not be resolved + */ +static int CDECL ImportFunctions(void) +{ + /* Macro to import functions */ + IMPORT_STMT; + return ERR_OK; +} + +/** + * Return the version of our component. + * Version as defined in dep file + */ +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; +} diff --git a/component/makefile b/component/makefile new file mode 100644 index 0000000..d7f6b45 --- /dev/null +++ b/component/makefile @@ -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 diff --git a/dist/README.md b/dist/README.md new file mode 100644 index 0000000..264e9f5 --- /dev/null +++ b/dist/README.md @@ -0,0 +1 @@ +Release outputs are created by tools/build-linux.sh. No precompiled .so is included. diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..372dfb3 --- /dev/null +++ b/docs/API.md @@ -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. diff --git a/docs/BUILD.md b/docs/BUILD.md new file mode 100644 index 0000000..333f494 --- /dev/null +++ b/docs/BUILD.md @@ -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. diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md new file mode 100644 index 0000000..6a18a95 --- /dev/null +++ b/docs/PACKAGING.md @@ -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 diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..4c922e2 --- /dev/null +++ b/docs/TESTING.md @@ -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. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..6fb0fbc --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -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. diff --git a/generated/CmpAtomModbus.c b/generated/CmpAtomModbus.c new file mode 100644 index 0000000..cb364d6 --- /dev/null +++ b/generated/CmpAtomModbus.c @@ -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) +{ + +} + diff --git a/generated/CmpAtomModbusItf.m4 b/generated/CmpAtomModbusItf.m4 new file mode 100644 index 0000000..ac59f30 --- /dev/null +++ b/generated/CmpAtomModbusItf.m4 @@ -0,0 +1,70 @@ +/** + * CmpAtomModbus + * + * + * + */ + +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 + +/** + * + * 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. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * +*/ +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 **/ + diff --git a/sdk/ExtensionSDK-original.zip b/sdk/ExtensionSDK-original.zip new file mode 100644 index 0000000000000000000000000000000000000000..cc463b10f4ad014e837a3d754114697ec9512c6b GIT binary patch literal 29922 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ν z+=Q%Ye!@LkL%eRHMweG4^V}OSHF`2tOb_3n&`_q$p-XjabCV3S2HB!BcPGbRq^$0g z;0=JZGB{%Tj}V)_%2@hh#VeJ9NV}IXUivU(_P4|N__5Vamm4V2h+V8d!E59YigUJh zvvnhf^XkmBdU#MBymfz5YOn~qumi2#DR7h`p|9?Th-x@k>1g0e9)-zqFy+pwvg5_1 z{|G`{OOf0l5Q+z0UEVLf942r($k;hP`y3QM<^Ae`FCd-8Whi28iCpFkI43kBN&OY* zG%K==`6Wbh()VC9wuY(3BNZkJPT_LIr!`?jkEI=-h3fR{M-~1Y1h#`y4W~gLl3MHK z3oSbnYU{J)Sa~z45?grZ$4HZplViVuir{Gmn@c3G)~pr!6&Qn?9uG#xT#Lj;dcF9Z zyZM~p_I=W;OYu8qpHd>0RNulka)L`~aVE!_D6WjIIKlxsCw0;X|7i0tSEEa)XWaeEfNB%-l-^e8SAoS*~B3&ZNW;`6ga(W z8Bz?gm~>u} zqojdBJkqPv`)=al1B4YF+&?S5&KlM5cASC_-PQHxYXP8S5BB^Dm);TL#a=l1j(-GV zq~r(obg@5v!dSEC2knNJ;)?&HvSjtRfTKxq9(wJrZ&O@B$&#^r$%eAZ3llsKMaYal zqn7@KzHSyLUQuG!VEjw1+s&VbKZ=EY^qsHnDY=u#mSBxNN5d%17ElS%U?)=eBpOF- z8w7+de)n4G4)jsxve(0h%Rt(}C4XUaxs}tWeI6ACo)RDv4~i7F02oBBQ@uWCaJ!M3 zlN6IPCT#7-2E?gyvOw2~~B%f-{N1fOW~_KK-YvY15a)J$2c zT5Yf94t2{xIZ@o|Q0PwLn`rLV{)A-*i$}m#Z2d9+kj8;j;bUQ`@~xbI5$|)}&XvbG zV^+ZL%|X@6?d88N%C4S_kR&MsCIiGZ=axt2bz3O``BA2)RnGhK>7C1;8GIPT=-

k?F$mXt&7&UGd_W>LJNz~p3G&!sH- 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 t>&-u>%", 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 "", so please remember this location! + +4) Now create a new project with the following command, and use the makefile from the : + make -f /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 + | |- include (dir) + | |- m4 (dir) + | |- src (dir) + | |- makefile + | |- makefile_template + | |- README + | + |- CmpFirstTest + |- 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 on your linux system + + +On the linux system (at ) +1) Go to your 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: CmpFirstTest, 0xffff2000 1.0.0.0 + +-> 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 CmpFirstTest 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 + + +########################## \ No newline at end of file diff --git a/sdk/ExtensionSDK/include/CmpErrors.h b/sdk/ExtensionSDK/include/CmpErrors.h new file mode 100644 index 0000000..93f6a72 --- /dev/null +++ b/sdk/ExtensionSDK/include/CmpErrors.h @@ -0,0 +1,16 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + + +#ifndef __CMPERRORS_H__ +#define __CMPERRORS_H__ + + +#define ERR_OK 0 +#define ERR_NOTINITIALIZED 0x0003 + +#endif \ No newline at end of file diff --git a/sdk/ExtensionSDK/include/CmpItf.h b/sdk/ExtensionSDK/include/CmpItf.h new file mode 100644 index 0000000..4ae6f97 --- /dev/null +++ b/sdk/ExtensionSDK/include/CmpItf.h @@ -0,0 +1,711 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + +#ifndef __CMPITF_H__ +#define __CMPITF_H__ + +#include + +#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); + +/* */ +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 + +/** + * Hook definitions to inform all components with a broadcast via HookFunction() ([0..10000] reserved for core components): + *
    + *
  • CH_INIT_XXX Hooks: Used for initialization of all components. + * Future range, not yet defined! = [1..1000]
  • + *
  • CH_EXIT_XXX Hooks: Used for deinitialization of all components. + * Future range, not yet defined! = [2000..3000]
  • + *
  • CH_XXX Hooks: Used for broadcast commands (e.g. cyclic operations). + * Future range, not yet defined! = [4000..5000]
  • + *
+ * IMPLEMENTATION NOTE: + * Future hooks should be defined in the appropriate range above! + *
+ */ + +/* + 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 +*/ + +/** +* Hook table +* +* 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! +* +* +*/ +#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\ + } + +/** + * Single import of interface functions + * + * 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 "Itf.h" + * USE_SINGLE_ITF() or USE_SINGLE_ITF(, ) + * USE_SINGLE_FUNCTION() + * + * static int CDECL ImportFunctions(void) + * { + * IMPORT_SINGLE_ITF(); + * IMPORT_SINGLE_FUNCTION(); + * return ERR_OK; + * } + * + */ +#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__*/ diff --git a/sdk/ExtensionSDK/include/CmpStd.h b/sdk/ExtensionSDK/include/CmpStd.h new file mode 100644 index 0000000..a23e477 --- /dev/null +++ b/sdk/ExtensionSDK/include/CmpStd.h @@ -0,0 +1,641 @@ +/* + * this is a special header for the SL extension API!!! + * + * Copyright (c) 2017-2020 CODESYS Development GmbH, Copyright (c) 1994-2016 3S-Smart Software Solutions GmbH. All rights reserved. + * + */ + +#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 +#include +#include +#include +#include +#include +#include + + + +/** + * 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 + +/** + * 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 +# 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 + +/** + * + * Abstract Memory Region. + * + */ +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__*/ diff --git a/sdk/ExtensionSDK/m4/Exports.m4 b/sdk/ExtensionSDK/m4/Exports.m4 new file mode 100644 index 0000000..3747d00 --- /dev/null +++ b/sdk/ExtensionSDK/m4/Exports.m4 @@ -0,0 +1,1514 @@ +include(`Util.m4') dnl +divert(-1) +% ----------------------------------- +% Copyright: Copyright CODESYS Development GmbH +% ----------------------------------- +changecom(`%') +% ----------------------------- +% Define some diverts that gather information about some special features +% and initialize those diversions +% ----------------------------- +define(DIVERT_INTERFACEDEF_C,3) +define(DIVERT_ENDINTERFACEDEF_C,4) +define(DIVERT_INTERFACEDEF,5) +define(DIVERT_ENDINTERFACEDEF,6) +define(DIVERT_ENTRYFUN,7) +define(DIVERT_ENDFILE,100) + + +% ----------------------------- +% The SET_PLACEHOLDER_NAME macro is to be ignored. It +% is used by the delivery manager only. +% ----------------------------- +define(`SET_PLACEHOLDER_NAME',`dnl') +') + +define(`GEN_INCLUDE', dnl +`ifelse(`-1',regexp(`$1',`\.m4$'), dnl + `error(`m4:'__file__`('__line__`):The first parameter "'$1`" is not a .m4 file + ')',dnl +`#include "patsubst(`$1',`\.m4$',`\.h')"')') + +define(`REF_ITF', `GEN_INCLUDE($1)') + +% Mark safety relevant component, from which the IBase is used +% in a safety critical manner. +define(`INTERFACEDEF', +`divert(DIVERT_INTERFACEDEF) +#ifdef CPLUSPLUS +class `I'$1 : public IBase +{ + public: +divert +divert(DIVERT_INTERFACEDEF_C) + +ifelse($1,`CmpIoDrv',`/* */')dnl +ifelse($1,`CmpIoDrvC',`/* */')dnl +ifelse($1,`CmpIoDrvIec',`/* */') +typedef struct +{ + IBase_C *pBase; +divert') + +define(`ENDINTERFACEDEF', +`divert(DIVERT_ENDINTERFACEDEF)dnl +}; + #ifndef `ITF_'$1 + #define `ITF_'$1 static `I'$1 *`pI'$1 = NULL; + #endif + #define `EXTITF_'$1 +#else /*CPLUSPLUS*/ + typedef `I'$1_C `I'$1; + #ifndef `ITF_'$1 + #define `ITF_'$1 + #endif + #define `EXTITF_'$1 +#endif +divert +divert(DIVERT_ENDINTERFACEDEF_C)dnl +} `I'$1_C; +divert') + +define(`SET_INTERFACE_NAME', + `define(CMP_NAME, $1)' + `define(ITFNOTDEFINED,_UPPERCASE(_TRIM($1))_NOTIMPLEMENTED)' + `define(ITFEXTERNAL,_UPPERCASE(_TRIM($1))_EXTERNAL) +`#ifndef _'_UPPERCASE(_TRIM($1))`ITF_H_' +`#define _'_UPPERCASE(_TRIM($1))`ITF_H_' +` +#include "CmpStd.h"' +define(`_INTERFACE_NAME',`I$1') +INTERFACEDEF($1) +ENDINTERFACEDEF($1) +divert(DIVERT_ENDFILE) +`#ifdef CPLUSPLUS_ONLY' +` #undef CPLUSPLUS_ONLY' +`#endif' + +`#endif /*_'_UPPERCASE(_TRIM($1))`ITF_H_*/' +divert +') + + +% +% Call the DEF_API Makro with the following parameters: +% DEF_API(, , , ) +% +% Example: +% DEF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`REGISTER_API2', `s_pfCMRegisterAPI2( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), ifelse($6,1,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))' )'dnl +`define(`GET_API', `s_pfCMGetAPI( "$3", (RTS_VOID_FCTPTR *)&pf$3, _CHKSUM($1, $2, $4))' )'dnl +`define(`GET_API2', `s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, ifelse(EXTERNAL,1,1,0), ifelse($6,0,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6),`1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($5, `1',`typedef $1 ($2 CDECL_EXT* _FUN_PTR_IEC($3)) $4;',`typedef $1 ($2 * _FUN_PTR($3)) $4;'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2)' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3 $3' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),ERR_OK)' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,ifelse(eval($# < 7),`1',REGISTER_API,REGISTER_API2),CAL_EXPAPI)' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6),`1',`_FUN_PTR($3)',`_FUN_PTR_IEC($3)') pf$3;' + `#define EXT_'$3 `extern ifelse(eval($# < 6),`1',`_FUN_PTR($3)',`_FUN_PTR_IEC($3)') pf$3'; + `#define GET_$3(fl) ifelse(eval($# < 7),1, `s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), ifelse(eval($# < 6),1,0,`ifelse($6,0,_CHKSUM($1, $2, $4),$6)'), ifelse(eval($# < 7),1,0,$7))',`s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl) | CM_IMPORT_EXTERNAL_LIB_FUNCTION, ifelse($6,0,_CHKSUM($1, $2, $4),$6), ifelse(eval($# < 7),1,0,$7))')' + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 ifelse(eval($# < 7),`1',REGISTER_API, REGISTER_API2)' +`#endif' +) + +% +% Call the DEF_STATIC_API Makro with the following parameters: +% DEF_STATIC_API(, , , ) +% +% Example: +% DEF_STATIC_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_STATIC_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6),`1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3 $3' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ifelse(EXTERNAL,1,REGISTER_API,ERR_OK)' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `_FUN_PTR($3) pf$3;' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4),0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +) + +% +% Call the DEF_CREATEITF_API Makro with the following parameters: +% DEF_CREATEITF_API(, , , ) +% +% Example: +% DEF_CREATEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``((I'CMP_NAME`*)s_pfCMCreateInstance(CLASSID_C'CMP_NAME'`, NULL))->I'$3 + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL && pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``((I'CMP_NAME`*)s_pfCMCreateInstance(CLASSID_C'CMP_NAME'`, NULL))->I'$3 + `#define CHK_$3 (s_pfCMCreateInstance != NULL && pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETEITF_API Makro with the following parameters: +% DEF_DELETEITF_API(, , , ) +% +% Example: +% DEF_DELETEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',``(((RTS_HANDLE)p0 == NULL || (RTS_HANDLE)p0 == RTS_INVALID_HANDLE) ? ERR_PARAMETER : ((I'CMP_NAME`*)p0)'->I'$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) ifelse(_TRIM($1),`void',`',``(((RTS_HANDLE)p0 == NULL || (RTS_HANDLE)p0 == RTS_INVALID_HANDLE) ? ERR_PARAMETER : ((I'CMP_NAME`*)p0)'->I'$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API Makro with the following parameters: +% DEF_HANDLEITF_API(, , , ) +% +% Example: +% DEF_HANDLEITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API2 Makro with the following parameters: +% DEF_HANDLEITF_API2(, , , ) +% +% Example: +% DEF_HANDLEITF_API2(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API2', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)(((IBase*)p0)->QueryInterface((IBase *)p0, ITFID_I`'CMP_NAME, (RTS_RESULT *)1)))->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)(((IBase*)p0)->QueryInterface((IBase *)p0, ITFID_I`'CMP_NAME, (RTS_RESULT *)1)))->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_HANDLEITF_API_OWNCPP Makro with the following parameters: +% DEF_HANDLEITF_API_OWNCPP(, , , ) +% +% NOTE: +% Identical to DEF_HANDLEITF_API except, that in the C++ wrapper you can use your own implementation. +% +% Example: +% DEF_HANDLEITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_HANDLEITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 (pI`'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? pI`'CMP_NAME->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) : ((I`'CMP_NAME*)p0)->I'`$3(_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 (pI`'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + + +% +% Call the DEF_CREATECLASSITF_API Makro with the following parameters: +% DEF_CREATECLASSITF_API(, , , ) +% +% Example: +% DEF_CREATECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `p0 = (IBase *)s_pfCMCreateInstance(p1, NULL);\ + ((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_CREATECLASSITF2_API Makro with the following parameters: +% DEF_CREATECLASSITF2_API(, , , ) +% +% Example: +% DEF_CREATECLASSITF2_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CREATECLASSITF2_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4'))`\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == CLASSID_INVALID ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, NULL)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +define(`NUMPARAMS_xxx',`$#') +define(`NUMPARAMS',`NUMPARAMS_xxx(_EXTRACTPARAMS($1))') +define(`MINPARAMS',`eval(NUMPARAMS(`$1') >= $2)') + +% +% Call the DEF_PRODUCECLASSITF_API Makro with the following parameters: +% DEF_PRODUCECLASSITF_API(, , , ) +% +% Example: +% DEF_PRODUCECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_PRODUCECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_PRODUCECLASSITF_API_OWNCPP Makro with the following parameters: +% DEF_PRODUCECLASSITF_API_OWNCPP(, , , ) +% +% Example: +% DEF_PRODUCECLASSITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_PRODUCECLASSITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_`'CMP_NAME`'$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + ((I'CMP_NAME`*)((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_'_INTERFACE_NAME`, (RTS_RESULT *)1)')->I$3(_MAKE_DUMMYPARAMLIST(`$4'))\ + )\ + )' + `#define CHK_$3 (s_pfCMCreateInstance != NULL)' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) `\ + (\ + (p0 = (IBase *)s_pfCMCreateInstance(p1, NULL)) == NULL ? RTS_INVALID_HANDLE : \ + (\ + (((IBase *)p0)->ClassId = p1) == 0 ? RTS_INVALID_HANDLE :\ + (\ + (((IBase *)p0)->hInstance = ((I'CMP_NAME`*)p2)'->I$3(p2, p1, ((I`'CMP_NAME*)p2)->pBase->hInstance, _MAKE_HANDLE_DUMMYPARAMLIST2(`$4', 3))) == RTS_INVALID_HANDLE ? (s_pfCMDeleteInstance2(p1, (IBase *)p0) ? RTS_INVALID_HANDLE : RTS_INVALID_HANDLE) : \ + (\ + ((IBase *)p0)->QueryInterface((IBase *)p0, ITFID_`'_INTERFACE_NAME`, NULL)'\ + )\ + )\ + )\ + )' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_CREATEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETECLASSITF_API Makro with the following parameters: +% DEF_DELETECLASSITF_API(, , , ) +% +% Example: +% DEF_DELETECLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_DELETECLASSITF2_API Makro with the following parameters: +% DEF_DELETECLASSITF2_API(, , , ) +% +% Example: +% DEF_DELETECLASSITF2_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_DELETECLASSITF2_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')) != ERR_OK ? \ + ERR_FAILED :\ + s_pfCMDeleteInstance2(((I`'CMP_NAME*)p0)->pBase->ClassId, ((I`'CMP_NAME*)p0)->pBase))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_CLASSITF_API Makro with the following parameters: +% DEF_CLASSITF_API(, , , ) +% +% Example: +% DEF_CLASSITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_CLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_RETURNHANDLECLASSITF_API Makro with the following parameters: +% DEF_RETURNHANDLECLASSITF_API(, , , ) +% +% Example: +% DEF_RETURNHANDLECLASSITF_API(`RTS_HANDLE',`CDECL', `MyApiFunction', `(RTS_HANDLE hObject, int nX, void *pData)') +% ==> ((IMyInterface*)p0)->IMyInterfaceFunction(((IMyInterface*)p0)->pBase->hInstance,p1,p2,p3)) +% +define(`DEF_RETURNHANDLECLASSITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`STATICITF_DEF $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`'$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : (('I`'CMP_NAME*)p0)->I$3(`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4')))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#else /* DYNAMIC_LINK */' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_OK' + `#define CAL_$3(_MAKE_DUMMYPARAMLIST(`$4')) (p0 == RTS_INVALID_HANDLE || p0 == NULL ? `'ifelse(_TRIM($1),`RTS_RESULT',`($1)ERR_PARAMETER',ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`0'))` : ((('I`'CMP_NAME*)p0)->pBase->hInstance = ((I`'CMP_NAME*)p0)->I$3(((I`'CMP_NAME*)p0)->pBase->hInstance`'ifelse(MINPARAMS(`$4',2),`1',`,',`')`'_MAKE_HANDLE_DUMMYPARAMLIST(`$4'))))' + `#define CHK_$3 TRUE' + `#define EXP_$3 ERR_OK' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_HANDLEITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_STATICITF_API Makro with the following parameters: +% DEF_STATICITF_API(, , , ) +% +% Example: +% DEF_STATICITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_STATICITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``C'CMP_NAME::I'$3 + `#define CHK_`'CMP_NAME`'$3 TRUE' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``C'CMP_NAME::I'$3 + `#define CHK_$3 TRUE' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) static $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))); +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API_OWNCPP(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API_OWNCPP', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_API Makro with the following parameters: +% DEF_ITF_API(, , , ) +% +% Example: +% DEF_ITF_API2(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_API2', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`$1 $2 CDECL_EXT $3$4;',`$1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',ifelse(_TRIM($1),`void *',`($1)(RTS_SIZE)ERR_NOTIMPLEMENTED',`($1)ERR_NOTIMPLEMENTED')))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) + +% +% Call the DEF_ITF_GLOBAL_API Makro with the following parameters: +% DEF_ITF_GLOBAL_API(, , , ) +% +% Example: +% DEF_ITF_GLOBAL_API(`int',`CDECL', `MyApiFunction', `(int nX, void *pData)') +% +define(`DEF_ITF_GLOBAL_API', +`ifelse(eval($# < 5),`0',`define(`EXTERNAL',_TRIM(`$5'))',`define(`EXTERNAL',0)')'dnl +`define(`REGISTER_API', `s_pfCMRegisterAPI( (const CMP_EXT_FUNCTION_REF*)"$3", (RTS_UINTPTR)$3, ifelse(EXTERNAL,1,1,0), _CHKSUM($1, $2, $4))' )'dnl +`define(`FCT_NOTIMPLEMENTED', `ifelse(_EXTRACTFIRSTPARAMTYPE(`$4'), `RTS_HANDLE', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPEPOINTER(`$4'), `*', `FUNCTION_NOTIMPLEMENTED2',`ifelse(_EXTRACTFIRSTPARAMTYPERTS(`$4'), `RTS_', `FUNCTION_NOTIMPLEMENTED',`FUNCTION_NOTIMPLEMENTED2')')')' )'dnl +`define(`CAL_GETAPI', `CAL_CMGETAPI( "$3" )' )'dnl +`define(`CAL_EXPAPI', `CAL_CMEXPAPI( "$3" )' )'dnl +`ifelse(_TRIM(`$5'),1,`DLL_DECL $1 $2 CDECL_EXT $3$4;',`DLL_DECL $1 $2 $3$4;')' +`ifelse(eval($# < 6), `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;', ifelse($6, `1',`typedef $1 ($2 * _FUN_PTR($3)) $4;',`'))' +`#if ifdef(`ITFNOTDEFINED',`defined(ITFNOTDEFINED) || ')defined(_UPPERCASE(_TRIM(`$3'))_NOTIMPLEMENTED)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) ERR_NOTIMPLEMENTED' + `#define CAL_$3`'ifelse(_TRIMVARPARAMLIST($4),1,`',`(_MAKE_DUMMYPARAMLIST(`$4'))') ifelse(_TRIM($1),`void',`', ifelse(_TRIM($4), `void',`($1)ERR_NOTIMPLEMENTED',ifelse(_TRIMVARPARAMLIST($4),1,FCT_NOTIMPLEMENTED,ifelse(_TRIM($1),`RTS_HANDLE',`($1)RTS_INVALID_HANDLE',`($1)ERR_NOTIMPLEMENTED'))))' + `#define CHK_$3 FALSE' + `#define EXP_$3 ERR_OK' +`#elif defined(STATIC_LINK)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 ifelse(EXTERNAL,1,REGISTER_API,CAL_EXPAPI)' +`#elif defined(MIXED_LINK) && !defined(ITFEXTERNAL)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_$3 $3' + `#define CHK_$3 TRUE' + `#define EXP_$3 REGISTER_API' +`#elif defined(CPLUSPLUS_ONLY)' + `#define USE_`'CMP_NAME`'$3' + `#define EXT_`'CMP_NAME`'$3' + `#define GET_`'CMP_NAME`'$3 ERR_OK' + `#define CAL_`'CMP_NAME`''$3 ``pI'CMP_NAME->I'$3 + `#define CHK_`'CMP_NAME`''$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_`'CMP_NAME`'$3 ERR_OK' +`#elif defined(CPLUSPLUS)' + `#define USE_$3' + `#define EXT_$3' + `#define GET_$3(fl) CAL_GETAPI' + `#define CAL_'$3 ``pI'CMP_NAME->I'$3 + `#define CHK_'$3 ``(pI'CMP_NAME != NULL)' + `#define EXP_$3 CAL_EXPAPI' +`#else /* DYNAMIC_LINK */' + `#define USE_'$3 `ifelse(eval($# < 6), `1',`_FUN_PTR($3) pf$3;', ifelse($6, `1', `_FUN_PTR($3) pf$3;'))' + `#define EXT_'$3 `extern _FUN_PTR($3) pf$3'; + `#define GET_'$3(fl) s_pfCMGetAPI2( "$3", (RTS_VOID_FCTPTR *)&pf$3, (fl), _CHKSUM($1, $2, $4), 0) + `#define CAL_'$3 pf$3 + `#define CHK_'$3 (pf$3 != NULL) + `#define EXP_$3 REGISTER_API' +`#endif' +`divert(DIVERT_INTERFACEDEF) virtual $1 $2 `I'$3(_CONCAT(`_MAKE_ITF_PARAMLIST',_TRIM(`$4'))) =0; +divert' +`divert(DIVERT_INTERFACEDEF_C) _FUN_PTR($3) `I'$3; + divert' +) +divert \ No newline at end of file diff --git a/sdk/ExtensionSDK/m4/Implementation.m4 b/sdk/ExtensionSDK/m4/Implementation.m4 new file mode 100644 index 0000000..7c3ea0e --- /dev/null +++ b/sdk/ExtensionSDK/m4/Implementation.m4 @@ -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'() 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((`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(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 \ No newline at end of file diff --git a/sdk/ExtensionSDK/m4/Util.m4 b/sdk/ExtensionSDK/m4/Util.m4 new file mode 100644 index 0000000..63f00a3 --- /dev/null +++ b/sdk/ExtensionSDK/m4/Util.m4 @@ -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 diff --git a/sdk/ExtensionSDK/makefile b/sdk/ExtensionSDK/makefile new file mode 100644 index 0000000..9edd426 --- /dev/null +++ b/sdk/ExtensionSDK/makefile @@ -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 diff --git a/sdk/ExtensionSDK/makefile_template b/sdk/ExtensionSDK/makefile_template new file mode 100644 index 0000000..fc510d9 --- /dev/null +++ b/sdk/ExtensionSDK/makefile_template @@ -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 + + + diff --git a/sdk/ExtensionSDK/src/CmpFrame.c b/sdk/ExtensionSDK/src/CmpFrame.c new file mode 100644 index 0000000..ae31e13 --- /dev/null +++ b/sdk/ExtensionSDK/src/CmpFrame.c @@ -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; +} + +/** + * Publish exported functions to the component manager, to make them available for other components. + * Use generated macro EXPORT_STMT. + * ERR_OK or error code in case of error + */ +static int CDECL ExportFunctions(void) +{ + /* Macro to export functions */ + EXPORT_STMT; + return ERR_OK; +} + +/** + * Import function pointers from other components. Use generated macro IMPORT_STMT. + * ERR_OK or error code if function name could not be resolved + */ +static int CDECL ImportFunctions(void) +{ + /* Macro to import functions */ + IMPORT_STMT; + return ERR_OK; +} + +/** + * Return the version of our component. + * Version as defined in dep file + */ +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; +} diff --git a/src/atom_bridge.c b/src/atom_bridge.c new file mode 100644 index 0000000..694610f --- /dev/null +++ b/src/atom_bridge.c @@ -0,0 +1,86 @@ +#include "atom_bridge.h" +#include "atom_mb.h" +#include +#include +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>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 || capc,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 || capc.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 +/* 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 diff --git a/src/atom_mb.c b/src/atom_mb.c new file mode 100644 index 0000000..0e2107b --- /dev/null +++ b/src/atom_mb.c @@ -0,0 +1,146 @@ +#include "atom_mb.h" +#include +#include +#include +#include +#include +#include +#include +#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;i1) 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;itx+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;iquantity;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(nowdeadline_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); +} diff --git a/src/atom_mb.h b/src/atom_mb.h new file mode 100644 index 0000000..f0c50d7 --- /dev/null +++ b/src/atom_mb.h @@ -0,0 +1,38 @@ +#ifndef ATOM_MB_H +#define ATOM_MB_H +#include +#include +/* 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 diff --git a/tests/bridge_driver.c b/tests/bridge_driver.c new file mode 100644 index 0000000..da2349f --- /dev/null +++ b/tests/bridge_driver.c @@ -0,0 +1,29 @@ +#define _POSIX_C_SOURCE 200809L +#include "atom_bridge.h" +#include +#include +#include +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; +} diff --git a/tests/driver.c b/tests/driver.c new file mode 100644 index 0000000..92f55d7 --- /dev/null +++ b/tests/driver.c @@ -0,0 +1,24 @@ +#define _POSIX_C_SOURCE 200809L +#include "atom_mb.h" +#include +#include +#include +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; +} diff --git a/tests/test_bridge.c b/tests/test_bridge.c new file mode 100644 index 0000000..45cc4aa --- /dev/null +++ b/tests/test_bridge.c @@ -0,0 +1,19 @@ +#include "atom_bridge.h" +#include +#include +#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; +} diff --git a/tests/test_tcp.py b/tests/test_tcp.py new file mode 100644 index 0000000..bcdcd36 --- /dev/null +++ b/tests/test_tcp.py @@ -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)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) diff --git a/tools/build-linux.sh b/tools/build-linux.sh new file mode 100755 index 0000000..f3745ea --- /dev/null +++ b/tools/build-linux.sh @@ -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" diff --git a/tools/build_compat_export.py b/tools/build_compat_export.py new file mode 100644 index 0000000..6ef1d8c --- /dev/null +++ b/tools/build_compat_export.py @@ -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'(?'+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.')