diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..de9f2fb --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +BasedOnStyle: Microsoft +IndentWidth: 4 +UseTab: Never +ColumnLimit: 100 +BreakBeforeBraces: Attach +PointerAlignment: Left +NamespaceIndentation: None +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +BinPackArguments: false +BinPackParameters: false +AlignAfterOpenBracket: Align +AllowAllArgumentsOnNextLine: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..89e2b2c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release +on: + push: + tags: ['*'] +jobs: + build: + permissions: + contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + arch: x64 + build-group: linux-x64 + - os: ubuntu-latest + arch: x64 + build-group: linux-arm + - os: ubuntu-latest + arch: x64 + build-group: android-arm + - os: macos-latest + arch: x64 + build-group: darwin-x64+arm64 + - os: windows-latest + arch: x86 + build-group: win32-x86 + - os: windows-latest + arch: x64 + build-group: win32-x64 + - os: windows-latest + arch: x64 + build-group: win32-arm64 + runs-on: ${{ matrix.os }} + name: Build ${{ matrix.build-group }} + env: + BUILD_GROUP: ${{ matrix.build-group }} + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + submodules: recursive + fetch-depth: 2 + + - name: Set up node + uses: actions/setup-node@v5 + with: + node-version: 18 + architecture: ${{ matrix.arch }} + + - name: Install + run: npm install --ignore-scripts + + - name: Prebuild + run: npm run prebuild-$BUILD_GROUP + shell: bash + + - name: Prepare artifact + run: tar -zcvf $BUILD_GROUP.tar.gz -C prebuilds . + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.BUILD_GROUP }} + path: ${{ env.BUILD_GROUP }}.tar.gz + retention-days: 1 + + release: + needs: build + permissions: + contents: write + runs-on: ubuntu-latest + name: Release + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Create GitHub release + uses: docker://antonyurchenko/git-release:v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + args: artifacts/*/*.tar.gz diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test.yml similarity index 57% rename from .github/workflows/test-and-release.yml rename to .github/workflows/test.yml index 0299b33..f611341 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test.yml @@ -1,41 +1,46 @@ -name: Test and Release -on: [push, pull_request, workflow_dispatch] +name: Test +on: [push, pull_request] permissions: contents: read jobs: - build: + test: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - node: [16, 18, 20, 22, 24] + node: [18, 20, 22] arch: [x86, x64] exclude: - { os: ubuntu-latest, arch: x86 } - { os: macos-latest, arch: x86 } - - { os: windows-latest, arch: x86, node: 24 } runs-on: ${{ matrix.os }} name: ${{ matrix.os }} / Node ${{ matrix.node }} ${{ matrix.arch }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 2 - # Force Python to 3.10 until prebuild updates to node-gyp 10 - - name: Use Python 3.10 - if: ${{ matrix.os != 'windows-latest' }} - uses: actions/setup-python@v4 - with: - python-version: '3.10' + - name: Use node ${{ matrix.node }} ${{ matrix.arch }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: ${{ matrix.node }} architecture: ${{ matrix.arch }} + - name: Install run: npm install + - name: Test - env: - PREBUILD_TOKEN: ${{ secrets.PREBUILD_TOKEN }} run: npm test + + # https://github.com/electron/electron/issues/42510#issuecomment-2171583086 + - name: Disable AppArmor restriction + if: matrix.os == 'ubuntu-latest' + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 + + - name: Test Electron + if: matrix.os == 'ubuntu-latest' && matrix.node == '18' + uses: GabrielBB/xvfb-action@v1 + with: + run: npm run test-electron diff --git a/.gitignore b/.gitignore index 3056f17..5abeef8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .vscode/ build/ +prebuilds/ node_modules/ -test/ \ No newline at end of file diff --git a/.npmignore b/.npmignore index deeb660..d627fdb 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,8 @@ +.github/ +.vscode/ test/ -.travis.yml -appveyor.yml -.npmignore +scripts/ +build/ +.clang-format .gitignore +.npmignore diff --git a/LICENSE b/LICENSE index a06b7a3..f2ea45b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2019 Mathias Küsel +Copyright (c) 2025 Mathias Küsel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index cb33cec..3ed043c 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,88 @@ -# node-snap7 +# node-snap7 [![npm](https://img.shields.io/npm/v/node-snap7.svg?label=&logo=npm)](https://www.npmjs.com/package/node-snap7) [![Node version](https://img.shields.io/node/v/node-snap7.svg)](https://www.npmjs.com/package/node-snap7) -[![Test and Release](https://github.com/mathiask88/node-snap7/actions/workflows/test-and-release.yml/badge.svg)](https://github.com/mathiask88/node-snap7/actions/workflows/test-and-release.yml) [![npm](https://img.shields.io/npm/dm/node-snap7.svg?label=dl)](https://www.npmjs.com/package/node-snap7) -**Current node-snap7 version:** 1.0.9\ -**Current snap7 version:** 1.4.2 +**Current node-snap7 version:** 2.0.0-beta\ +**Current snap7 version:** 1.4.3 -**In my spare time I am working on a [node-addon-api](https://github.com/nodejs/node-addon-api) rewrite and want to switch from [prebuild-install](https://github.com/prebuild/prebuild-install) to [prebuildify](https://github.com/prebuild/prebuildify).\ -The current S7Server implementation has some bugs, please use with caution.** +node-snap7 now uses [node-addon-api](https://github.com/nodejs/node-addon-api) (N-API) instead of NAN, ships N-API prebuilds generated with [prebuildify](https://github.com/prebuild/prebuildify), and the S7Server implementation is considered stable. Promise, callback, and synchronous call styles are available across the API. ## About -This is a node.js wrapper for snap7. Snap7 is an open source, 32/64 bit, multi-platform Ethernet communication suite for interfacing natively with Siemens S7 PLCs (See [compatibility](http://snap7.sourceforge.net/snap7_client.html#target_compatibility)). +This is a node.js wrapper for [snap7](https://github.com/davenardella/snap7). [snap7](https://github.com/davenardella/snap7) is an open source, 32/64 bit, multi-platform Ethernet communication suite for interfacing natively with Siemens S7 PLCs (see [compatibility](http://snap7.sourceforge.net/snap7_client.html#target_compatibility)). ## Installation Install with: - npm install node-snap7 +``` +npm install node-snap7 +``` -node-snap7 uses `prebuild` and `prebuild-install` for handling prebuilt binaries. See [this list](https://github.com/mathiask88/node-snap7/releases) of supported prebuilt platform binaries. When installing node-snap7 `prebuild-install` will install prebuilt binaries from GitHub if they exist and fallback to a compile step if they don't. +node-snap7 ships N-API prebuilt binaries generated with `prebuildify` and loaded locally through `node-gyp-build` during install. If a prebuild for your platform is not included, installation will fall back to compiling from source. If you don't want to use the `prebuild` for the platform you are installing on, specify the `--build-from-source` flag when you install. For building from source you need the following requirements: - Windows: - - [Visual Studio 2013 Express or higher](https://www.visualstudio.com/de/vs/visual-studio-express/) - - [Python 2.7](https://www.python.org/downloads/release/python-2714/) + - Visual Studio Build Tools with Desktop C++ workload (2019 or newer recommended) + - [Python 3](https://www.python.org/downloads/) - Linux: - C++11 toolchain - - [Python 2.7](https://www.python.org/downloads/release/python-2714/) + - [Python 3](https://www.python.org/downloads/) + - `make` and other common build essentials ## Special thanks to -- Davide Nardella for creating snap7 +- Davide Nardella for creating [snap7](https://github.com/davenardella/snap7) ## How to use ### API - [Client](doc/client.md) - [Server](doc/server.md) +### Call styles +Each exported method is available as a Promise-returning async function, a callback-style function (when a callback is passed as the last argument), and a synchronous variant (with the `Sync` suffix). + ### Client Example ```javascript -var snap7 = require('node-snap7'); +const snap7 = require('node-snap7'); -var s7client = new snap7.S7Client(); -s7client.ConnectTo('192.168.1.12', 0, 1, function(err) { - if(err) - return console.log(' >> Connection failed. Code #' + err + ' - ' + s7client.ErrorText(err)); +const s7client = new snap7.S7Client(); - // Read the first byte from PLC process outputs... - s7client.ABRead(0, 1, function(err, res) { - if(err) - return console.log(' >> ABRead failed. Code #' + err + ' - ' + s7client.ErrorText(err)); +async function main() { + try { + await s7client.ConnectTo('192.168.1.12', 0, 1); + + // Read the first byte from PLC process outputs and print it + const res = await s7client.ABRead(0, 1); + console.log(res); + } catch (err) { + console.error(' >> Operation failed. Code #', err, '-', s7client.ErrorText(err)); + } +} + +main(); +``` - // ... and write it to stdout - console.log(res) - }); +Callback style remains available: +```javascript +s7client.ABRead(0, 1, (err, res) => { + if (err) console.error(s7client.ErrorText(err)); + else console.log(res); }); ``` -### Server Example +Synchronous variants can be used with the `Sync` suffix: ```javascript -var snap7 = require('node-snap7'); +const res = s7client.ABReadSync(0, 1); +console.log(res); +``` -var s7server = new snap7.S7Server(); +### Server Example +```javascript +const snap7 = require('node-snap7'); +const s7server = new snap7.S7Server(); // Set up event listener s7server.on("event", function(event) { @@ -72,7 +90,7 @@ s7server.on("event", function(event) { }); // Create a new Buffer and register it to the server as DB1 -var db1 = new Buffer(100).fill('ÿ'); +const db1 = Buffer.alloc(100, 0); s7server.RegisterArea(s7server.srvAreaDB, 1, db1); // Start the server @@ -85,11 +103,38 @@ setTimeout(function() { }, 20000); ``` -Have a look at the resourceless server example [here](doc/server.md#event-read-write). +### Resourceless server example +```javascript +const snap7 = require('node-snap7'); +const s7server = new snap7.S7Server(); + +// Enable resourceless mode to handle requests manually +s7server.SetResourceless(true); + +s7server.on('readWrite', (sender, operation, tag, buffer, done) => { + console.log(`${operation === s7server.operationRead ? 'Read' : 'Write'} from ${sender}`); + console.log(tag); + + if (operation === s7server.operationRead) { + buffer.fill(0x42); // respond with dummy data + return done(buffer); + } + + console.log('Payload:', buffer); + done(); // always call to release the worker thread +}); + +s7server.StartTo('127.0.0.1'); +``` + +## Testing +- Run Node.js tests (Node runtime): `npm test` +- Run the same suite inside Electron's Node runtime: `npm run test-electron` + On headless CI, wrap with `xvfb-run npm run test-electron`. ## License & copyright -Copyright (c) 2019, Mathias Küsel +Copyright (c) 2025, Mathias Küsel node-snap7 is licensed under the MIT license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details. -node-snap7 builds on the excellent work of the snap7 framework from Davide Nardella. Snap7 is issued under the GPL/LGPLv3 (see `./deps/snap7/gpl.txt ./deps/snap7/lgpl-3.0.txt`). +node-snap7 builds on the excellent work of the [snap7](https://github.com/davenardella/snap7) framework from Davide Nardella. [snap7](https://github.com/davenardella/snap7) is issued under the GPL/LGPLv3 (see [`./deps/snap7/gpl.txt`](./deps/snap7/gpl.txt) and [`./deps/snap7/lgpl-3.0.txt`](./deps/snap7/lgpl-3.0.txt)). diff --git a/binding.gyp b/binding.gyp index a368242..b5e4c47 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,6 @@ "targets": [{ "target_name": "node_snap7", "include_dirs": [ - "8 +- Client + An error in ListBlocksOfType occurred when the PDU>=480 and the blocks to be + listed were high. + Thanks to Rade Nisevic. +- .NET wrapper + Fixed an error in GetIntAt. + Thanks to Lukasz Stefanowicz +- Server + Some local vars initialization to avoid a (wrong but blocking) error of "using + uninitialized var) in VS, GNU cpp didn't report it because the vars, when used + were initialized. + Thenks to Werner + + ======================================================================[2015-06-14] Version 1.4.0 - release for gourmets (fully compatible with 1.0.0) ---------------------------------------------------------------------------------- @@ -12,11 +76,11 @@ Version 1.4.0 - release for gourmets (fully compatible with 1.0.0) [Added] - Resourceless server - Now Snap7Server can work in "resourceless" mode, i.e. you dont need to share - resources (DB, E, A) with it : On every read/write request a callback is + Now Snap7Server can work in "resourceless" mode, i.e. you don t need to share + resources (DB, E, A ) with it : On every read/write request a callback is called passing it the TAG (Type of memory E,A,DB..,DB number if any, Start, Size, WordLen), and a pointer to the internal server area to read/write your - data on demand. + data on demand . The 1.4.0 wrappers were updated to reflect this new mode and I wrote some examples on how to use this feature, that however is very simple. @@ -103,7 +167,7 @@ Comfort - .net S7Client.ReadMultivars (and Write) was improved. (Thanks to LanceL who made it) - S7Client now checks if it's connected before do anything. - (Thanks to Mathias Ksel for reporting it) + (Thanks to Mathias K sel for reporting it) [Latest OS] @@ -195,10 +259,10 @@ Version 1.2.0 - New Minor platform release (fully compatible with 1.0.0) [fixed] - S7API directive missing for two functions in snap7_libmain.h - (Thanks to Mathias Ksel for reporting) + (Thanks to Mathias K sel for reporting) - fixed Snap7.S7Server.Srv_RegisterArea in snap7.net.cs - (Thanks to Andr for reporting). + (Thanks to Andr for reporting). - Added a static var to contain the callback addresses into c# examples. The .net garbage collector *sometime* garbages the delegates (called by unmanaged diff --git a/deps/snap7/src/core/s7_client.cpp b/deps/snap7/src/core/s7_client.cpp index 14df4d9..c4d56ca 100644 --- a/deps/snap7/src/core/s7_client.cpp +++ b/deps/snap7/src/core/s7_client.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_client.h b/deps/snap7/src/core/s7_client.h index 8fa3b44..fb0225e 100644 --- a/deps/snap7/src/core/s7_client.h +++ b/deps/snap7/src/core/s7_client.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_firmware.h b/deps/snap7/src/core/s7_firmware.h index ed019c2..35c641d 100644 --- a/deps/snap7/src/core/s7_firmware.h +++ b/deps/snap7/src/core/s7_firmware.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_isotcp.cpp b/deps/snap7/src/core/s7_isotcp.cpp index bfcd31d..6ec6bd3 100644 --- a/deps/snap7/src/core/s7_isotcp.cpp +++ b/deps/snap7/src/core/s7_isotcp.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_isotcp.h b/deps/snap7/src/core/s7_isotcp.h index 1a18ef7..39206f8 100644 --- a/deps/snap7/src/core/s7_isotcp.h +++ b/deps/snap7/src/core/s7_isotcp.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_micro_client.cpp b/deps/snap7/src/core/s7_micro_client.cpp index cadd3bc..2a1d871 100644 --- a/deps/snap7/src/core/s7_micro_client.cpp +++ b/deps/snap7/src/core/s7_micro_client.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_micro_client.h b/deps/snap7/src/core/s7_micro_client.h index 22229c4..416de1a 100644 --- a/deps/snap7/src/core/s7_micro_client.h +++ b/deps/snap7/src/core/s7_micro_client.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_partner.cpp b/deps/snap7/src/core/s7_partner.cpp index 08a29da..6babb66 100644 --- a/deps/snap7/src/core/s7_partner.cpp +++ b/deps/snap7/src/core/s7_partner.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_partner.h b/deps/snap7/src/core/s7_partner.h index 4da4c2b..6af24bc 100644 --- a/deps/snap7/src/core/s7_partner.h +++ b/deps/snap7/src/core/s7_partner.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_peer.cpp b/deps/snap7/src/core/s7_peer.cpp index 80ec3a7..9ee1ef2 100644 --- a/deps/snap7/src/core/s7_peer.cpp +++ b/deps/snap7/src/core/s7_peer.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | @@ -31,6 +31,7 @@ TSnap7Peer::TSnap7Peer() PDUH_out=PS7ReqHeader(&PDU.Payload); PDURequest=480; // Our request, FPDULength will contain the CPU answer LastError=0; + cntword = 0; Destroying = false; } //--------------------------------------------------------------------------- diff --git a/deps/snap7/src/core/s7_peer.h b/deps/snap7/src/core/s7_peer.h index adc4298..91b4a18 100644 --- a/deps/snap7/src/core/s7_peer.h +++ b/deps/snap7/src/core/s7_peer.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_server.cpp b/deps/snap7/src/core/s7_server.cpp index 84003ed..97667d2 100644 --- a/deps/snap7/src/core/s7_server.cpp +++ b/deps/snap7/src/core/s7_server.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | @@ -1244,10 +1244,9 @@ void TS7Worker::SZLSystemState() { SZL.Answer.Header.DataLen=SwapWord(sizeof(SZLSysState)); SZL.ResParams->Err =0x0000; - memcpy(SZL.ResData,&SZLNotAvail,sizeof(SZLSysState)); + memcpy(SZL.ResData,&SZLSysState,sizeof(SZLSysState)); isoSendBuffer(&SZL.Answer,28); SZL.SZLDone=true; - } void TS7Worker::SZLData(void *P, int len) { diff --git a/deps/snap7/src/core/s7_server.h b/deps/snap7/src/core/s7_server.h index 661a989..a0fb124 100644 --- a/deps/snap7/src/core/s7_server.h +++ b/deps/snap7/src/core/s7_server.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_text.cpp b/deps/snap7/src/core/s7_text.cpp index eb03af9..82ec283 100644 --- a/deps/snap7/src/core/s7_text.cpp +++ b/deps/snap7/src/core/s7_text.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_text.h b/deps/snap7/src/core/s7_text.h index a2d8a4f..01a338a 100644 --- a/deps/snap7/src/core/s7_text.h +++ b/deps/snap7/src/core/s7_text.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/core/s7_types.h b/deps/snap7/src/core/s7_types.h index f24c53f..714d19d 100644 --- a/deps/snap7/src/core/s7_types.h +++ b/deps/snap7/src/core/s7_types.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/lib/snap7_libmain.cpp b/deps/snap7/src/lib/snap7_libmain.cpp index 470bd9a..8f705c8 100644 --- a/deps/snap7/src/lib/snap7_libmain.cpp +++ b/deps/snap7/src/lib/snap7_libmain.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.4.1 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | @@ -542,7 +542,7 @@ int S7API Cli_ErrorText(int Error, char *Text, int TextLen) { try{ ErrCliText(Error, Text, TextLen); - Text[TextLen - 1] = '\0'; + Text[TextLen-1] = '\0'; } catch (...){ return errLibInvalidParam; @@ -894,7 +894,7 @@ int S7API Srv_ErrorText(int Error, char *Text, int TextLen) { try{ ErrSrvText(Error, Text, TextLen); - Text[TextLen - 1] = '\0'; + Text[TextLen-1] = '\0'; } catch (...){ return errLibInvalidParam; @@ -906,7 +906,7 @@ int S7API Srv_EventText(TSrvEvent &Event, char *Text, int TextLen) { try{ EvtSrvText(Event, Text, TextLen); - Text[TextLen - 1] = '\0'; + //Text[TextLen] = '\0'; } catch (...){ return errLibInvalidParam; @@ -1186,7 +1186,7 @@ int S7API Par_ErrorText(int Error, char *Text, int TextLen) { try{ ErrParText(Error, Text, TextLen); - Text[TextLen - 1] = '\0'; + Text[TextLen - 1] = '\0'; } catch (...){ return errLibInvalidParam; diff --git a/deps/snap7/src/lib/snap7_libmain.h b/deps/snap7/src/lib/snap7_libmain.h index 06381d4..77ae1e5 100644 --- a/deps/snap7/src/lib/snap7_libmain.h +++ b/deps/snap7/src/lib/snap7_libmain.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_msgsock.cpp b/deps/snap7/src/sys/snap_msgsock.cpp index 2ad9ee6..357a5d2 100644 --- a/deps/snap7/src/sys/snap_msgsock.cpp +++ b/deps/snap7/src/sys/snap_msgsock.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_msgsock.h b/deps/snap7/src/sys/snap_msgsock.h index 6446a48..8924f55 100644 --- a/deps/snap7/src/sys/snap_msgsock.h +++ b/deps/snap7/src/sys/snap_msgsock.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_platform.h b/deps/snap7/src/sys/snap_platform.h index b52b243..e37297c 100644 --- a/deps/snap7/src/sys/snap_platform.h +++ b/deps/snap7/src/sys/snap_platform.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.4.1 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | @@ -73,29 +73,7 @@ # error platform still unsupported (please add it yourself and report ;-) #endif -// Visual C++ not C99 compliant (VS2008--) -#ifdef _MSC_VER -# if _MSC_VER >= 1600 -# include // VS2010++ have it -# else - typedef signed __int8 int8_t; - typedef signed __int16 int16_t; - typedef signed __int32 int32_t; - typedef signed __int64 int64_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; - typedef unsigned __int64 uint64_t; - #ifdef _WIN64 - typedef unsigned __int64 uintptr_t; - #else - typedef unsigned __int32 uintptr_t; - #endif -# endif -#else -# include -#endif - +#include #include #include #include diff --git a/deps/snap7/src/sys/snap_sysutils.cpp b/deps/snap7/src/sys/snap_sysutils.cpp index e909064..5f91b03 100644 --- a/deps/snap7/src/sys/snap_sysutils.cpp +++ b/deps/snap7/src/sys/snap_sysutils.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_sysutils.h b/deps/snap7/src/sys/snap_sysutils.h index 0787d94..404ab1c 100644 --- a/deps/snap7/src/sys/snap_sysutils.h +++ b/deps/snap7/src/sys/snap_sysutils.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_tcpsrvr.cpp b/deps/snap7/src/sys/snap_tcpsrvr.cpp index 24e8968..e665331 100644 --- a/deps/snap7/src/sys/snap_tcpsrvr.cpp +++ b/deps/snap7/src/sys/snap_tcpsrvr.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_tcpsrvr.h b/deps/snap7/src/sys/snap_tcpsrvr.h index e09a163..3c2e40b 100644 --- a/deps/snap7/src/sys/snap_tcpsrvr.h +++ b/deps/snap7/src/sys/snap_tcpsrvr.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_threads.cpp b/deps/snap7/src/sys/snap_threads.cpp index fef4684..f1120b5 100644 --- a/deps/snap7/src/sys/snap_threads.cpp +++ b/deps/snap7/src/sys/snap_threads.cpp @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/snap_threads.h b/deps/snap7/src/sys/snap_threads.h index f4b8d82..845d8dc 100644 --- a/deps/snap7/src/sys/snap_threads.h +++ b/deps/snap7/src/sys/snap_threads.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/sol_threads.h b/deps/snap7/src/sys/sol_threads.h index 074ace0..100ffc4 100644 --- a/deps/snap7/src/sys/sol_threads.h +++ b/deps/snap7/src/sys/sol_threads.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/unix_threads.h b/deps/snap7/src/sys/unix_threads.h index 827adf8..468a1e1 100644 --- a/deps/snap7/src/sys/unix_threads.h +++ b/deps/snap7/src/sys/unix_threads.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/deps/snap7/src/sys/win_threads.h b/deps/snap7/src/sys/win_threads.h index cbfa911..76e36d0 100644 --- a/deps/snap7/src/sys/win_threads.h +++ b/deps/snap7/src/sys/win_threads.h @@ -1,7 +1,7 @@ /*=============================================================================| -| PROJECT SNAP7 1.3.0 | +| PROJECT SNAP7 1.4.3 | |==============================================================================| -| Copyright (C) 2013, 2015 Davide Nardella | +| Copyright (C) 2013, 2025 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | diff --git a/doc/client.md b/doc/client.md index 41e570a..270e84b 100644 --- a/doc/client.md +++ b/doc/client.md @@ -1,865 +1,706 @@ - ## S7Client - - [Control functions](#control-functions) - - [Connect()](#connect) - - [ConnectTo()](#connect-to) - - [SetConnectionParams()](#set-connection-params) - - [SetConnectionType()](#set-connection-type) - - [Disconnect()](#disconnect) - - [GetParam()](#get-param) - - [SetParam()](#set-param) - - [Data I/O functions](#data-functions) - - [ReadArea()](#read-area) - - [WriteArea()](#write-area) - - [DBRead()](#dbread) - - [DBWrite()](#dbwrite) - - [ABRead()](#abread) - - [ABWrite()](#abwrite) - - [EBRead()](#ebread) - - [EBWrite()](#ebwrite) - - [MBRead()](#mbread) - - [MBWrite()](#mbwrite) - - [TMRead()](#tmread) - - [TMWrite()](#tmwrite) - - [CTRead()](#ctread) - - [CTWrite()](#ctwrite) - - [ReadMultiVars()](#read-multi-vars) - - [WriteMultiVars()](#write-multi-vars) - - [Directory function](#directory-functions) - - [ListBlocks()](#list-blocks) - - [ListBlocksOfType()](#list-blocks-of-type) - - [GetAgBlockInfo()](#get-ag-blockinfo) - - [GetPgBlockInfo()](#get-pg-blockinfo) - - [Block oriented functions](#block-functions) - - [FullUpload()](#full-upload) - - [Upload()](#upload) - - [Download()](#download) - - [Delete()](#delete) - - [DBGet()](#dbget) - - [DBFill()](#dbfill) - - [Date/Time functions](#datetime-functions) - - [GetPlcDateTime()](#get-plc-datetime) - - [SetPlcDateTime()](#set-plc-datetime) - - [SetPlcSystemDateTime()](#set-plc-system-datetime) - - [System info functions](#systeminfo-functions) - - [ReadSZL()](#read-szl) - - [ReadSZLList()](#read-szl-list) - - [GetOrderCode()](#get-order-code) - - [GetCpuInfo()](#get-cpu-info) - - [GetCpInfo()](#get-cp-info) - - [PLC control functions](#control-functions) - - [PlcHotStart()](#plc-hot-start) - - [PlcColdStart()](#plc-cold-start) - - [PlcStop()](#plc-stop) - - [CopyRamToRom()](#copy-ram-to-rom) - - [Compress()](#compress) - - [Security functions](#security-functions) - - [SetSessionPassword()](#set-session-password) - - [ClearSessionPassword()](#clear-session-password) - - [GetProtection()](#get-protection) - - [Properties](#properties) - - [ExecTime()](#exec-time) - - [LastError()](#last-error) - - [PDURequested()](#pdu-requested) - - [PDULength()](#pdu-length) - - [PlcStatus()](#plc-status) - - [Connected()](#connected) - - [ErrorText()](#error-text) - -### API - Control functions - ----------- - -#### S7Client.Connect([callback]) -Connects the client to the PLC with the parameters specified in the previous call of `ConnectTo()` or `SetConnectionParams()`. - - - The optional `callback` parameter will be executed after connection attempt - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.ConnectTo(ip, rack, slot[, callback]) -Connects the client to the hardware at `ip`, `rack`, `slot` coordinates. - -- `ip` PLC/Equipment IPV4 Address ex. “192.168.1.12” -- `rack` PLC Rack number -- `slot` PLC Slot number -- The optional `callback` parameter will be executed after connection attempt - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.SetConnectionParams(ip, localTSAP, remoteTSAP) -Sets internally `ip`, `localTSAP`, `remoteTSAP` coordinates. - -- `ip` PLC/Equipment IPv4 address ex. “192.168.1.12” -- `localTSAP` Local TSAP (PC TSAP) -- `remoteTSAP` Remote TSAP (PLC TSAP) - -Returns `true` on success or `false` on error. - -#### S7Client.SetConnectionType(type) -Sets the connection resource type, i.e the way in which the Clients connects to a PLC. - -- `type` - -| ConnectionType | Value | Description | -|:--------------------------|:----------:|:------------| -| `S7Client.CONNTYPE_PG` | 0x01 | PG -| `S7Client.CONNTYPE_OP` | 0x02 | OP -| `S7Client.CONNTYPE_BASIC` | 0x03..0x10 | S7 Basic - -#### S7Client.Disconnect() -Disconnects “gracefully” the Client from the PLC. - -Returns `true` on success or `false` on error. - -#### S7Client.GetParam(paramNumber) -Reads an internal Client object parameter. - - - `paramNumber` One from the parameter list below - -| Name | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Client.RemotePort` | 2 | Socket remote Port -| `S7Client.PingTimeout` | 3 | Client Ping timeout -| `S7Client.SendTimeout` | 4 | Socket Send timeout -| `S7Client.RecvTimeout` | 5 | Socket Recv timeout -| `S7Client.SrcRef` | 7 | ISOTcp Source reference -| `S7Client.DstRef` | 8 | ISOTcp Destination reference -| `S7Client.SrcTSap` | 9 | ISOTcp Source TSAP -| `S7Client.PDURequest` | 10 | Initial PDU length request - -Returns the `parameter value` on success or `false` on error. - -#### S7Client.SetParam(paramNumber, value) -Sets an internal Client object parameter. - - - `paramNumber` One from the parameter list above - - `value` New parameter value - -Returns `true` on success or `false` on error. - -### API - Data I/O functions - ----------- - -#### S7Client.ReadArea(area, dbNumber, start, amount, wordLen[, callback]) -This is the main function to read data from a PLC. With it you can read DB, Inputs, Outputs, Merkers, Timers and Counters. - - - `area` Area identifier (see table [below](#table-area)) - - `dbNumber` DB number if area = S7AreaDB, otherwise ignored - - `start` Offset to start - - `amount` Amount of **words** to read - - `wordLen` Word size (see table [below](#table-wordlen)) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.WriteArea(area, dbNumber, start, amount, wordLen, buffer[, callback]) -This is the main function to write data into a PLC. - - - `area` Area identifier (see table [below](#table-area)) - - `dbNumber` DB number if area = S7AreaDB, otherwise ignored - - `start` Offset to start - - `amount` Amount of **words** to write - - `wordLen` Word size (see table [below](#table-wordlen)) - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - - - -| Area | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Client.S7AreaPE` | 0x81 | Process inputs -| `S7Client.S7AreaPA` | 0x82 | Process outputs -| `S7Client.S7AreaMK` | 0x83 | Merkers -| `S7Client.S7AreaDB` | 0x84 | DB -| `S7Client.S7AreaCT` | 0x1C | Counters -| `S7Client.S7AreaTM` | 0x1D | Timers - - - -| WordLen | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Client.S7WLBit` | 0x01 | Bit (inside a word) -| `S7Client.S7WLByte` | 0x02 | Byte (8 bit) -| `S7Client.S7WLWord` | 0x04 | Word (16 bit) -| `S7Client.S7WLDWord` | 0x06 | Double Word (32 bit) -| `S7Client.S7WLReal` | 0x08 | Real (32 bit float) -| `S7Client.S7WLCounter` | 0x1C | Counter (16 bit) -| `S7Client.S7WLTimer` | 0x1D | Timer (16 bit) - -#### S7Client.DBRead(dbNumber, start, size[, callback]) -This is a lean function of `ReadArea()` to read PLC DB. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaDB` and `wordLen = s7client.S7WLByte`. - - - `dbNumber` DB number - - `start` Offset to start - - `size` Size to read (bytes) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.DBWrite(dbNumber, start, size, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC DB. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaDB` and `wordLen = s7client.S7WLByte`. - - - `dbNumber` DB number - - `start` Offset to start - - `size` Size to write (bytes) - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.ABRead(start, size[, callback]) -This is a lean function of `ReadArea()` to read PLC process outputs. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaPA` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to read (bytes) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.ABWrite(start, size, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC process outputs. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaPA` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to write (bytes) - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.EBRead(start, size[, callback]) -This is a lean function of `ReadArea()` to read PLC process inputs. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaPE` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to read (bytes) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.EBWrite(start, size, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC process inputs. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaPE` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to write (bytes) - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.MBRead(start, size[, callback]) -This is a lean function of `ReadArea()` to read PLC Merkers. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaMK` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to read (bytes) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.MBWrite(start, size, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC Merkers. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaMK` and `wordLen = s7client.S7WLByte`. - - - `start` Offset to start - - `size` Size to write (bytes) - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.TMRead(start, amount[, callback]) -This is a lean function of `ReadArea()` to read PLC Timers. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaTM` and `wordLen = S7Client.S7WLTimer`. - - - `start` Offset to start - - `amount` Number of timers - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.TMWrite(start, amount, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC Timers. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaTM` and `wordLen = S7Client.S7WLTimer`. - - - `start` Offset to start - - `amount` Number of timers - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.CTRead(start, amount[, callback]) -This is a lean function of `ReadArea()` to read PLC Counters. -It simply internally calls `ReadArea()` with `area = S7Client.S7AreaCT` and `wordLen = S7Client.S7WLCounter`. - - - `start` Offset to start - - `amount` Number of counters - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns a `buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.CTWrite(start, amount, buffer[, callback]) -This is a lean function of `WriteArea()` to write PLC Counters. -It simply internally calls `WriteArea()` with `area = S7Client.S7AreaCT` and `wordLen = S7Client.S7WLCounter`. - - - `start` Offset to start - - `amount` Number of counters - - `buffer` User buffer - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.ReadMultiVars(multiVars[, callback]) -This is function allows to read different kind of variables from a PLC in a single call. With it you can read DB, Inputs, Outputs, Merkers, Timers and Counters. - - - `multiVars` Array of objects with read information (see structure below) - - The optional `callback` parameter will be executed after read - -If `callback` is **not** set the function is **blocking** and returns an `array` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: -```javascript -// multiVars array structure -[ - { - "Area": S7Client.S7AreaDB, - "WordLen": S7Client.S7WLByte, - "DBNumber": 1, - "Start": 0, - "Amount": 1 - }, - { - "Area": S7Client.S7AreaCT, - "WordLen": S7Client.S7WLCounter, - "Start": 0, - "Amount": 8 - }, - { - "Area": S7Client.S7AreaPA, - "WordLen": S7Client.S7WLByte, - "Start": 0, - "Amount": 16 - }, - ... -] -// result array -[ - { - "Result": 0, // Error code - "Data": ... // Buffer object or null if Result <> 0 - }, - ... -] -``` - -Since could happen that some variables are read, some other not because maybe they don't exist in PLC. It is important to check the single item result. - -Due the different kind of variables involved , there is no split feature available for this function, so the maximum data size must not exceed the PDU size. -The advantage of this function becomes big when you have many small non-contiguous variables to be read. - -#### S7Client.WriteMultiVars(multiVars[, callback]) -This is function allows to write different kind of variables into a PLC in a single call. With it you can write DB, Inputs, Outputs, Merkers, Timers and Counters. - - - `multiVars` Array of objects with write information (see structure below) - - The optional `callback` parameter will be executed after write - -If `callback` is **not** set the function is **blocking** and returns an `array` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: -```javascript -// multiVars array structure -[ - { - "Area": S7Client.S7AreaDB, - "WordLen": S7Client.S7WLByte, - "DBNumber": 1, - "Start": 0, - "Amount": 1, - "Data": buffer1 // Buffer variable - }, - { - "Area": S7Client.S7AreaCT, - "WordLen": S7Client.S7WLCounter, - "Start": 0, - "Amount": 8, - "Data": buffer2 // Buffer variable - }, - { - "Area": S7Client.S7AreaPA, - "WordLen": S7Client.S7WLByte, - "Start": 0, - "Amount": 16, - "Data": buffer3 // Buffer variable - }, - ... -] -// result array -[ - { - "Result": 0 // Error code - }, - ... -] -``` - -### API - Directory functions - ----------- - -#### S7Client.ListBlocks([callback]) -This function returns an object of the AG blocks amount divided by type. - -- The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `object` (see below) on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: -```javascript -{ - "OBCount": 0, - "FBCount": 0, - "FCCount": 0, - "SFBCount": 0, - "SFCCount": 0, - "DBCount": 0, - "SDBCount": 0 -} -``` - -#### S7Client.ListBlocksOfType(blockType[, callback]) -This function returns an array of the AG list of a specified block type. - - - `blockType` Type of block (see table below) - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `array` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Each item of the result array will contain a block number. - - - -| BlockType | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Client.Block_OB` | 0x38 | OB -| `S7Client.Block_DB` | 0x41 | DB -| `S7Client.Block_SDB` | 0x42 | SDB -| `S7Client.Block_FC` | 0x43 | FC -| `S7Client.Block_SFC` | 0x44 | SFC -| `S7Client.Block_FB` | 0x45 | FB -| `S7Client.Block_SFB` | 0x46 | SFB - -#### S7Client.GetAgBlockInfo(blockType, blockNum[, callback]) -Returns an object with detailed information about a given AG block. -This function is very useful if you need to read or write data in a DB which you do not know the size in advance (see MC7Size field) - - - `blockType` Type of block (see table [above](#table-blocktype)) - - `blockNum` Number of block - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `object` (see below) on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: - -```javascript -{ - "BlkType": , // Block Type (see SubBlkType table) - "BlkNumber": , // Block number - "BlkLang": , // Block Language (see LangType Table) - "BlkFlags": , // Block flags (bitmapped) - "MC7Size": , // The real size in bytes - "LoadSize": , // Load memory size - "LocalData": , // Local data - "SBBLength": , // SBB Length - "CheckSum": , // Checksum - "Version": , // Version (BCD 00) - "CodeDate": , // Code date - "IntfDate": , // Interface date - "Author": , // Author - "Family": , // Family - "Header": // Header -} -``` - -| SubBlockType | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Client.SubBlk_OB` | 0x08 | OB -| `S7Client.SubBlk_DB` | 0x0A | DB -| `S7Client.SubBlk_SDB` | 0x0B | SDB -| `S7Client.SubBlk_FC` | 0x0C | FC -| `S7Client.SubBlk_SFC` | 0x0D | SFC -| `S7Client.SubBlk_FB` | 0x0E | FB -| `S7Client.SubBlk_SFB` | 0x0F | SFB - -| LangType | Value | Description | -|:--------------------------|:-----:|:------------| -| `S7Client.BlockLangAWL` | 0x01 | AWL -| `S7Client.BlockLangKOP` | 0x02 | KOP -| `S7Client.BlockLangFUP` | 0x03 | FUP -| `S7Client.BlockLangSCL` | 0x04 | SCL -| `S7Client.BlockLangDB` | 0x05 | DB -| `S7Client.BlockLangGRAPH` | 0x06 | GRAPH - -#### S7Client.GetPgBlockInfo(buffer) -Returns detailed information about a block present in a user buffer. This function is usually used in conjunction with `FullUpload()`. -An uploaded block saved to disk, could be loaded in a user buffer and checked with this function. - - - `buffer` User buffer - -Returns an `object` (see [example](#example-blockinfo) above) on success or `false`on error. - -### API - Block oriented functions - ----------- - -#### S7Client.FullUpload(blockType, blockNum, size[, callback]) -Uploads a block from AG. The whole block (including header and footer) is copied into the user buffer. - - - `blockType` Type of block (see table [above](#table-blocktype)) - - `blockNum` Number of block - - `size` Buffer size (if smaller than the data -uploaded, only `size` bytes are copied and `errCliPartialDataRead` is returned) - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns a `Buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.Upload(blockType, blockNum[, callback]) -Uploads a block body from AG. Only the block body (but header and footer) is copied into the user buffer. - - - `blockType` Type of block (see table [above](#table-blocktype)) - - `blockNum` Number of block - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns a `Buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.Download(blockNum, buffer[, callback]) -Downloads a block into AG. A whole block (including header and footer) must be available into the user buffer. - - - `blockNum` Number of block - - `buffer` User buffer - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -If the parameter `blockNum` is `-1`, the block number is not changed else the block is downloaded with the provided number (just like a “Download As…”). - -#### S7Client.Delete(blockType, blockNum[, callback]) -Deletes a block into AG. - - !!! There is no undo function available !!! - - - `blockType` Type of block (see table [above](#table-blocktype)) - - `blockNum` Number of block - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.DBGet(dbNumber[, callback]) -Uploads a DB from AG. This function is equivalent to `Upload()` with `BlockType = Block_DB` but it uses a different approach so it’s not subject to the security level set. - -Only data is uploaded. - - - `dbNumber` DB number - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns a `Buffer` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -This function first gathers the DB size via `GetAgBlockInfo()` then calls `DBRead()`. - -#### S7Client.DBFill(dbNumber, fillChar[, callback]) -Fills a DB in AG with a given byte without the need of specifying its size. - - - `dbNumber` DB number - - `fillChar` char or char code - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -### API - Date/Time functions - ----------- - -#### S7Client.GetPlcDateTime([callback]) -Reads PLC date and time. - -- The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns a javascript `Date()` object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.SetPlcDateTime(dateTime[, callback]) -Sets the PLC date and time. - - - `dateTime` - - The optional `callback` parameter will be executed after completion - -The `dateTime` argument can be a javascript `Date()` object or an object with the structure below. -```javascript -{ - "year": 2015, // year - "month": 4, // months since January 0-11 - "day": 3, // day of the month 1-31 - "hours": 19, // hours since midnight 0-23 - "minutes": 37, // minutes after the hour 0-59 - "seconds": 0 // seconds after the minute 0-59 -} +# S7Client API + +High-level access to Siemens S7 PLCs via Snap7. Most methods support Promise and callback forms plus a `Sync` variant. + +- Promise form: resolves with the value below; rejects with `Snap7Error` (`code`, `errno`). +- Callback form: pass `(err, result)` as the last argument; function returns `undefined`. +- Sync form: `*Sync` variant returns the value or `undefined`, or throws `Snap7Error`. + +--- + +## Table of Contents +- [Usage basics](#usage-basics) +- [Control functions](#control-functions) + - [Connect](#connect) + - [ConnectTo](#connectto) + - [SetConnectionParams](#setconnectionparams) + - [SetConnectionType](#setconnectiontype) + - [Disconnect](#disconnect) + - [GetParam](#getparam) + - [SetParam](#setparam) +- [Data I/O functions](#data-io-functions) + - [ReadArea](#readarea) + - [WriteArea](#writearea) + - [DBRead](#dbread) + - [DBWrite](#dbwrite) + - [MBRead](#mbread) + - [MBWrite](#mbwrite) + - [EBRead](#ebread) + - [EBWrite](#ebwrite) + - [ABRead](#abread) + - [ABWrite](#abwrite) + - [TMRead](#tmread) + - [TMWrite](#tmwrite) + - [CTRead](#ctread) + - [CTWrite](#ctwrite) + - [ReadMultiVars](#readmultivars) + - [WriteMultiVars](#writemultivars) +- [Directory functions](#directory-functions) + - [ListBlocks](#listblocks) + - [ListBlocksOfType](#listblocksoftype) + - [GetAgBlockInfo](#getagblockinfo) + - [GetPgBlockInfo](#getpgblockinfo) +- [Block operations](#block-operations) + - [FullUpload](#fullupload) + - [Upload](#upload) + - [Download](#download) + - [Delete](#delete) + - [DBGet](#dbget) + - [DBFill](#dbfill) +- [Date/Time functions](#datetime-functions) + - [GetPlcDateTime](#getplcdatetime) + - [SetPlcDateTime](#setplcdatetime) + - [SetPlcSystemDateTime](#setplcsystemdatetime) +- [System info functions](#system-info-functions) + - [ReadSZL](#readszl) + - [ReadSZLList](#readszllist) + - [GetOrderCode](#getordercode) + - [GetCpuInfo](#getcpuinfo) + - [GetCpInfo](#getcpinfo) +- [PLC control functions](#plc-control-functions) + - [PlcHotStart](#plchotstart) + - [PlcColdStart](#plccoldstart) + - [PlcStop](#plcstop) + - [CopyRamToRom](#copyramtorom) + - [Compress](#compress) + - [PlcStatus](#plcstatus) +- [Security functions](#security-functions) + - [SetSessionPassword](#setsessionpassword) + - [ClearSessionPassword](#clearsessionpassword) + - [GetProtection](#getprotection) +- [Properties & diagnostics](#properties--diagnostics) +- [Constants](#constants) + - [Areas](#areas) + - [Word lengths](#word-lengths) + - [Connection types](#connection-types) + - [Block types](#block-types) + - [PLC status codes](#plc-status-codes) + - [Client parameters](#client-parameters) +- [Type definitions](#type-definitions) + - [BlocksList](#blockslist) + - [BlockInfo](#blockinfo) + - [OrderCode](#ordercode) + - [CpuInfo](#cpuinfo) + - [CpInfo](#cpinfo) + - [Protection](#protection) + - [DateTimeObject](#datetimeobject) + - [S7MultiVarReadResult](#s7multivarreadresult) + - [S7MultiVarWriteResult](#s7multivarwriteresult) + +--- + +## Usage basics +- Use constants from [Constants](#constants) for `Area`, `WordLen`, connection types, etc. +- `start` is a byte offset unless `WordLen` is `S7WLBit`, in which case it is a bit index within the byte. +- `amount` counts elements of the chosen `WordLen` (bytes for `S7WLByte`, words for `S7WLWord`, etc.). +- Buffers you pass must be large enough for `amount * WordLen`. + +--- + +## Control functions + +### Connect ``` - -If `callback` is **not** set the function is **blocking** and returns a `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Client.SetPlcSystemDateTime([callback]) -Sets the PLC date and time in accord to the PC system Date/Time. - - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -### API - System info functions - ----------- - -#### S7Client.ReadSZL(id, index[, callback]) -Reads a partial list of given `id`and `index`. - - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns a `buffer` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.ReadSZLList([callback]) -Reads the directory of the partial lists. - - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `array` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -#### S7Client.GetOrderCode([callback]) -Gets CPU order code and version info. - - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `object` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: -```javascript -{ - "Code": , // Order Code - "V1": , // Version V1.V2.V3 - "V2": , - "V3": -} +Connect(): Promise +Connect(callback: (err: Snap7Error | null) => undefined): undefined +ConnectSync(): undefined ``` +Connect using the parameters set via `ConnectTo` or `SetConnectionParams`. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.GetCpuInfo([callback]) -Gets CPU module name, serial number and other info. - - - The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns an `object` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. - -Example: -```javascript -{ - "ModuleTypeName": , - "SerialNumber": , - "ASName": , - "Copyright": , - "ModuleName": -} +### ConnectTo ``` +ConnectTo(ip: string, rack: number, slot: number): Promise +ConnectTo(ip: string, rack: number, slot: number, callback: (err: Snap7Error | null) => undefined): undefined +ConnectToSync(ip: string, rack: number, slot: number): undefined +``` +Connect directly to the given IP, rack, and slot. +- Parameters: + - `ip`: PLC IPv4 address + - `rack`: rack number + - `slot`: slot number +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) + +### SetConnectionParams +``` +SetConnectionParams(ip: string, localTSAP: number, remoteTSAP: number): undefined +``` +Set ISO-on-TCP parameters prior to connecting. +- Parameters: + - `ip`: PLC IPv4 address + - `localTSAP`: local TSAP + - `remoteTSAP`: remote TSAP +- Returns: `undefined` + +### SetConnectionType +``` +SetConnectionType(type: number): undefined +``` +Set the connection resource type. +- Parameters: + - `type`: see [Connection types](#connection-types) +- Returns: `undefined` -#### S7Client.GetCpInfo([callback]) -Gets CP (communication processor) info. - - - The optional `callback` parameter will be executed after completion +### Disconnect +``` +Disconnect(): undefined +``` +Close the connection. +- Returns: `undefined` -If `callback` is **not** set the function is **blocking** and returns an `object` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. +### GetParam +``` +GetParam(paramNumber: number): number +``` +Read a client parameter. +- Parameters: + - `paramNumber`: see [Client parameters](#client-parameters) +- Returns: parameter value -Example: -```javascript -{ - "MaxPduLength": , - "MaxConnections": , - "MaxMpiRate": , - "MaxBusRate": -} +### SetParam +``` +SetParam(paramNumber: number, value: number): undefined ``` +Write a client parameter. +- Parameters: + - `paramNumber`: see [Client parameters](#client-parameters) + - `value`: new value +- Returns: `undefined` -### API - PLC control functions +--- ----------- +## Data I/O functions -#### S7Client.PlcHotStart([callback]) -Puts the CPU in RUN mode performing an HOT START. +### ReadArea +``` +ReadArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen): Promise +ReadArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, callback: (err: Snap7Error | null, data: Buffer) => undefined): undefined +ReadAreaSync(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen): Buffer +``` +Read raw data from the PLC. +- Parameters: + - `area`: memory area + - `dbNumber`: DB number (only for `S7AreaDB`) + - `start`: byte offset (bit index if `S7WLBit`) + - `amount`: element count in units of `wordLen` + - `wordLen`: element type/size +- Returns: Promise resolves with `Buffer`; callback receives `(err, buffer)`; Sync returns `Buffer` (throws on error) + +### WriteArea +``` +WriteArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer): Promise +WriteArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer, callback: (err: Snap7Error | null) => undefined): undefined +WriteAreaSync(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer): undefined +``` +Write raw data to the PLC. +- Parameters: + - `area`: memory area + - `dbNumber`: DB number (only for `S7AreaDB`) + - `start`: byte offset (bit index if `S7WLBit`) + - `amount`: element count in units of `wordLen` + - `wordLen`: element type/size + - `buffer`: data to write +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) + +### Convenience read/write wrappers +Each wrapper supports Promise/Callback/Sync variants like `Foo(...) -> Promise<...>`, `Foo(..., callback) -> undefined`, `FooSync(...) -> ...`. +- DB: `DBRead`, `DBWrite` +- Merker: `MBRead`, `MBWrite` +- Inputs: `EBRead`, `EBWrite` +- Outputs: `ABRead`, `ABWrite` +- Timers: `TMRead`, `TMWrite` +- Counters: `CTRead`, `CTWrite` + +### ReadMultiVars +``` +ReadMultiVars(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number }[]): Promise +ReadMultiVars(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number }[], callback: (err: Snap7Error | null, data: S7MultiVarReadResult[]) => undefined): undefined +ReadMultiVarsSync(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number }[]): S7MultiVarReadResult[] +``` +Read multiple addresses in one request. +- Parameters: + - `items`: `{ Area, WordLen, DBNumber?, Start, Amount }` +- Returns: Promise resolves with `S7MultiVarReadResult[]`; callback receives `(err, results)`; Sync returns `S7MultiVarReadResult[]` (throws on error) - - The optional `callback` parameter will be executed after completion +### WriteMultiVars +``` +WriteMultiVars(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number; Data: Buffer }[]): Promise +WriteMultiVars(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number; Data: Buffer }[], callback: (err: Snap7Error | null, data: S7MultiVarWriteResult[]) => undefined): undefined +WriteMultiVarsSync(items: { Area: Area; WordLen: WordLen; DBNumber?: number; Start: number; Amount: number; Data: Buffer }[]): S7MultiVarWriteResult[] +``` +Write multiple addresses in one request. +- Parameters: + - `items`: `{ Area, WordLen, DBNumber?, Start, Amount, Data: Buffer }` +- Returns: Promise resolves with `S7MultiVarWriteResult[]`; callback receives `(err, results)`; Sync returns `S7MultiVarWriteResult[]` (throws on error) -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +--- -#### S7Client.PlcColdStart([callback]) -Puts the CPU in RUN mode performing a COLD START. +## Directory functions - - The optional `callback` parameter will be executed after completion +### ListBlocks +``` +ListBlocks(): Promise +ListBlocks(callback: (err: Snap7Error | null, data: BlocksList) => undefined): undefined +ListBlocksSync(): BlocksList +``` +Get a count of blocks on the PLC. +- Returns: Promise resolves with `BlocksList`; callback receives `(err, blocks)`; Sync returns `BlocksList` (throws on error) -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +### ListBlocksOfType +``` +ListBlocksOfType(blockType: BlockType): Promise +ListBlocksOfType(blockType: BlockType, callback: (err: Snap7Error | null, data: number[]) => undefined): undefined +ListBlocksOfTypeSync(blockType: BlockType): number[] +``` +List block numbers of a specific type. +- Parameters: + - `blockType`: block type code +- Returns: Promise resolves with `number[]`; callback receives `(err, numbers)`; Sync returns `number[]` (throws on error) -#### S7Client.PlcStop([callback]) -Puts the CPU in STOP mode. +### GetAgBlockInfo +``` +GetAgBlockInfo(blockType: BlockType, blockNum: number): Promise +GetAgBlockInfo(blockType: BlockType, blockNum: number, callback: (err: Snap7Error | null, data: BlockInfo) => undefined): undefined +GetAgBlockInfoSync(blockType: BlockType, blockNum: number): BlockInfo +``` +Get metadata for a block on the PLC. +- Parameters: + - `blockType`: block type code + - `blockNum`: block number +- Returns: Promise resolves with `BlockInfo`; callback receives `(err, info)`; Sync returns `BlockInfo` (throws on error) - - The optional `callback` parameter will be executed after completion +### GetPgBlockInfo +``` +GetPgBlockInfo(buffer: Buffer): BlockInfo +``` +Get metadata for a block stored in a buffer. +- Parameters: + - `buffer`: block buffer +- Returns: `BlockInfo` (throws on error) -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +--- -#### S7Client.CopyRamToRom(timeout[, callback]) -Performs the Copy Ram to Rom action. +## Block operations - - `timeout` Maximum time expected to complete the operation (ms) - - The optional `callback` parameter will be executed after completion or on timeout +### FullUpload +``` +FullUpload(blockType: BlockType, blockNum: number, size: number): Promise +FullUpload(blockType: BlockType, blockNum: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => undefined): undefined +FullUploadSync(blockType: BlockType, blockNum: number, size: number): Buffer +``` +Download an entire block including headers. +- Parameters: + - `blockType`: block type code + - `blockNum`: block number + - `size`: max bytes to read +- Returns: Promise resolves with `Buffer`; callback receives `(err, buffer)`; Sync returns `Buffer` (throws on error) + +### Upload +``` +Upload(blockType: BlockType, blockNum: number, size: number): Promise +Upload(blockType: BlockType, blockNum: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => undefined): undefined +UploadSync(blockType: BlockType, blockNum: number, size: number): Buffer +``` +Download the MC7 code portion of a block. +- Parameters: + - `blockType`: block type code + - `blockNum`: block number + - `size`: max bytes to read +- Returns: Promise resolves with `Buffer`; callback receives `(err, buffer)`; Sync returns `Buffer` (throws on error) + +### Download +``` +Download(blockNum: number, buffer: Buffer): Promise +Download(blockNum: number, buffer: Buffer, callback: (err: Snap7Error | null) => undefined): undefined +DownloadSync(blockNum: number, buffer: Buffer): undefined +``` +Upload a compiled block buffer to the PLC. +- Parameters: + - `blockNum`: block number + - `buffer`: compiled block data +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +### Delete +``` +Delete(blockType: BlockType, blockNum: number): Promise +Delete(blockType: BlockType, blockNum: number, callback: (err: Snap7Error | null) => undefined): undefined +DeleteSync(blockType: BlockType, blockNum: number): undefined +``` +Delete a block on the PLC. +- Parameters: + - `blockType`: block type code + - `blockNum`: block number +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -Not all CPUs support this operation. -The CPU must be in STOP mode. +### DBGet +``` +DBGet(dbNumber: number): Promise +DBGet(dbNumber: number, callback: (err: Snap7Error | null, data: Buffer) => undefined): undefined +DBGetSync(dbNumber: number): Buffer +``` +Fetch the raw content of a DB. +- Parameters: + - `dbNumber`: DB number +- Returns: Promise resolves with `Buffer`; callback receives `(err, buffer)`; Sync returns `Buffer` (throws on error) -#### S7Client.Compress(timeout[, callback]) -Performs the Memory compress action. +### DBFill +``` +DBFill(dbNumber: number, fillChar: number | string): Promise +DBFill(dbNumber: number, fillChar: number | string, callback: (err: Snap7Error | null) => undefined): undefined +DBFillSync(dbNumber: number, fillChar: number | string): undefined +``` +Fill an entire DB with a byte or character. +- Parameters: + - `dbNumber`: DB number + - `fillChar`: fill byte or single-character string +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) - - `timeout` Maximum time expected to complete the operation (ms) - - The optional `callback` parameter will be executed after completion or on timeout +--- -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +## Date/Time functions -### API - Security functions +### GetPlcDateTime +``` +GetPlcDateTime(): Promise +GetPlcDateTime(callback: (err: Snap7Error | null, data: Date) => undefined): undefined +GetPlcDateTimeSync(): Date +``` +Read the PLC system clock. +- Returns: Promise resolves with `Date`; callback receives `(err, date)`; Sync returns `Date` (throws on error) ----------- +### SetPlcDateTime +``` +SetPlcDateTime(dateTime: Date | [DateTimeObject](#datetimeobject)): Promise +SetPlcDateTime(dateTime: Date | [DateTimeObject](#datetimeobject), callback: (err: Snap7Error | null) => undefined): undefined +SetPlcDateTimeSync(dateTime: Date | [DateTimeObject](#datetimeobject)): undefined +``` +Set the PLC system clock. +- Parameters: + - `dateTime`: desired time (Date or [DateTimeObject](#datetimeobject)) +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.SetSessionPassword(password[, callback]) -Send the password to the PLC to meet its security level. +### SetPlcSystemDateTime +``` +SetPlcSystemDateTime(): Promise +SetPlcSystemDateTime(callback: (err: Snap7Error | null) => undefined): undefined +SetPlcSystemDateTimeSync(): undefined +``` +Set the PLC system clock to the host time. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) - - `password` Password - - The optional `callback` parameter will be executed after completion +--- -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +## System info functions -A `password` accepted by a PLC is an 8 chars string, a longer password will be trimmed, and a shorter one will be "right space padded". +### ReadSZL +``` +ReadSZL(id: number, index: number): Promise +ReadSZL(id: number, index: number, callback: (err: Snap7Error | null, data: Buffer) => undefined): undefined +ReadSZLSync(id: number, index: number): Buffer +``` +Read a System-Zustands-Listen (SZL) record. +- Parameters: + - `id`: SZL ID + - `index`: SZL index +- Returns: Promise resolves with `Buffer`; callback receives `(err, buffer)`; Sync returns `Buffer` (throws on error) -#### S7Client.ClearSessionPassword([callback]) -Clears the password set for the current session (logout). +### ReadSZLList +``` +ReadSZLList(): Promise +ReadSZLList(callback: (err: Snap7Error | null, data: number[]) => undefined): undefined +ReadSZLListSync(): number[] +``` +List available SZL IDs. +- Returns: Promise resolves with `number[]`; callback receives `(err, ids)`; Sync returns `number[]` (throws on error) - - The optional `callback` parameter will be executed after completion +### GetOrderCode +``` +GetOrderCode(): Promise +GetOrderCode(callback: (err: Snap7Error | null, data: OrderCode) => undefined): undefined +GetOrderCodeSync(): OrderCode +``` +Retrieve the PLC order code. +- Returns: Promise resolves with `OrderCode`; callback receives `(err, code)`; Sync returns `OrderCode` (throws on error) -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. +### GetCpuInfo +``` +GetCpuInfo(): Promise +GetCpuInfo(callback: (err: Snap7Error | null, data: CpuInfo) => undefined): undefined +GetCpuInfoSync(): CpuInfo +``` +Get CPU identification data. +- Returns: Promise resolves with `CpuInfo`; callback receives `(err, info)`; Sync returns `CpuInfo` (throws on error) -#### S7Client.GetProtection([callback]) -Gets the CPU protection level info. +### GetCpInfo +``` +GetCpInfo(): Promise +GetCpInfo(callback: (err: Snap7Error | null, data: CpInfo) => undefined): undefined +GetCpInfoSync(): CpInfo +``` +Get communication processor information. +- Returns: Promise resolves with `CpInfo`; callback receives `(err, info)`; Sync returns `CpInfo` (throws on error) - - The optional `callback` parameter will be executed after completion +--- -If `callback` is **not** set the function is **blocking** and returns the protection object on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. +## PLC control functions -Example: -```javascript -{ - "sch_schal": 1, - "sch_par": 0, - "sch_rel": 0, - "bart_sch": 1, - "anl_sch": 0 -} +### PlcHotStart ``` +PlcHotStart(): Promise +PlcHotStart(callback: (err: Snap7Error | null) => undefined): undefined +PlcHotStartSync(): undefined +``` +Warm-start the PLC. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -| S7Protection | Values | Description | -|:-------------|:--------|:------------| -| `sch_schal` | 1,2,3 | Protection level set with the mode selector -| `sch_par` | 0,1,2,3 | Password level, 0 : no password -| `sch_rel` | 0,1,2,3 | Valid protection level of the CPU -| `bart_sch` | 1,2,3,4 | Mode selector setting (1:RUN, 2:RUN-P, 3:STOP, :MRES, 0:undefined or cannot be determined) -| `anl_sch` | 0,1,2 | Startup switch setting (1:CRST, 2:WRST, 0:undefined, does not exist of cannot be determined) - -### API - Properties - ----------- +### PlcColdStart +``` +PlcColdStart(): Promise +PlcColdStart(callback: (err: Snap7Error | null) => undefined): undefined +PlcColdStartSync(): undefined +``` +Cold-start the PLC. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.ExecTime() -Returns the last job execution time in milliseconds or `false`on error. +### PlcStop +``` +PlcStop(): Promise +PlcStop(callback: (err: Snap7Error | null) => undefined): undefined +PlcStopSync(): undefined +``` +Stop the PLC CPU. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.LastError() -Returns the last job result. +### CopyRamToRom +``` +CopyRamToRom(timeout: number): Promise +CopyRamToRom(timeout: number, callback: (err: Snap7Error | null) => undefined): undefined +CopyRamToRomSync(timeout: number): undefined +``` +Copy RAM to ROM on the PLC. +- Parameters: + - `timeout`: timeout in milliseconds +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.PDURequested() -Returns the PDU length requested by the client or `false` on error. The requested PDU length can be modified with [SetParam()](#set-param). +### Compress +``` +Compress(timeout: number): Promise +Compress(timeout: number, callback: (err: Snap7Error | null) => undefined): undefined +CompressSync(timeout: number): undefined +``` +Compress PLC memory. +- Parameters: + - `timeout`: timeout in milliseconds +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -#### S7Client.PDULength() -Returns the PDU length negotiated between the client and the PLC during the connection or `false` on error. +### PlcStatus +``` +PlcStatus(): Promise +PlcStatus(callback: (err: Snap7Error | null, data: PlcStatus) => undefined): undefined +PlcStatusSync(): PlcStatus +``` +Read the current CPU status. +- Returns: Promise resolves with `PlcStatus`; callback receives `(err, status)`; Sync returns `PlcStatus` (throws on error) -It’s useful to know the PDU negotiated when we need to call `ReadMultivar()` or `WriteMultiVar()`. All other data transfer functions handle this information by themselves and split the telegrams automatically if needed. +--- -#### S7Client.PlcStatus([callback]) -Returns the CPU status (running/stopped). +## Security functions - - The optional `callback` parameter will be executed after completion +### SetSessionPassword +``` +SetSessionPassword(password: string): Promise +SetSessionPassword(password: string, callback: (err: Snap7Error | null) => undefined): undefined +SetSessionPasswordSync(password: string): undefined +``` +Set a session password for the current connection. +- Parameters: + - `password`: session password +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -If `callback` is **not** set the function is **blocking** and returns the CPU status on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` and `result` argument is given to the callback. +### ClearSessionPassword +``` +ClearSessionPassword(): Promise +ClearSessionPassword(callback: (err: Snap7Error | null) => undefined): undefined +ClearSessionPasswordSync(): undefined +``` +Clear the active session password. +- Returns: Promise resolves with `undefined`; callback receives `(err)`; Sync returns `undefined` (throws on error) -| Status | Value | Description | -|:------------------------------|:-----:|:------------| -| `S7Client.S7CpuStatusUnknown` | 0x00 | The CPU status is unknown -| `S7Client.S7CpuStatusRun` | 0x08 | The CPU is running -| `S7Client.S7CpuStatusStop` | 0x04 | The CPU is stopped +### GetProtection +``` +GetProtection(): Promise +GetProtection(callback: (err: Snap7Error | null, data: Protection) => undefined): undefined +GetProtectionSync(): Protection +``` +Read the PLC protection levels. +- Returns: Promise resolves with `Protection`; callback receives `(err, protection)`; Sync returns `Protection` (throws on error) + +--- + +## Properties & diagnostics +- `ExecTime() -> number` — execution time (ms) of the last job. +- `PDURequested() -> number` — requested PDU length during negotiation. +- `PDULength() -> number` — negotiated PDU length. +- `Connected() -> boolean` — connection state. +- `ErrorText(code: number) -> string` — human-readable text for a Snap7 error code. + +--- + +## Constants + +### Areas +| Name | Value | Description | +|:-----|:----:|:------------| +| `srvAreaPE` | 0 | Process inputs | +| `srvAreaPA` | 1 | Process outputs | +| `srvAreaMK` | 2 | Merkers | +| `srvAreaCT` | 3 | Counters | +| `srvAreaTM` | 4 | Timers | +| `srvAreaDB` | 5 | Data blocks | + +### Operation types +| Name | Value | Description | +|:-----|:----:|:------------| +| `operationRead` | 0x00 | Read operation | +| `operationWrite` | 0x01 | Write operation | + +### Server status codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `SrvStopped` | 0x00 | Server stopped | +| `SrvRunning` | 0x01 | Server running | +| `SrvError` | 0x02 | Server error | + +### CPU status codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `S7CpuStatusUnknown` | 0x00 | Status not known | +| `S7CpuStatusRun` | 0x08 | CPU is running | +| `S7CpuStatusStop` | 0x04 | CPU is stopped | + +### Server parameters +| Name | Value | Description | +|:-----|:----:|:------------| +| `LocalPort` | 1 | Listener port | +| `WorkInterval` | 6 | Worker interval | +| `PDURequest` | 10 | Requested PDU length | +| `MaxClients` | 11 | Maximum clients | + +### Event masks +| Name | Value | Description | +|:-----|:----:|:------------| +| `evcAll` | 0xFFFFFFFF | Enable all events | +| `evcNone` | 0x00000000 | Disable all events | + +### Event codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `evcServerStarted` | 0x00000001 | Server started | +| `evcServerStopped` | 0x00000002 | Server stopped | +| `evcListenerCannotStart` | 0x00000004 | Listener failed to start | +| `evcClientAdded` | 0x00000008 | Client added | +| `evcClientRejected` | 0x00000010 | Client rejected | +| `evcClientNoRoom` | 0x00000020 | No room for client | +| `evcClientException` | 0x00000040 | Client exception | +| `evcClientDisconnected` | 0x00000080 | Client disconnected | +| `evcClientTerminated` | 0x00000100 | Client terminated | +| `evcClientsDropped` | 0x00000200 | Clients dropped | +| `evcPDUincoming` | 0x00010000 | Incoming PDU | +| `evcDataRead` | 0x00020000 | Data read | +| `evcDataWrite` | 0x00040000 | Data write | +| `evcNegotiatePDU` | 0x00080000 | PDU negotiation | +| `evcReadSZL` | 0x00100000 | SZL read | +| `evcClock` | 0x00200000 | Clock event | +| `evcUpload` | 0x00400000 | Upload | +| `evcDownload` | 0x00800000 | Download | +| `evcDirectory` | 0x01000000 | Directory | +| `evcSecurity` | 0x02000000 | Security | +| `evcControl` | 0x04000000 | Control | + +### Event subcodes +| Name | Value | Description | +|:-----|:----:|:------------| +| `evsUnknown` | 0x00000000 | Unknown | +| `evsStartUpload` | 0x00000001 | Start upload | +| `evsStartDownload` | 0x00000002 | Start download | +| `evsGetBlockList` | 0x00000003 | Get block list | +| `evsStartListBoT` | 0x00000004 | Start list BoT | +| `evsListBoT` | 0x00000005 | List BoT | +| `evsGetBlockInfo` | 0x00000006 | Get block info | +| `evsGetClock` | 0x00000007 | Get clock | +| `evsSetClock` | 0x00000008 | Set clock | +| `evsSetPassword` | 0x00000009 | Set password | +| `evsClrPassword` | 0x0000000A | Clear password | + +### Event control codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `CodeControlUnknown` | 0x00 | Unknown control | +| `CodeControlColdStart` | 0x01 | Cold start | +| `CodeControlWarmStart` | 0x02 | Warm start | +| `CodeControlStop` | 0x03 | Stop | +| `CodeControlCompress` | 0x04 | Compress | +| `CodeControlCpyRamRom` | 0x05 | Copy RAM to ROM | +| `CodeControlInsDel` | 0x06 | Insert/Delete | + +### Event results +| Name | Value | Description | +|:-----|:----:|:------------| +| `evrNoError` | 0x00000000 | No error | +| `evrFragmentRejected` | 0x00000001 | Fragment rejected | +| `evrMalformedPDU` | 0x00000002 | Malformed PDU | +| `evrSparseBytes` | 0x00000003 | Sparse bytes | +| `evrCannotHandlePDU` | 0x00000004 | Cannot handle PDU | +| `evrNotImplemented` | 0x00000005 | Not implemented | +| `evrErrException` | 0x00000006 | Exception | +| `evrErrAreaNotFound` | 0x00000007 | Area not found | +| `evrErrOutOfRange` | 0x00000008 | Out of range | +| `evrErrOverPDU` | 0x00000009 | Over PDU | +| `evrErrTransportSize` | 0x0000000A | Transport size error | +| `evrInvalidGroupUData` | 0x0000000B | Invalid group UData | +| `evrInvalidSZL` | 0x0000000C | Invalid SZL | +| `evrDataSizeMismatch` | 0x0000000D | Data size mismatch | +| `evrCannotUpload` | 0x0000000E | Cannot upload | +| `evrCannotDownload` | 0x0000000F | Cannot download | +| `evrUploadInvalidID` | 0x00000010 | Upload invalid ID | +| `evrResNotFound` | 0x00000011 | Resource not found | + +### Error codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `errSrvCannotStart` | 0x00100000 | Cannot start server | +| `errSrvDBNullPointer` | 0x00200000 | DB null pointer | +| `errSrvAreaAlreadyExists` | 0x00300000 | Area already exists | +| `errSrvUnknownArea` | 0x00400000 | Unknown area | +| `errSrvInvalidParams` | 0x00500000 | Invalid params | +| `errSrvTooManyDB` | 0x00600000 | Too many DBs | +| `errSrvInvalidParamNumber` | 0x00700000 | Invalid param number | +| `errSrvCannotChangeParam` | 0x00800000 | Cannot change param | + +--- + +## Type definitions + +### SrvEvent +| Field | Type | Description | +|:------|:-----|:------------| +| `EvtTime` | Date | Event timestamp | +| `EvtSender` | string | Sender IP | +| `EvtCode` | number | Event code (see [Event codes](#event-codes)) | +| `EvtRetCode` | number | Return code (see [Event results](#event-results)) | +| `EvtParam1` | number | Event parameter 1 | +| `EvtParam2` | number | Event parameter 2 | +| `EvtParam3` | number | Event parameter 3 | +| `EvtParam4` | number | Event parameter 4 | + +### S7Tag +| Field | Type | Description | +|:------|:-----|:------------| +| `Area` | number | Area code (see [Areas](#areas)) | +| `DBNumber` | number | DB number (for DB areas) | +| `Start` | number | Start offset | +| `Size` | number | Number of elements | +| `WordLen` | number | Word length code | -#### S7Client.Connected() -Returns the connection status. -#### S7Client.ErrorText(errNum) -Returns a textual explanation of a given error number. - - `errNum` Error number diff --git a/doc/server.md b/doc/server.md index 133030f..06dc418 100644 --- a/doc/server.md +++ b/doc/server.md @@ -1,326 +1,431 @@ -## S7Server -- [Administrative functions](#administrative-functions) - - [Start()](#start) - - [StartTo()](#start-to) - - [Stop()](#stop) - - [GetParam()](#get-param) - - [SetParam()](#set-param) - - [SetResourceless()](#set-resourceless) -- [Memory functions](#memory-functions) - - [RegisterArea()](#register-area) - - [UnregisterArea()](#unregister-area) - - [GetArea()](#get-area) - - [SetArea()](#set-area) - - [LockArea()](#lock-area) - - [UnlockArea()](#unlock-area) -- [Event functions](#event-functions) - - [Event 'event'](#event-event) - - [Event 'readWrite'](#event-read-write) - - [GetEventMask()](#get-event-mask) - - [SetEventMask()](#set-event-mask) -- [Miscellaneous functions](#miscellaneous-functions) - - [LastError()](#last-error) - - [EventText()](#event-text) - - [ErrorText()](#error-text) - - [ServerStatus()](#server-status) - - [ClientsCount()](#clients-count) - - [GetCpuStatus()](#get-cpu-status) - - [SetCpuStatus()](#set-cpu-status) - -### API - Administrative functions - ----------- - -#### S7Server.Start([callback]) -Starts the server and binds it to the IP address specified in the previous call of `StartTo()`. If `StartTo()` was not previously called, `0.0.0.0` is assumed as IP address. - -- The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns `true` on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Server.StartTo(ip[, callback]) -Starts the server and binds it to the specified IP address and the IsoTCP port. - -- `ip` PLC/Equipment IPV4 Address ex. “192.168.1.12” -- The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns the CPU status on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Server.Stop([callback]) -Stops the server, disconnects gracefully all clients, destroys al S7 workers and unbinds the listener socket from its address. - -- The optional `callback` parameter will be executed after completion - -If `callback` is **not** set the function is **blocking** and returns the CPU status on success or `false` on error.
-If `callback` is set the function is **non-blocking** and an `error` argument is given to the callback. - -#### S7Server.GetParam(paramNumber) -Returns an internal server parameter. - - - `paramNumber` One from the parameter list [below](#table-area) - - - -| Name | Value | Description | -|:------------------------|:-----:|:------------| -| `S7Server.LocalPort` | 1 | Socket local port -| `S7Server.WorkInterval` | 6 | Socket worker interval -| `S7Server.PDURequest` | 10 | Initial PDU length request -| `S7Server.MaxClients` | 11 | Max clients allowed - -Returns the `parameter value` on success or `false` on error. - -#### S7Server.SetParam(paramNumber, value) -Sets an internal server parameter. - - - `paramNumber` One from the parameter list [above](#table-area) - - `value` New parameter value - -Returns `true` on success or `false` on error. - -#### S7Server.SetResourceless(value) -Sets the server to resourceless mode. - - - `value` new value - -Returns `true` on success or `false` on error. - -### API - Memory functions - ----------- +# S7Server API -#### S7Server.RegisterArea(areaCode[, index], buffer) -Registers a memory area in the server. That memory block will be visible by the clients. +Simulate a PLC and handle requests via Snap7. Lifecycle methods return Promises; most other calls are synchronous and throw `Snap7Error` on invalid arguments or runtime failures. - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - - `buffer` User buffer +- Promise form: resolves on success, rejects with `Snap7Error` (contains `code`, `errno`). +- Callback form: `(err, result)` when provided; function returns `undefined`. +- Sync form: returns value or `undefined`, or throws `Snap7Error`. -Returns `true` on success or `false` on error. +--- -#### S7Server.UnregisterArea(areaCode[, index]) -Unregisters a memory area in the server. - - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - -Returns `true` on success or `false` on error. - -#### S7Server.GetArea(areaCode[, index]) -Gets the content of a previously registered memory area block. - - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - -Returns a `buffer` object. - -#### S7Server.SetArea(areaCode[, index], buffer) -Sets the content of a previously registered memory area block. - - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - - `buffer` Buffer object - -#### S7Server.LockArea(areaCode[, index]) -Locks the memory area so that a server worker thread is blocked on access attempt until the lock is released with [UnlockArea()](#unlock-area). - - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - -#### S7Server.UnlockArea(areaCode[, index]) -Unlocks a previously locked memory area. - - - - `areaCode` Area identifier (see table [below](#table-area)) - - `index` DB number if `areaCode` equals `srvAreaDB`, otherwise ignored - - - -| Area | Value | Description | -|:-----------------------|:-----:|:------------| -| `S7Server.srvAreaPE` | 0 | Process inputs -| `S7Server.srvAreaPA` | 1 | Process outputs -| `S7Server.srvAreaMK` | 2 | Merkers -| `S7Server.srvAreaCT` | 3 | Counters -| `S7Server.srvAreaTM` | 4 | Timers -| `S7Server.srvAreaDB` | 5 | DB - -### API - Event functions - ----------- - -#### S7Server event: 'event' -Emitted on server events. - - - `event` Event object - - Event object: - -```javascript -{ - EvtTime; // Date - EvtSender; // Sender - EvtCode; // Event code - EvtRetCode; // Event result - EvtParam1; // Param 1 (if available) - EvtParam2; // Param 2 (if available) - EvtParam3; // Param 3 (if available) - EvtParam4; // Param 4 (if available) -} +## Table of Contents +- [Usage basics](#usage-basics) +- [Administrative functions](#administrative-functions) + - [Start](#start) + - [StartTo](#startto) + - [Stop](#stop) + - [GetParam](#getparam) + - [SetParam](#setparam) + - [SetResourceless](#setresourceless) + - [GetEventsMask](#geteventsmask) + - [SetEventsMask](#seteventsmask) +- [Memory functions](#memory-functions) + - [RegisterArea](#registerarea) + - [UnregisterArea](#unregisterarea) + - [GetArea](#getarea) + - [SetArea](#setarea) + - [LockArea](#lockarea) + - [UnlockArea](#unlockarea) +- [Events](#events) + - [event](#event) + - [readWrite](#readwrite) + - [error](#error) +- [Diagnostics & status](#diagnostics--status) + - [EventText](#eventtext) + - [ErrorText](#errortext) + - [ServerStatus](#serverstatus) + - [ClientsCount](#clientscount) + - [GetCpuStatus](#getcpustatus) + - [SetCpuStatus](#setcpustatus) +- [Constants](#constants) + - [Areas](#areas) + - [Operation types](#operation-types) + - [Server status codes](#server-status-codes) + - [CPU status codes](#cpu-status-codes) + - [Server parameters](#server-parameters) + - [Event masks](#event-masks) + - [Event codes](#event-codes) + - [Event subcodes](#event-subcodes) + - [Event control codes](#event-control-codes) + - [Event results](#event-results) + - [Error codes](#error-codes) +- [Type definitions](#type-definitions) + - [SrvEvent](#srvevent) + - [S7Tag](#s7tag) + +--- + +## Usage basics +- `Start()` binds to the last IP set by `StartTo()` or `0.0.0.0` if none was set; `StartTo(ip)` binds explicitly. +- `SetResourceless(true)` enables the `readWrite` hook; the worker waits for your callback to continue. +- `RegisterArea`/`SetArea` expose buffers as PLC areas. For DB areas, `index` is the DB number; otherwise `index` is ignored. +- Sync methods throw on error; otherwise they return `undefined` or the documented value. + +--- + +## Administrative functions + +### Start ``` -Example: - -```javascript -var s7server = new snap7.S7Server(); - -s7server.on("event", function(event) { - console.log(s7server.EventText(event)); -}); - -s7server.StartTo('127.0.0.1'); +Start(): Promise ``` +Start the server using the current bind address. +- Returns: resolves with `undefined`, rejects with `Snap7Error` -#### S7Server event: 'readWrite' -Emitted on every read/write event. Only available in resourceless mode. - - - `sender` IPv4 address of the sender - - `operation` Operation type - - `tagObj` Tag object - - `buffer` Buffer object - - `callback` Callback function - -The server worker thread is **blocked** until `callback` is called. Therefore **calling is crucial**, to prevent a deadlock in the worker thread.
-On a read event the `callback` expects a buffer as argument that is provided to the client. You can use the `buffer` argument which is an empty buffer of the correct size. - - - -| Operation type | Value | Description | -|:--------------------------|:-----:|:---------------------| -| `S7Server.operationRead` | 0x00 | Read operation -| `S7Server.operationWrite` | 0x01 | Write operation - - Tag object: - -```javascript -{ - Area; // Area code (DB, MK,…) - DBNumber; // DB number (if any or 0) - Start; // Offset start - Size; // Number of elements - WordLen; // Tag WordLength -} +### StartTo ``` +StartTo(ip: string): Promise +``` +Start the server and bind to a specific IP address. +- Parameters: + - `ip`: IPv4 address to bind +- Returns: resolves with `undefined`, rejects with `Snap7Error` -Example: - -```javascript -var s7server = new snap7.S7Server(); - -s7server.SetResourceless(true); - -s7server.on("readWrite", function(sender, operation, tagObj, buffer, callback) { - console.log((operation === s7server.operationRead ? 'Read' : 'Write') + ' event from ' + sender); - console.log('Area : ' + tagObj.Area); - console.log('DBNumber : ' + tagObj.DBNumber); - console.log('Start : ' + tagObj.Start); - console.log('Size : ' + tagObj.Size); - console.log('WordLen : ' + tagObj.WordLen); - - if (operation === s7server.operationRead) { - buffer.fill(255); - return callback(buffer); - } else { - console.log('Buffer : ' + buffer); - return callback(); - } -}); +### Stop +``` +Stop(): Promise +``` +Stop the server and disconnect clients. +- Returns: resolves with `undefined`, rejects with `Snap7Error` -s7server.StartTo('127.0.0.1'); +### GetParam +``` +GetParam(paramNumber: number): number ``` +Read a server parameter. +- Parameters: + - `paramNumber`: see [Server parameters](#server-parameters) +- Returns: parameter value -#### S7Server.GetEventMask() -Returns the server event filter mask. +### SetParam +``` +SetParam(paramNumber: number, value: number): undefined +``` +Write a server parameter. +- Parameters: + - `paramNumber`: see [Server parameters](#server-parameters) + - `value`: new value +- Returns: `undefined` +### SetResourceless +``` +SetResourceless(value: boolean): undefined +``` +Enable/disable resourceless mode (required for `readWrite` events). +- Parameters: + - `value`: `true` to enable, `false` to disable +- Returns: `undefined` -#### S7Server.SetEventMask(mask) -Sets the server event filter mask. +### GetEventsMask +``` +GetEventsMask(): number +``` +Read the current events mask. +- Returns: mask value - - `mask` Bit mask (see table [below](#table-mask)) +### SetEventsMask +``` +SetEventsMask(mask: number): undefined +``` +Set the events mask. +- Parameters: + - `mask`: bitmask (see [Event codes](#event-codes) or [Event masks](#event-masks)) +- Returns: `undefined` - +--- -| Event code | Value | -|:------------------------------------|:--------------:| -| `S7Server.evcAll` | 0xFFFFFFFF -| `S7Server.evcNone` | 0x00000000 -| `S7Server.evcServerStarted` | 0x00000001 -| `S7Server.evcServerStopped` | 0x00000002 -| `S7Server.evcListenerCannotStart` | 0x00000004 -| `S7Server.evcClientAdded` | 0x00000008 -| `S7Server.evcClientRejected` | 0x00000010 -| `S7Server.evcClientNoRoom` | 0x00000020 -| `S7Server.evcClientException` | 0x00000040 -| `S7Server.evcClientDisconnected` | 0x00000080 -| `S7Server.evcClientTerminated` | 0x00000100 -| `S7Server.evcClientsDropped` | 0x00000200 -| `S7Server.evcPDUincoming` | 0x00010000 -| `S7Server.evcDataRead` | 0x00020000 -| `S7Server.evcDataWrite` | 0x00040000 -| `S7Server.evcNegotiatePDU` | 0x00080000 -| `S7Server.evcReadSZL` | 0x00100000 -| `S7Server.evcClock` | 0x00200000 -| `S7Server.evcUpload` | 0x00400000 -| `S7Server.evcDownload` | 0x00800000 -| `S7Server.evcDirectory` | 0x01000000 -| `S7Server.evcSecurity` | 0x02000000 -| `S7Server.evcControl` | 0x04000000 +## Memory functions +### RegisterArea +``` +RegisterArea(areaCode: number, index: number, buffer: Buffer): undefined +``` +Expose a buffer as a PLC area. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas + - `buffer`: data buffer +- Returns: `undefined`; throws `Snap7Error` on failure + +### UnregisterArea +``` +UnregisterArea(areaCode: number, index: number): undefined +``` +Remove a previously registered area. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas +- Returns: `undefined`; throws `Snap7Error` on failure -### API - Miscellaneous functions +### GetArea +``` +GetArea(areaCode: number, index: number): Buffer +``` +Get the buffer for a registered area. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas +- Returns: registered area buffer ----------- +### SetArea +``` +SetArea(areaCode: number, index: number, buffer: Buffer): undefined +``` +Replace the buffer for a registered area. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas + - `buffer`: new data buffer +- Returns: `undefined`; throws `Snap7Error` on failure + +### LockArea +``` +LockArea(areaCode: number, index: number): undefined +``` +Lock a registered area; worker threads block until unlocked. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas +- Returns: `undefined`; throws `Snap7Error` on failure -#### S7Server.LastError() -Returns the last job result. +### UnlockArea +``` +UnlockArea(areaCode: number, index: number): undefined +``` +Unlock a locked area. +- Parameters: + - `areaCode`: see [Areas](#areas) + - `index`: DB number when `areaCode` is `srvAreaDB`; ignored for other areas +- Returns: `undefined`; throws `Snap7Error` on failure -#### S7Server.EventText(eventObj) -Returns a textual explanation of a given event. +--- - - `eventObj` Event object (example [here](#event-object)) +## Events -#### S7Server.ErrorText(errNum) -Returns a textual explanation of a given error number. +### event +``` +'on("event", (evt: SrvEvent) => undefined) +``` +Emitted for server lifecycle/protocol events. - - `errNum` Error number +### readWrite +``` +'on("readWrite", (sender: string, operation: number, tag: S7Tag, buffer: Buffer, callback: (buf?: Buffer) => undefined) => undefined) +``` +Emitted on every data read/write when resourceless mode is enabled. The worker waits until `callback` is invoked; for read operations supply the response buffer. -#### S7Server.ServerStatus() -Returns the server status. (see table [below](#table-server-status)) +### error +``` +'on("error", (err: Snap7Error) => undefined) +``` +Emitted when the server encounters an error. - +--- -| Status | Value | Description | -|:----------------------|:-----:|:---------------------| -| `S7Server.SrvStopped` | 0x00 | The Server is stopped -| `S7Server.SrvRunning` | 0x01 | The Server is Running -| `S7Server.SrvError` | 0x02 | Server Error +## Diagnostics & status -#### S7Server.ClientsCount() -Returns the number of clients connected to the server. +### EventText +``` +EventText(evt: SrvEvent): string +``` +Return human-readable text for an event. +- Parameters: + - `evt`: event object +- Returns: event text -#### S7Server.GetCpuStatus() -Returns the Virtual CPU status. (see table [below](#table-cpu-status)) +### ErrorText +``` +ErrorText(errNum: number): string +``` +Return human-readable text for an error code. +- Parameters: + - `errNum`: error code +- Returns: error text -#### S7Server.SetCpuStatus(cpuStatus) -Sets the Virtual CPU status. +### ServerStatus +``` +ServerStatus(): number +``` +Return the server status (see [Server status codes](#server-status-codes)). +- Returns: server status code - - `cpuStatus` Status value (see table [below](#table-cpu-status)) +### ClientsCount +``` +ClientsCount(): number +``` +Return the number of connected clients. +- Returns: client count - +### GetCpuStatus +``` +GetCpuStatus(): number +``` +Return the simulated CPU status (see [CPU status codes](#cpu-status-codes)). +- Returns: CPU status code -| Status | Value | Description | -|:------------------------------|:-----:|:-------------------------| -| `S7Server.S7CpuStatusUnknown` | 0x00 | The CPU status is unknown -| `S7Server.S7CpuStatusRun` | 0x08 | The CPU is running -| `S7Server.S7CpuStatusStop` | 0x04 | The CPU is stopped +### SetCpuStatus +``` +SetCpuStatus(status: number): boolean +``` +Set the simulated CPU status. +- Parameters: + - `status`: see [CPU status codes](#cpu-status-codes) +- Returns: `true` on success, otherwise `false` + +--- + +## Constants + +### Areas +| Name | Value | Description | +|:-----|:----:|:------------| +| `srvAreaPE` | 0 | Process inputs | +| `srvAreaPA` | 1 | Process outputs | +| `srvAreaMK` | 2 | Merkers | +| `srvAreaCT` | 3 | Counters | +| `srvAreaTM` | 4 | Timers | +| `srvAreaDB` | 5 | Data blocks | + +### Operation types +| Name | Value | Description | +|:-----|:----:|:------------| +| `operationRead` | 0x00 | Read operation | +| `operationWrite` | 0x01 | Write operation | + +### Server status codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `SrvStopped` | 0x00 | Server stopped | +| `SrvRunning` | 0x01 | Server running | +| `SrvError` | 0x02 | Server error | + +### CPU status codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `S7CpuStatusUnknown` | 0x00 | Status not known | +| `S7CpuStatusRun` | 0x08 | CPU is running | +| `S7CpuStatusStop` | 0x04 | CPU is stopped | + +### Server parameters +| Name | Value | Description | +|:-----|:----:|:------------| +| `LocalPort` | 1 | Listener port | +| `WorkInterval` | 6 | Worker interval | +| `PDURequest` | 10 | Requested PDU length | +| `MaxClients` | 11 | Maximum clients | + +### Event masks +| Name | Value | Description | +|:-----|:----:|:------------| +| `evcAll` | 0xFFFFFFFF | Enable all events | +| `evcNone` | 0x00000000 | Disable all events | + +### Event codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `evcServerStarted` | 0x00000001 | Server started | +| `evcServerStopped` | 0x00000002 | Server stopped | +| `evcListenerCannotStart` | 0x00000004 | Listener failed to start | +| `evcClientAdded` | 0x00000008 | Client added | +| `evcClientRejected` | 0x00000010 | Client rejected | +| `evcClientNoRoom` | 0x00000020 | No room for client | +| `evcClientException` | 0x00000040 | Client exception | +| `evcClientDisconnected` | 0x00000080 | Client disconnected | +| `evcClientTerminated` | 0x00000100 | Client terminated | +| `evcClientsDropped` | 0x00000200 | Clients dropped | +| `evcPDUincoming` | 0x00010000 | Incoming PDU | +| `evcDataRead` | 0x00020000 | Data read | +| `evcDataWrite` | 0x00040000 | Data write | +| `evcNegotiatePDU` | 0x00080000 | PDU negotiation | +| `evcReadSZL` | 0x00100000 | SZL read | +| `evcClock` | 0x00200000 | Clock event | +| `evcUpload` | 0x00400000 | Upload | +| `evcDownload` | 0x00800000 | Download | +| `evcDirectory` | 0x01000000 | Directory | +| `evcSecurity` | 0x02000000 | Security | +| `evcControl` | 0x04000000 | Control | + +### Event subcodes +| Name | Value | Description | +|:-----|:----:|:------------| +| `evsUnknown` | 0x00000000 | Unknown | +| `evsStartUpload` | 0x00000001 | Start upload | +| `evsStartDownload` | 0x00000002 | Start download | +| `evsGetBlockList` | 0x00000003 | Get block list | +| `evsStartListBoT` | 0x00000004 | Start list BoT | +| `evsListBoT` | 0x00000005 | List BoT | +| `evsGetBlockInfo` | 0x00000006 | Get block info | +| `evsGetClock` | 0x00000007 | Get clock | +| `evsSetClock` | 0x00000008 | Set clock | +| `evsSetPassword` | 0x00000009 | Set password | +| `evsClrPassword` | 0x0000000A | Clear password | + +### Event control codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `CodeControlUnknown` | 0x00 | Unknown control | +| `CodeControlColdStart` | 0x01 | Cold start | +| `CodeControlWarmStart` | 0x02 | Warm start | +| `CodeControlStop` | 0x03 | Stop | +| `CodeControlCompress` | 0x04 | Compress | +| `CodeControlCpyRamRom` | 0x05 | Copy RAM to ROM | +| `CodeControlInsDel` | 0x06 | Insert/Delete | + +### Event results +| Name | Value | Description | +|:-----|:----:|:------------| +| `evrNoError` | 0x00000000 | No error | +| `evrFragmentRejected` | 0x00000001 | Fragment rejected | +| `evrMalformedPDU` | 0x00000002 | Malformed PDU | +| `evrSparseBytes` | 0x00000003 | Sparse bytes | +| `evrCannotHandlePDU` | 0x00000004 | Cannot handle PDU | +| `evrNotImplemented` | 0x00000005 | Not implemented | +| `evrErrException` | 0x00000006 | Exception | +| `evrErrAreaNotFound` | 0x00000007 | Area not found | +| `evrErrOutOfRange` | 0x00000008 | Out of range | +| `evrErrOverPDU` | 0x00000009 | Over PDU | +| `evrErrTransportSize` | 0x0000000A | Transport size error | +| `evrInvalidGroupUData` | 0x0000000B | Invalid group UData | +| `evrInvalidSZL` | 0x0000000C | Invalid SZL | +| `evrDataSizeMismatch` | 0x0000000D | Data size mismatch | +| `evrCannotUpload` | 0x0000000E | Cannot upload | +| `evrCannotDownload` | 0x0000000F | Cannot download | +| `evrUploadInvalidID` | 0x00000010 | Upload invalid ID | +| `evrResNotFound` | 0x00000011 | Resource not found | + +### Error codes +| Name | Value | Description | +|:-----|:----:|:------------| +| `errSrvCannotStart` | 0x00100000 | Cannot start server | +| `errSrvDBNullPointer` | 0x00200000 | DB null pointer | +| `errSrvAreaAlreadyExists` | 0x00300000 | Area already exists | +| `errSrvUnknownArea` | 0x00400000 | Unknown area | +| `errSrvInvalidParams` | 0x00500000 | Invalid params | +| `errSrvTooManyDB` | 0x00600000 | Too many DBs | +| `errSrvInvalidParamNumber` | 0x00700000 | Invalid param number | +| `errSrvCannotChangeParam` | 0x00800000 | Cannot change param | + +--- + +## Type definitions + +### SrvEvent +| Field | Type | Description | +|:------|:-----|:------------| +| `EvtTime` | Date | Event timestamp | +| `EvtSender` | string | Sender IP | +| `EvtCode` | number | Event code (see [Event codes](#event-codes)) | +| `EvtRetCode` | number | Return code (see [Event results](#event-results)) | +| `EvtParam1` | number | Event parameter 1 | +| `EvtParam2` | number | Event parameter 2 | +| `EvtParam3` | number | Event parameter 3 | +| `EvtParam4` | number | Event parameter 4 | + +### S7Tag +| Field | Type | Description | +|:------|:-----|:------------| +| `Area` | number | Area code (see [Areas](#areas)) | +| `DBNumber` | number | DB number (for DB areas) | +| `Start` | number | Start offset | +| `Size` | number | Number of elements | +| `WordLen` | number | Word length code | diff --git a/lib/node-snap7.d.ts b/lib/node-snap7.d.ts new file mode 100644 index 0000000..ff39fc5 --- /dev/null +++ b/lib/node-snap7.d.ts @@ -0,0 +1,735 @@ +declare module 'node-snap7' { + import { EventEmitter } from 'events'; + + export interface Snap7Error extends Error { + code?: string; // Symbolic Snap7 code, e.g., "SNAP7_CLIENT_CODE_33" + errno?: number; // Numeric Snap7/ISO/TCP error code + } + + export interface S7Client { + // Connection Methods + Connect(): Promise; + Connect(callback: (err: Snap7Error | null, result: void) => void): void; + ConnectSync(): void; + + ConnectTo(ip: string, rack: number, slot: number): Promise; + ConnectTo(ip: string, rack: number, slot: number, callback: (err: Snap7Error | null, result: void) => void): void; + ConnectToSync(ip: string, rack: number, slot: number): void; + SetConnectionParams(remoteAddress: string, localTSAP: number, remoteTSAP: number): void; + SetConnectionType(type: ConnectionType): void; + Disconnect(): void; + + // Parameter Methods + GetParam(paramNumber: ClientParameter): number; + SetParam(paramNumber: ClientParameter, value: number): void; + + // Data I/O Methods + ReadArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen): Promise; + ReadArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, callback: (err: Snap7Error | null, data: Buffer) => void): void; + ReadAreaSync(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen): Buffer; + + WriteArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer): Promise; + WriteArea(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + WriteAreaSync(area: Area, dbNumber: number, start: number, amount: number, wordLen: WordLen, buffer: Buffer): void; + + ReadMultiVars(items: S7MultiVarRead[]): Promise; + ReadMultiVars(items: S7MultiVarRead[], callback: (err: Snap7Error | null, data: S7MultiVarReadResult[]) => void): void; + ReadMultiVarsSync(items: S7MultiVarRead[]): S7MultiVarReadResult[]; + + WriteMultiVars(items: S7MultiVarWrite[]): Promise; + WriteMultiVars(items: S7MultiVarWrite[], callback: (err: Snap7Error | null, result: S7MultiVarWriteResult[]) => void): void; + WriteMultiVarsSync(items: S7MultiVarWrite[]): S7MultiVarWriteResult[]; + + // Convenience Methods + DBRead(dbNumber: number, start: number, size: number): Promise; + DBRead(dbNumber: number, start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + DBReadSync(dbNumber: number, start: number, size: number): Buffer; + + DBWrite(dbNumber: number, start: number, size: number, buffer: Buffer): Promise; + DBWrite(dbNumber: number, start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + DBWriteSync(dbNumber: number, start: number, size: number, buffer: Buffer): void; + + MBRead(start: number, size: number): Promise; + MBRead(start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + MBReadSync(start: number, size: number): Buffer; + + MBWrite(start: number, size: number, buffer: Buffer): Promise; + MBWrite(start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + MBWriteSync(start: number, size: number, buffer: Buffer): void; + + EBRead(start: number, size: number): Promise; + EBRead(start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + EBReadSync(start: number, size: number): Buffer; + + EBWrite(start: number, size: number, buffer: Buffer): Promise; + EBWrite(start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + EBWriteSync(start: number, size: number, buffer: Buffer): void; + + ABRead(start: number, size: number): Promise; + ABRead(start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + ABReadSync(start: number, size: number): Buffer; + + ABWrite(start: number, size: number, buffer: Buffer): Promise; + ABWrite(start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + ABWriteSync(start: number, size: number, buffer: Buffer): void; + + TMRead(start: number, size: number): Promise; + TMRead(start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + TMReadSync(start: number, size: number): Buffer; + + TMWrite(start: number, size: number, buffer: Buffer): Promise; + TMWrite(start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + TMWriteSync(start: number, size: number, buffer: Buffer): void; + + CTRead(start: number, size: number): Promise; + CTRead(start: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + CTReadSync(start: number, size: number): Buffer; + + CTWrite(start: number, size: number, buffer: Buffer): Promise; + CTWrite(start: number, size: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + CTWriteSync(start: number, size: number, buffer: Buffer): void; + + // Block Operations + ListBlocks(): Promise; + ListBlocks(callback: (err: Snap7Error | null, data: BlocksList) => void): void; + ListBlocksSync(): BlocksList; + + ListBlocksOfType(blockType: BlockType): Promise; + ListBlocksOfType(blockType: BlockType, callback: (err: Snap7Error | null, data: number[]) => void): void; + ListBlocksOfTypeSync(blockType: BlockType): number[]; + + Upload(blockType: BlockType, blockNumber: number, size: number): Promise; + Upload(blockType: BlockType, blockNumber: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + UploadSync(blockType: BlockType, blockNumber: number, size: number): Buffer; + + FullUpload(blockType: BlockType, blockNumber: number, size: number): Promise; + FullUpload(blockType: BlockType, blockNumber: number, size: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + FullUploadSync(blockType: BlockType, blockNumber: number, size: number): Buffer; + + Download(blockNumber: number, buffer: Buffer): Promise; + Download(blockNumber: number, buffer: Buffer, callback: (err: Snap7Error | null, result: void) => void): void; + DownloadSync(blockNumber: number, buffer: Buffer): void; + + Delete(blockType: BlockType, blockNum: number): Promise; + Delete(blockType: BlockType, blockNum: number, callback: (err: Snap7Error | null, result: void) => void): void; + DeleteSync(blockType: BlockType, blockNum: number): void; + + DBFill(dbNumber: number, fillChar: number | string): Promise; + DBFill(dbNumber: number, fillChar: number | string, callback: (err: Snap7Error | null, result: void) => void): void; + DBFillSync(dbNumber: number, fillChar: number | string): void; + + DBGet(dbNumber: number): Promise; + DBGet(dbNumber: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + DBGetSync(dbNumber: number): Buffer; + + // PLC Control + PlcHotStart(): Promise; + PlcHotStart(callback: (err: Snap7Error | null, result: void) => void): void; + PlcHotStartSync(): void; + + PlcColdStart(): Promise; + PlcColdStart(callback: (err: Snap7Error | null, result: void) => void): void; + PlcColdStartSync(): void; + + PlcStop(): Promise; + PlcStop(callback: (err: Snap7Error | null, result: void) => void): void; + PlcStopSync(): void; + + // Control Functions + CopyRamToRom(timeout: number): Promise; + CopyRamToRom(timeout: number, callback: (err: Snap7Error | null, result: void) => void): void; + CopyRamToRomSync(timeout: number): void; + + Compress(timeout: number): Promise; + Compress(timeout: number, callback: (err: Snap7Error | null, result: void) => void): void; + CompressSync(timeout: number): void; + + PlcStatus(): Promise; + PlcStatus(callback: (err: Snap7Error | null, status: PlcStatus) => void): void; + PlcStatusSync(): PlcStatus; + + GetProtection(): Promise; + GetProtection(callback: (err: Snap7Error | null, data: Protection) => void): void; + GetProtectionSync(): Protection; + + SetSessionPassword(password: string): Promise; + SetSessionPassword(password: string, callback: (err: Snap7Error | null, result: void) => void): void; + SetSessionPasswordSync(password: string): void; + + ClearSessionPassword(): Promise; + ClearSessionPassword(callback: (err: Snap7Error | null, result: void) => void): void; + ClearSessionPasswordSync(): void; + + // DateTime Functions + GetPlcDateTime(): Promise; + GetPlcDateTime(callback: (err: Snap7Error | null, date: Date) => void): void; + GetPlcDateTimeSync(): Date; + + SetPlcDateTime(date: Date | DateTimeObject): Promise; + SetPlcDateTime(date: Date | DateTimeObject, callback: (err: Snap7Error | null, result: void) => void): void; + SetPlcDateTimeSync(date: Date | DateTimeObject): void; + + SetPlcSystemDateTime(): Promise; + SetPlcSystemDateTime(callback: (err: Snap7Error | null, result: void) => void): void; + SetPlcSystemDateTimeSync(): void; + + // SZL Operations + ReadSZL(id: number, index: number): Promise; + ReadSZL(id: number, index: number, callback: (err: Snap7Error | null, data: Buffer) => void): void; + ReadSZLSync(id: number, index: number): Buffer; + + ReadSZLList(): Promise; + ReadSZLList(callback: (err: Snap7Error | null, data: number[]) => void): void; + ReadSZLListSync(): number[]; + + // Information + GetCpuInfo(): Promise; + GetCpuInfo(callback: (err: Snap7Error | null, data: CpuInfo) => void): void; + GetCpuInfoSync(): CpuInfo; + + GetCpInfo(): Promise; + GetCpInfo(callback: (err: Snap7Error | null, data: CpInfo) => void): void; + GetCpInfoSync(): CpInfo; + + GetOrderCode(): Promise; + GetOrderCode(callback: (err: Snap7Error | null, data: OrderCode) => void): void; + GetOrderCodeSync(): OrderCode; + + // Block Info + GetAgBlockInfo(blockType: BlockType, blockNumber: number): Promise; + GetAgBlockInfo(blockType: BlockType, blockNumber: number, callback: (err: Snap7Error | null, data: BlockInfo) => void): void; + GetAgBlockInfoSync(blockType: BlockType, blockNumber: number): BlockInfo; + + GetPgBlockInfo(buffer: Buffer): BlockInfo; + + // Properties and Info + ExecTime(): number; + PDURequested(): number; + PDULength(): number; + Connected(): boolean; + ErrorText(code: number): string; + + // Error codes + errNegotiatingPDU: number; + errCliInvalidParams: number; + errCliJobPending: number; + errCliTooManyItems: number; + errCliInvalidWordLen: number; + errCliPartialDataWritten: number; + errCliSizeOverPDU: number; + errCliInvalidPlcAnswer: number; + errCliAddressOutOfRange: number; + errCliInvalidTransportSize: number; + errCliWriteDataSizeMismatch: number; + errCliItemNotAvailable: number; + errCliInvalidValue: number; + errCliCannotStartPLC: number; + errCliAlreadyRun: number; + errCliCannotStopPLC: number; + errCliCannotCopyRamToRom: number; + errCliCannotCompress: number; + errCliAlreadyStop: number; + errCliFunNotAvailable: number; + errCliUploadSequenceFailed: number; + errCliInvalidDataSizeRecvd: number; + errCliInvalidBlockType: number; + errCliInvalidBlockNumber: number; + errCliInvalidBlockSize: number; + errCliDownloadSequenceFailed: number; + errCliInsertRefused: number; + errCliDeleteRefused: number; + errCliNeedPassword: number; + errCliInvalidPassword: number; + errCliNoPasswordToSetOrClear: number; + errCliJobTimeout: number; + errCliPartialDataRead: number; + errCliBufferTooSmall: number; + errCliFunctionRefused: number; + errCliDestroying: number; + errCliInvalidParamNumber: number; + errCliCannotChangeParam: number; + + // Connection Types + CONNTYPE_PG: number; + CONNTYPE_OP: number; + CONNTYPE_BASIC: number; + + // CPU Status codes + S7CpuStatusUnknown: number; + S7CpuStatusRun: number; + S7CpuStatusStop: number; + + // Area IDs + S7AreaPE: number; + S7AreaPA: number; + S7AreaMK: number; + S7AreaDB: number; + S7AreaCT: number; + S7AreaTM: number; + + // Word Length + S7WLBit: number; + S7WLByte: number; + S7WLWord: number; + S7WLDWord: number; + S7WLReal: number; + S7WLCounter: number; + S7WLTimer: number; + + // Block Types + Block_OB: number; + Block_DB: number; + Block_SDB: number; + Block_FC: number; + Block_SFC: number; + Block_FB: number; + Block_SFB: number; + + // Sub Block Types + SubBlk_OB: number; + SubBlk_DB: number; + SubBlk_SDB: number; + SubBlk_FC: number; + SubBlk_SFC: number; + SubBlk_FB: number; + SubBlk_SFB: number; + + // Block Languages + BlockLangAWL: number; + BlockLangKOP: number; + BlockLangFUP: number; + BlockLangSCL: number; + BlockLangDB: number; + BlockLangGRAPH: number; + + // Parameters + LocalPort: number; + RemotePort: number; + PingTimeout: number; + SendTimeout: number; + RecvTimeout: number; + WorkInterval: number; + SrcRef: number; + DstRef: number; + SrcTSap: number; + PDURequest: number; + MaxClients: number; + BSendTimeout: number; + BRecvTimeout: number; + RecoveryTime: number; + KeepAliveTime: number; + } + + interface S7MultiVarRead { + Area: Area; + WordLen: WordLen; + DBNumber?: number | undefined; + Start: number; + Amount: number; + } + + interface S7MultiVarReadResult { + Result: number; + Data: Buffer | null; + } + + interface S7MultiVarWrite { + Area: Area; + WordLen: WordLen; + DBNumber?: number | undefined; + Start: number; + Amount: number; + Data: Buffer; + } + + interface S7MultiVarWriteResult { + Result: number; + } + + interface BlocksList { + OBCount: number; + FBCount: number; + FCCount: number; + SFBCount: number; + SFCCount: number; + DBCount: number; + SDBCount: number; + } + + interface Protection { + sch_schal: number; + sch_par: number; + sch_rel: number; + bart_sch: number; + anl_sch: number; + } + + interface DateTimeObject { + year: number; + month: number; + day: number; + hours: number; + minutes: number; + seconds: number; + } + + interface BlockInfo { + BlkType: number; + BlkNumber: number; + BlkLang: number; + BlkFlags: number; + MC7Size: number; + LoadSize: number; + LocalData: number; + SBBLength: number; + CheckSum: number; + Version: number; + CodeDate: string; + IntfDate: string; + Author: string; + Family: string; + Header: string; + } + + interface CpInfo { + MaxPduLength: number; + MaxConnections: number; + MaxMpiRate: number; + MaxBusRate: number; + } + + interface CpuInfo { + ModuleTypeName: string; + SerialNumber: string; + ASName: string; + Copyright: string; + ModuleName: string; + } + + interface OrderCode { + Code: string; + V1: number; + V2: number; + V3: number; + } + + export enum ConnectionType { + PG = 0x01, + OP = 0x02, + BASIC = 0x03 + } + + export enum PlcStatus { + Unknown = 0x00, + Run = 0x08, + Stop = 0x04 + } + + export enum Area { + PE = 0x81, + PA = 0x82, + MK = 0x83, + DB = 0x84, + CT = 0x1C, + TM = 0x1D + } + + export enum WordLen { + Bit = 0x01, + Byte = 0x02, + Word = 0x04, + DWord = 0x06, + Real = 0x08, + Counter = 0x1C, + Timer = 0x1D + } + + export enum BlockType { + OB = 0x38, + DB = 0x41, + SDB = 0x42, + FC = 0x43, + SFC = 0x44, + FB = 0x45, + SFB = 0x46 + } + + export enum SubBlockType { + OB = 0x08, + DB = 0x0A, + SDB = 0x0B, + FC = 0x0C, + SFC = 0x0D, + FB = 0x0E, + SFB = 0x0F + } + + export enum BlockLang { + AWL = 0x01, + KOP = 0x02, + FUP = 0x03, + SCL = 0x04, + DB = 0x05, + GRAPH = 0x06 + } + + export enum ClientParameter { + RemotePort = 2, + PingTimeout = 3, + SendTimeout = 4, + RecvTimeout = 5, + SrcRef = 7, + DstRef = 8, + SrcTSap = 9, + PDURequest = 10 + } + + export enum ServerParameter { + LocalPort = 1, + WorkInterval = 6, + PDURequest = 10, + MaxClients = 11 + } + + export enum ClientError { + NegotiatingPDU = 0x0001, + InvalidParams = 0x0002, + JobPending = 0x0003, + TooManyItems = 0x0004, + InvalidWordLen = 0x0005, + PartialDataWritten = 0x0006, + SizeOverPDU = 0x0007, + InvalidPlcAnswer = 0x0008, + AddressOutOfRange = 0x0009, + InvalidTransportSize = 0x000A, + WriteDataSizeMismatch = 0x000B, + ItemNotAvailable = 0x000C, + InvalidValue = 0x000D, + CannotStartPLC = 0x000E, + AlreadyRun = 0x000F, + CannotStopPLC = 0x0010, + CannotCopyRamToRom = 0x0011, + CannotCompress = 0x0012, + AlreadyStop = 0x0013, + FunNotAvailable = 0x0014, + UploadSequenceFailed = 0x0015, + InvalidDataSizeRecvd = 0x0016, + InvalidBlockType = 0x0017, + InvalidBlockNumber = 0x0018, + InvalidBlockSize = 0x0019, + DownloadSequenceFailed = 0x001A, + InsertRefused = 0x001B, + DeleteRefused = 0x001C, + NeedPassword = 0x001D, + InvalidPassword = 0x001E, + NoPasswordToSetOrClear = 0x001F, + JobTimeout = 0x0020, + PartialDataRead = 0x0021, + BufferTooSmall = 0x0022, + FunctionRefused = 0x0023, + Destroying = 0x0024, + InvalidParamNumber = 0x0025, + CannotChangeParam = 0x0026 + } + + export enum ServerError { + CannotStart = 0x00100000, + DBNullPointer = 0x00200000, + AreaAlreadyExists = 0x00300000, + UnknownArea = 0x00400000, + InvalidParams = 0x00500000, + TooManyDB = 0x00600000, + InvalidParamNumber = 0x00700000, + CannotChangeParam = 0x00800000 + } + + export class S7Client { + constructor(); + } + + export interface S7Server extends EventEmitter { + // Administrative Methods + /** Resolves on success; rejects with a Snap7Error on failure. */ + Start(): Promise; + /** Resolves on success; rejects with a Snap7Error on failure. */ + StartTo(ip: string): Promise; + /** Resolves on success; rejects with a Snap7Error on failure. */ + Stop(): Promise; + GetParam(paramNumber: ServerParameter): number; + SetParam(paramNumber: ServerParameter, value: number): void; + SetResourceless(value: boolean): void; + + // Memory Methods + RegisterArea(areaCode: number, index: number, buffer: Buffer): void; + UnregisterArea(areaCode: number, index: number): void; + GetArea(areaCode: number, index: number): Buffer; + SetArea(areaCode: number, index: number, buffer: Buffer): void; + LockArea(areaCode: number, index: number): void; + UnlockArea(areaCode: number, index: number): void; + + // Event Methods + GetEventsMask(): number; + SetEventsMask(mask: number): void; + + // Miscellaneous Methods + EventText(evt: SrvEvent): string; + ErrorText(errNum: number): string; + ServerStatus(): number; + ClientsCount(): number; + GetCpuStatus(): number; + SetCpuStatus(status: number): boolean; + + // EventEmitter overloads + on(event: 'event', listener: (evt: SrvEvent) => void): this; + on( + event: 'readWrite', + listener: ( + sender: string, + operation: number, + tag: S7Tag, + buffer: Buffer, + callback: (buf?: Buffer) => void + ) => void + ): this; + on(event: 'error', listener: (err: Snap7Error) => void): this; + + // Error Constants + errSrvCannotStart: number; + errSrvDBNullPointer: number; + errSrvAreaAlreadyExists: number; + errSrvUnknownArea: number; + errSrvInvalidParams: number; + errSrvTooManyDB: number; + errSrvInvalidParamNumber: number; + errSrvCannotChangeParam: number; + + // Constants + srvAreaPE: number; + srvAreaPA: number; + srvAreaMK: number; + srvAreaCT: number; + srvAreaTM: number; + srvAreaDB: number; + + // Event Constants + evcServerStarted: number; + evcServerStopped: number; + evcListenerCannotStart: number; + evcClientAdded: number; + evcClientRejected: number; + evcClientNoRoom: number; + evcClientException: number; + evcClientDisconnected: number; + evcClientTerminated: number; + evcClientsDropped: number; + evcPDUincoming: number; + evcDataRead: number; + evcDataWrite: number; + evcNegotiatePDU: number; + evcReadSZL: number; + evcClock: number; + evcUpload: number; + evcDownload: number; + evcDirectory: number; + evcSecurity: number; + evcControl: number; + + // Event Masks + evcAll: number; + evcNone: number; + + // Event Subcodes + evsUnknown: number; + evsStartUpload: number; + evsStartDownload: number; + evsGetBlockList: number; + evsStartListBoT: number; + evsListBoT: number; + evsGetBlockInfo: number; + evsGetClock: number; + evsSetClock: number; + evsSetPassword: number; + evsClrPassword: number; + + // Event params: function groups + grProgrammer: number; + grCyclicData: number; + grBlocksInfo: number; + grSZL: number; + grPassword: number; + grBSend: number; + grClock: number; + grSecurity: number; + + // Event params: control codes + CodeControlUnknown: number; + CodeControlColdStart: number; + CodeControlWarmStart: number; + CodeControlStop: number; + CodeControlCompress: number; + CodeControlCpyRamRom: number; + CodeControlInsDel: number; + + // Event results + evrNoError: number; + evrFragmentRejected: number; + evrMalformedPDU: number; + evrSparseBytes: number; + evrCannotHandlePDU: number; + evrNotImplemented: number; + evrErrException: number; + evrErrAreaNotFound: number; + evrErrOutOfRange: number; + evrErrOverPDU: number; + evrErrTransportSize: number; + evrInvalidGroupUData: number; + evrInvalidSZL: number; + evrDataSizeMismatch: number; + evrCannotUpload: number; + evrCannotDownload: number; + evrUploadInvalidID: number; + evrResNotFound: number; + + // Parameter constants + LocalPort: number; + WorkInterval: number; + PDURequest: number; + MaxClients: number; + + // Operation Constants + operationRead: number; + operationWrite: number; + + // Status Constants + SrvStopped: number; + SrvRunning: number; + SrvError: number; + + // Events + on(event: 'event', listener: (event: SrvEvent) => void): this; + on(event: 'readWrite', listener: (sender: string, operation: number, tag: S7Tag, buffer: Buffer, callback: Function) => void): this; + } + + interface SrvEvent { + EvtTime: Date; + EvtSender: string; + EvtCode: number; + EvtRetCode: number; + EvtParam1: number; + EvtParam2: number; + EvtParam3: number; + EvtParam4: number; + } + + interface S7Tag { + Area: number; + DBNumber: number; + Start: number; + Size: number; + WordLen: number; + } + + export class S7Server extends EventEmitter { + constructor(); + } +} diff --git a/lib/node-snap7.js b/lib/node-snap7.js index 06afae8..ead5bf4 100644 --- a/lib/node-snap7.js +++ b/lib/node-snap7.js @@ -1,59 +1,116 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ -var events = require('events'); +const EventEmitter = require('events').EventEmitter; +const inherits = require('util').inherits; +const snap7 = require('../binding'); -module.exports = snap7 = require('bindings')('node_snap7.node'); +module.exports = snap7; -snap7.S7Client.prototype.DBRead = function (dbNumber, start, size, cb) { - return this.ReadArea(this.S7AreaDB, dbNumber, start, size, this.S7WLByte, cb); -} +inherits(snap7.S7Server, EventEmitter); -snap7.S7Client.prototype.DBWrite = function (dbNumber, start, size, buf, cb) { - return this.WriteArea(this.S7AreaDB, dbNumber, start, size, this.S7WLByte, buf, cb); -} +const methodNames = [ + 'Connect', + 'ConnectTo', + 'ReadArea', + 'WriteArea', + 'ReadMultiVars', + 'WriteMultiVars', + 'ListBlocks', + 'ListBlocksOfType', + 'GetAgBlockInfo', + 'FullUpload', + 'Upload', + 'Download', + 'Delete', + 'DBGet', + 'DBFill', + 'GetPlcDateTime', + 'SetPlcDateTime', + 'SetPlcSystemDateTime', + 'ReadSZL', + 'ReadSZLList', + 'GetOrderCode', + 'GetCpuInfo', + 'GetCpInfo', + 'PlcHotStart', + 'PlcColdStart', + 'PlcStop', + 'CopyRamToRom', + 'Compress', + 'SetSessionPassword', + 'ClearSessionPassword', + 'GetProtection', + 'PlcStatus' +]; -snap7.S7Client.prototype.MBRead = function (start, size, cb) { - return this.ReadArea(this.S7AreaMK, 0, start, size, this.S7WLByte, cb); -} +function createPrototypeMethods(methodName) { + // Promise first; optionally bridge to callback + snap7.S7Client.prototype[methodName] = function (...args) { + const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; -snap7.S7Client.prototype.MBWrite = function (start, size, buf, cb) { - return this.WriteArea(this.S7AreaMK, 0, start, size, this.S7WLByte, buf, cb); -} + // Native promises are returned only when a function arg is present, so pass a noop. + const promise = this[`_${methodName}`](...args, () => { }); -snap7.S7Client.prototype.EBRead = function (start, size, cb) { - return this.ReadArea(this.S7AreaPE, 0, start, size, this.S7WLByte, cb); -} + if (cb) { + promise.then((res) => cb(null, res), (err) => cb(err)); + return; + } + return promise; + }; -snap7.S7Client.prototype.EBWrite = function (start, size, buf, cb) { - return this.WriteArea(this.S7AreaPE, 0, start, size, this.S7WLByte, buf, cb); + // Keep synchronous call-through for users who need blocking behavior. + snap7.S7Client.prototype[`${methodName}Sync`] = function (...args) { + return this[`_${methodName}`](...args); + }; } -snap7.S7Client.prototype.ABRead = function (start, size, cb) { - return this.ReadArea(this.S7AreaPA, 0, start, size, this.S7WLByte, cb); -} +// Apply to all methods +methodNames.forEach(createPrototypeMethods); -snap7.S7Client.prototype.ABWrite = function (start, size, buf, cb) { - return this.WriteArea(this.S7AreaPA, 0, start, size, this.S7WLByte, buf, cb); -} +const areaConfigs = [ + { prefix: 'DB', area: 'S7AreaDB', wordLen: 'S7WLByte', useDBNumber: true }, + { prefix: 'MB', area: 'S7AreaMK', wordLen: 'S7WLByte' }, + { prefix: 'EB', area: 'S7AreaPE', wordLen: 'S7WLByte' }, + { prefix: 'AB', area: 'S7AreaPA', wordLen: 'S7WLByte' }, + { prefix: 'TM', area: 'S7AreaTM', wordLen: 'S7WLTimer' }, + { prefix: 'CT', area: 'S7AreaCT', wordLen: 'S7WLCounter' } +]; -snap7.S7Client.prototype.TMRead = function (start, size, cb) { - return this.ReadArea(this.S7AreaTM, 0, start, size, this.S7WLTimer, cb); -} +areaConfigs.forEach(({ prefix, area, wordLen, useDBNumber }) => { + // Async Read with Promise/Callback + snap7.S7Client.prototype[`${prefix}Read`] = function (...args) { + const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; + const [start, size, dbNumber] = useDBNumber ? [args[1], args[2], args[0]] : [args[0], args[1], 0]; -snap7.S7Client.prototype.TMWrite = function (start, size, buf, cb) { - return this.WriteArea(this.S7AreaTM, 0, start, size, this.S7WLTimer, buf, cb); -} + if (callback) + return this.ReadArea(this[area], dbNumber, start, size, this[wordLen], callback); -snap7.S7Client.prototype.CTRead = function (start, size, cb) { - return this.ReadArea(this.S7AreaCT, 0, start, size, this.S7WLCounter, cb); -} + return this.ReadArea(this[area], dbNumber, start, size, this[wordLen]); + }; -snap7.S7Client.prototype.CTWrite = function (start, size, buf, cb) { - return this.WriteArea(this.S7AreaCT, 0, start, size, this.S7WLCounter, buf, cb); -} + // Sync Read + snap7.S7Client.prototype[`${prefix}ReadSync`] = function (...args) { + const [start, size, dbNumber] = useDBNumber ? [args[1], args[2], args[0]] : [args[0], args[1], 0]; + return this.ReadAreaSync(this[area], dbNumber, start, size, this[wordLen]); + }; + + // Async Write with Promise/Callback + snap7.S7Client.prototype[`${prefix}Write`] = function (...args) { + const callback = typeof args[args.length - 1] === 'function' ? args.pop() : undefined; + const [start, size, buf, dbNumber] = useDBNumber ? [args[1], args[2], args[3], args[0]] : [args[0], args[1], args[2], 0]; + + if (callback) + return this.WriteArea(this[area], dbNumber, start, size, this[wordLen], buf, callback); + + return this.WriteArea(this[area], dbNumber, start, size, this[wordLen], buf); + }; -snap7.S7Server.super_ = events.EventEmitter; -Object.setPrototypeOf(snap7.S7Server.prototype, events.EventEmitter.prototype); + // Sync Write + snap7.S7Client.prototype[`${prefix}WriteSync`] = function (...args) { + const [start, size, buf, dbNumber] = useDBNumber ? [args[1], args[2], args[3], args[0]] : [args[0], args[1], args[2], 0]; + return this.WriteAreaSync(this[area], dbNumber, start, size, this[wordLen], buf); + }; +}); diff --git a/lib/node-snap7.mjs b/lib/node-snap7.mjs new file mode 100644 index 0000000..d958e56 --- /dev/null +++ b/lib/node-snap7.mjs @@ -0,0 +1,7 @@ +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const snap7 = require('./node-snap7.js'); + +export default snap7; +export const { S7Client, S7Server } = snap7; diff --git a/package.json b/package.json index cd56d81..70504da 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,27 @@ { "name": "node-snap7", + "version": "2.0.0-beta", "main": "./lib/node-snap7.js", - "version": "1.0.9", + "types": "./lib/node-snap7.d.ts", + "exports": { + ".": { + "require": "./lib/node-snap7.js", + "import": "./lib/node-snap7.mjs", + "types": "./lib/node-snap7.d.ts" + } + }, "description": "Native node.js addon/wrapper for snap7", + "license": "MIT", + "readmeFilename": "README.md", + "gypfile": true, "homepage": "https://github.com/mathiask88/node-snap7", "repository": { "type": "git", "url": "git://github.com/mathiask88/node-snap7.git" }, + "bugs": { + "url": "https://github.com/mathiask88/node-snap7/issues" + }, "keywords": [ "snap7", "sps", @@ -18,21 +32,32 @@ "name": "Mathias Küsel" }, "engines": { - "node": "16 || 18 || 20 || 22 || 24" + "node": ">=18" }, - "license": "MIT", - "readmeFilename": "README.md", "dependencies": { - "nan": "^2.23.0", - "bindings": "^1.5.0", - "prebuild-install": "^7.1.2" + "node-gyp-build": "~4.8.4" }, "devDependencies": { - "prebuild": "^13.0.1", - "prebuild-ci": "^4.0.0" + "electron": "^30.0.0", + "node-addon-api": "~8.5.0", + "node-gyp": "^12.1.0", + "prebuildify": "^6.0.1", + "prebuildify-ci": "^1.0.5", + "prebuildify-cross": "^5.1.1" }, "scripts": { - "install": "prebuild-install || node-gyp rebuild", - "test": "prebuild-ci" + "install": "node-gyp-build", + "rebuild": "npm run install --build-from-source", + "prebuild": "prebuildify -t 18.20.8 --napi --strip", + "test": "node ./scripts/run-node-tests.js", + "test-electron": "node ./scripts/run-electron-tests.js", + "download-prebuilds": "prebuildify-ci download", + "prebuild-linux-arm": "prebuildify-cross -i linux-armv6 -i linux-armv7 -i linux-arm64-lts -t 18.20.8 --napi --strip", + "prebuild-android-arm": "prebuildify-cross -i android-armv7 -i android-arm64 -t 18.20.8 --napi --strip", + "prebuild-linux-x64": "prebuildify-cross -i almalinux-devtoolset11 -i alpine -t 18.20.8 --napi --strip", + "prebuild-darwin-x64+arm64": "prebuildify -t 18.20.8 --napi --strip --arch x64+arm64", + "prebuild-win32-x86": "prebuildify -t 18.20.8 --napi --strip", + "prebuild-win32-x64": "prebuildify -t 18.20.8 --napi --strip", + "prebuild-win32-arm64": "prebuildify -t 22.21.1 --napi --strip --arch arm64" } } diff --git a/scripts/run-electron-tests.js b/scripts/run-electron-tests.js new file mode 100644 index 0000000..af424d8 --- /dev/null +++ b/scripts/run-electron-tests.js @@ -0,0 +1,56 @@ +const { spawn, spawnSync } = require('child_process'); +const electronBinary = require('electron'); +const os = require('os'); + +function getElectronProcessVersions() { + try { + const result = spawnSync( + electronBinary, + ['-e', 'console.log(JSON.stringify(process.versions))'], + { + env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, + encoding: 'utf8' + } + ); + if (result.status === 0 && result.stdout) { + return JSON.parse(result.stdout.trim()); + } + } catch { + // ignore and fall back to empty object + } + return {}; +} + +const electronVersions = getElectronProcessVersions(); +const electronNode = electronVersions.node || 'unknown'; +const electronChrome = electronVersions.chrome || 'unknown'; +const electronV8 = electronVersions.v8 || 'unknown'; +const electronNapi = electronVersions.napi || 'unknown'; +const electronVersion = electronVersions.electron || 'unknown'; + +console.log( + `[electron-tests] electron ${electronVersion} (node ${electronNode}, chrome ${electronChrome}, v8 ${electronV8}, napi ${electronNapi}); runner node ${process.version} (v8 ${process.versions.v8}, napi ${process.versions.napi})${os.EOL}` +); + +// Use Electron's embedded Node to run the Node.js test runner on the collected files. +// Keep concurrency at 1 to avoid port collisions in integration tests. +const child = spawn( + electronBinary, + ['--test', '--test-concurrency=1'], + { + stdio: 'inherit', + env: { + ...process.env, + ELECTRON_RUN_AS_NODE: '1' + } + } +); + +child.on('exit', code => { + process.exit(code); +}); + +child.on('error', err => { + console.error('Failed to start Electron for tests:', err); + process.exit(1); +}); diff --git a/scripts/run-node-tests.js b/scripts/run-node-tests.js new file mode 100644 index 0000000..3829924 --- /dev/null +++ b/scripts/run-node-tests.js @@ -0,0 +1,24 @@ +const { spawn } = require('child_process'); +const os = require('os'); + +console.log( + `[node-tests] node ${process.version} (v8 ${process.versions.v8}, napi ${process.versions.napi})${os.EOL}` +); + +const child = spawn( + process.execPath, + ['--test', '--test-concurrency=1'], + { + stdio: 'inherit', + env: process.env + } +); + +child.on('exit', code => { + process.exit(code); +}); + +child.on('error', err => { + console.error('Failed to start Node tests:', err); + process.exit(1); +}); diff --git a/src/node_snap7.cpp b/src/node_snap7.cpp index 839a09d..d7d7c36 100644 --- a/src/node_snap7.cpp +++ b/src/node_snap7.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ @@ -8,11 +8,13 @@ namespace node_snap7 { -NAN_MODULE_INIT(InitAll) { - S7Client::Init(target); - S7Server::Init(target); +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + S7Client::Init(env, exports); + S7Server::Init(env, exports); + + return exports; } -NODE_MODULE(node_snap7, InitAll) +NODE_API_MODULE(node_snap7, InitAll) -} // namespace node_snap7 +} // namespace node_snap7 diff --git a/src/node_snap7_client.cpp b/src/node_snap7_client.cpp index e226765..20e5e08 100644 --- a/src/node_snap7_client.cpp +++ b/src/node_snap7_client.cpp @@ -1,2387 +1,1865 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ #include -#include -#include +#include namespace node_snap7 { -Nan::Persistent S7Client::constructor; - -NAN_MODULE_INIT(S7Client::Init) { - Nan::HandleScope scope; - - v8::Local tpl; - tpl = Nan::New(S7Client::New); - - v8::Local name = Nan::New("S7Client") - .ToLocalChecked(); - - tpl->SetClassName(name); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - // Setup the prototype - // Control functions - Nan::SetPrototypeMethod( - tpl - , "Connect" - , S7Client::Connect); - Nan::SetPrototypeMethod( - tpl - , "ConnectTo" - , S7Client::ConnectTo); - Nan::SetPrototypeMethod( - tpl - , "SetConnectionParams" - , S7Client::SetConnectionParams); - Nan::SetPrototypeMethod( - tpl - , "SetConnectionType" - , S7Client::SetConnectionType); - Nan::SetPrototypeMethod( - tpl - , "Disconnect" - , S7Client::Disconnect); - Nan::SetPrototypeMethod( - tpl - , "GetParam" - , S7Client::GetParam); - Nan::SetPrototypeMethod( - tpl - , "SetParam" - , S7Client::SetParam); - - // Data I/O Main functions - Nan::SetPrototypeMethod( - tpl - , "ReadArea" - , S7Client::ReadArea); - Nan::SetPrototypeMethod( - tpl - , "WriteArea" - , S7Client::WriteArea); - Nan::SetPrototypeMethod( - tpl - , "ReadMultiVars" - , S7Client::ReadMultiVars); - Nan::SetPrototypeMethod( - tpl - , "WriteMultiVars" - , S7Client::WriteMultiVars); - - // Directory functions - Nan::SetPrototypeMethod( - tpl - , "ListBlocks" - , S7Client::ListBlocks); - Nan::SetPrototypeMethod( - tpl - , "GetAgBlockInfo" - , S7Client::GetAgBlockInfo); - Nan::SetPrototypeMethod( - tpl - , "GetPgBlockInfo" - , S7Client::GetPgBlockInfo); - Nan::SetPrototypeMethod( - tpl - , "ListBlocksOfType" - , S7Client::ListBlocksOfType); - - // Blocks functions - Nan::SetPrototypeMethod( - tpl - , "Upload" - , S7Client::Upload); - Nan::SetPrototypeMethod( - tpl - , "FullUpload" - , S7Client::FullUpload); - Nan::SetPrototypeMethod( - tpl - , "Download" - , S7Client::Download); - Nan::SetPrototypeMethod( - tpl - , "Delete" - , S7Client::Delete); - Nan::SetPrototypeMethod( - tpl - , "DBGet" - , S7Client::DBGet); - Nan::SetPrototypeMethod( - tpl - , "DBFill" - , S7Client::DBFill); - - // Date/Time functions - Nan::SetPrototypeMethod( - tpl - , "GetPlcDateTime" - , S7Client::GetPlcDateTime); - Nan::SetPrototypeMethod( - tpl - , "SetPlcDateTime" - , S7Client::SetPlcDateTime); - Nan::SetPrototypeMethod( - tpl - , "SetPlcSystemDateTime" - , S7Client::SetPlcSystemDateTime); - - // System Info functions - Nan::SetPrototypeMethod( - tpl - , "GetOrderCode" - , S7Client::GetOrderCode); - Nan::SetPrototypeMethod( - tpl - , "GetCpuInfo" - , S7Client::GetCpuInfo); - Nan::SetPrototypeMethod( - tpl - , "GetCpInfo" - , S7Client::GetCpInfo); - Nan::SetPrototypeMethod( - tpl - , "ReadSZL" - , S7Client::ReadSZL); - Nan::SetPrototypeMethod( - tpl - , "ReadSZLList" - , S7Client::ReadSZLList); - - // Control functions - Nan::SetPrototypeMethod( - tpl - , "PlcHotStart" - , S7Client::PlcHotStart); - Nan::SetPrototypeMethod( - tpl - , "PlcColdStart" - , S7Client::PlcColdStart); - Nan::SetPrototypeMethod( - tpl - , "PlcStop" - , S7Client::PlcStop); - Nan::SetPrototypeMethod( - tpl - , "CopyRamToRom" - , S7Client::CopyRamToRom); - Nan::SetPrototypeMethod( - tpl - , "Compress" - , S7Client::Compress); - - // Security functions - Nan::SetPrototypeMethod( - tpl - , "GetProtection" - , S7Client::GetProtection); - Nan::SetPrototypeMethod( - tpl - , "SetSessionPassword" - , S7Client::SetSessionPassword); - Nan::SetPrototypeMethod( - tpl - , "ClearSessionPassword" - , S7Client::ClearSessionPassword); - - // Properties - Nan::SetPrototypeMethod( - tpl - , "ExecTime" - , S7Client::ExecTime); - Nan::SetPrototypeMethod( - tpl - , "LastError" - , S7Client::LastError); - Nan::SetPrototypeMethod( - tpl - , "PDURequested" - , S7Client::PDURequested); - Nan::SetPrototypeMethod( - tpl - , "PDULength" - , S7Client::PDULength); - Nan::SetPrototypeMethod( - tpl - , "PlcStatus" - , S7Client::PlcStatus); - Nan::SetPrototypeMethod( - tpl - , "Connected" - , S7Client::Connected); - - // Error to text function - Nan::SetPrototypeMethod( - tpl - , "ErrorText" - , S7Client::ErrorText); - - // Error codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errNegotiatingPDU").ToLocalChecked() - , Nan::New(errNegotiatingPDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidParams").ToLocalChecked() - , Nan::New(errCliInvalidParams) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliJobPending").ToLocalChecked() - , Nan::New(errCliJobPending) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliTooManyItems").ToLocalChecked() - , Nan::New(errCliTooManyItems) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidWordLen").ToLocalChecked() - , Nan::New(errCliInvalidWordLen) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliPartialDataWritten").ToLocalChecked() - , Nan::New(errCliPartialDataWritten) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliSizeOverPDU").ToLocalChecked() - , Nan::New(errCliSizeOverPDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidPlcAnswer").ToLocalChecked() - , Nan::New(errCliInvalidPlcAnswer) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliAddressOutOfRange").ToLocalChecked() - , Nan::New(errCliAddressOutOfRange) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidTransportSize").ToLocalChecked() - , Nan::New(errCliInvalidTransportSize) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliWriteDataSizeMismatch").ToLocalChecked() - , Nan::New(errCliWriteDataSizeMismatch) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliItemNotAvailable").ToLocalChecked() - , Nan::New(errCliItemNotAvailable) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidValue").ToLocalChecked() - , Nan::New(errCliInvalidValue) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliCannotStartPLC").ToLocalChecked() - , Nan::New(errCliCannotStartPLC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliAlreadyRun").ToLocalChecked() - , Nan::New(errCliAlreadyRun) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliCannotStopPLC").ToLocalChecked() - , Nan::New(errCliCannotStopPLC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliCannotCopyRamToRom").ToLocalChecked() - , Nan::New(errCliCannotCopyRamToRom) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliCannotCompress").ToLocalChecked() - , Nan::New(errCliCannotCompress) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliAlreadyStop").ToLocalChecked() - , Nan::New(errCliAlreadyStop) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliFunNotAvailable").ToLocalChecked() - , Nan::New(errCliFunNotAvailable) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliUploadSequenceFailed").ToLocalChecked() - , Nan::New(errCliUploadSequenceFailed) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidDataSizeRecvd").ToLocalChecked() - , Nan::New(errCliInvalidDataSizeRecvd) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidBlockType").ToLocalChecked() - , Nan::New(errCliInvalidBlockType) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidBlockNumber").ToLocalChecked() - , Nan::New(errCliInvalidBlockNumber) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidBlockSize").ToLocalChecked() - , Nan::New(errCliInvalidBlockSize) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliDownloadSequenceFailed").ToLocalChecked() - , Nan::New(errCliDownloadSequenceFailed) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInsertRefused").ToLocalChecked() - , Nan::New(errCliInsertRefused) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliDeleteRefused").ToLocalChecked() - , Nan::New(errCliDeleteRefused) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliNeedPassword").ToLocalChecked() - , Nan::New(errCliNeedPassword) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidPassword").ToLocalChecked() - , Nan::New(errCliInvalidPassword) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliNoPasswordToSetOrClear").ToLocalChecked() - , Nan::New(errCliNoPasswordToSetOrClear) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliJobTimeout").ToLocalChecked() - , Nan::New(errCliJobTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliPartialDataRead").ToLocalChecked() - , Nan::New(errCliPartialDataRead) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliBufferTooSmall").ToLocalChecked() - , Nan::New(errCliBufferTooSmall) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliFunctionRefused").ToLocalChecked() - , Nan::New(errCliFunctionRefused) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliDestroying").ToLocalChecked() - , Nan::New(errCliDestroying) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliInvalidParamNumber").ToLocalChecked() - , Nan::New(errCliInvalidParamNumber) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errCliCannotChangeParam").ToLocalChecked() - , Nan::New(errCliCannotChangeParam) - , v8::ReadOnly); - - // Client Connection Type - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CONNTYPE_PG").ToLocalChecked() - , Nan::New(CONNTYPE_PG) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CONNTYPE_OP").ToLocalChecked() - , Nan::New(CONNTYPE_OP) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CONNTYPE_BASIC").ToLocalChecked() - , Nan::New(CONNTYPE_BASIC) - , v8::ReadOnly); - - // CPU Status codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusUnknown").ToLocalChecked() - , Nan::New(S7CpuStatusUnknown) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusRun").ToLocalChecked() - , Nan::New(S7CpuStatusRun) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusStop").ToLocalChecked() - , Nan::New(S7CpuStatusStop) - , v8::ReadOnly); - - // Area ID - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaPE").ToLocalChecked() - , Nan::New(S7AreaPE) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaPA").ToLocalChecked() - , Nan::New(S7AreaPA) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaMK").ToLocalChecked() - , Nan::New(S7AreaMK) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaDB").ToLocalChecked() - , Nan::New(S7AreaDB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaCT").ToLocalChecked() - , Nan::New(S7AreaCT) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7AreaTM").ToLocalChecked() - , Nan::New(S7AreaTM) - , v8::ReadOnly); - - // Word Length - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLBit").ToLocalChecked() - , Nan::New(S7WLBit) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLByte").ToLocalChecked() - , Nan::New(S7WLByte) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLWord").ToLocalChecked() - , Nan::New(S7WLWord) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLDWord").ToLocalChecked() - , Nan::New(S7WLDWord) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLReal").ToLocalChecked() - , Nan::New(S7WLReal) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLCounter").ToLocalChecked() - , Nan::New(S7WLCounter) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7WLTimer").ToLocalChecked() - , Nan::New(S7WLTimer) - , v8::ReadOnly); - - // Block type - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_OB").ToLocalChecked() - , Nan::New(Block_OB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_DB").ToLocalChecked() - , Nan::New(Block_DB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_SDB").ToLocalChecked() - , Nan::New(Block_SDB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_FC").ToLocalChecked() - , Nan::New(Block_FC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_SFC").ToLocalChecked() - , Nan::New(Block_SFC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_FB").ToLocalChecked() - , Nan::New(Block_FB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("Block_SFB").ToLocalChecked() - , Nan::New(Block_SFB) - , v8::ReadOnly); - - // Sub Block Type - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_OB").ToLocalChecked() - , Nan::New(SubBlk_OB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_SDB").ToLocalChecked() - , Nan::New(SubBlk_SDB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_FC").ToLocalChecked() - , Nan::New(SubBlk_FC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_SFC").ToLocalChecked() - , Nan::New(SubBlk_SFC) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_FB").ToLocalChecked() - , Nan::New(SubBlk_FB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SubBlk_SFB").ToLocalChecked() - , Nan::New(SubBlk_SFB) - , v8::ReadOnly); - - // Block languages - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangAWL").ToLocalChecked() - , Nan::New(BlockLangAWL) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangKOP").ToLocalChecked() - , Nan::New(BlockLangKOP) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangFUP").ToLocalChecked() - , Nan::New(BlockLangFUP) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangSCL").ToLocalChecked() - , Nan::New(BlockLangSCL) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangDB").ToLocalChecked() - , Nan::New(BlockLangDB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BlockLangGRAPH").ToLocalChecked() - , Nan::New(BlockLangGRAPH) - , v8::ReadOnly); - - // Parameter - Nan::SetPrototypeTemplate( - tpl - , Nan::New("LocalPort").ToLocalChecked() - , Nan::New(p_u16_LocalPort) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("RemotePort").ToLocalChecked() - , Nan::New(p_u16_RemotePort) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("PingTimeout").ToLocalChecked() - , Nan::New(p_i32_PingTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SendTimeout").ToLocalChecked() - , Nan::New(p_i32_SendTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("RecvTimeout").ToLocalChecked() - , Nan::New(p_i32_RecvTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("WorkInterval").ToLocalChecked() - , Nan::New(p_i32_WorkInterval) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SrcRef").ToLocalChecked() - , Nan::New(p_u16_SrcRef) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("DstRef").ToLocalChecked() - , Nan::New(p_u16_DstRef) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SrcTSap").ToLocalChecked() - , Nan::New(p_u16_SrcTSap) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("PDURequest").ToLocalChecked() - , Nan::New(p_i32_PDURequest) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("MaxClients").ToLocalChecked() - , Nan::New(p_i32_MaxClients) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BSendTimeout").ToLocalChecked() - , Nan::New(p_i32_BSendTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("BRecvTimeout").ToLocalChecked() - , Nan::New(p_i32_BRecvTimeout) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("RecoveryTime").ToLocalChecked() - , Nan::New(p_u32_RecoveryTime) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("KeepAliveTime").ToLocalChecked() - , Nan::New(p_u32_KeepAliveTime) - , v8::ReadOnly); - - constructor.Reset(tpl); - Nan::Set(target, name, Nan::GetFunction(tpl).ToLocalChecked()); +Napi::Object S7Client::Init(Napi::Env env, Napi::Object exports) { + + Napi::Function func = DefineClass( + env, + "S7Client", + {// Control functions + InstanceMethod("_Connect", &S7Client::Connect), + InstanceMethod("_ConnectTo", &S7Client::ConnectTo), + InstanceMethod("SetConnectionParams", &S7Client::SetConnectionParams), + InstanceMethod("SetConnectionType", &S7Client::SetConnectionType), + InstanceMethod("Disconnect", &S7Client::Disconnect), + InstanceMethod("GetParam", &S7Client::GetParam), + InstanceMethod("SetParam", &S7Client::SetParam), + + // Data I/O Main functions + InstanceMethod("_ReadArea", &S7Client::ReadArea), + InstanceMethod("_WriteArea", &S7Client::WriteArea), + InstanceMethod("_ReadMultiVars", &S7Client::ReadMultiVars), + InstanceMethod("_WriteMultiVars", &S7Client::WriteMultiVars), + + // Directory functions + InstanceMethod("_ListBlocks", &S7Client::ListBlocks), + InstanceMethod("_GetAgBlockInfo", &S7Client::GetAgBlockInfo), + InstanceMethod("GetPgBlockInfo", &S7Client::GetPgBlockInfo), + InstanceMethod("_ListBlocksOfType", &S7Client::ListBlocksOfType), + + // Blocks functions + InstanceMethod("_Upload", &S7Client::Upload), + InstanceMethod("_FullUpload", &S7Client::FullUpload), + InstanceMethod("_Download", &S7Client::Download), + InstanceMethod("_Delete", &S7Client::Delete), + InstanceMethod("_DBGet", &S7Client::DBGet), + InstanceMethod("_DBFill", &S7Client::DBFill), + + // Date/Time functions + InstanceMethod("_GetPlcDateTime", &S7Client::GetPlcDateTime), + InstanceMethod("_SetPlcDateTime", &S7Client::SetPlcDateTime), + InstanceMethod("_SetPlcSystemDateTime", &S7Client::SetPlcSystemDateTime), + + // System Info functions + InstanceMethod("_GetOrderCode", &S7Client::GetOrderCode), + InstanceMethod("_GetCpuInfo", &S7Client::GetCpuInfo), + InstanceMethod("_GetCpInfo", &S7Client::GetCpInfo), + InstanceMethod("_ReadSZL", &S7Client::ReadSZL), + InstanceMethod("_ReadSZLList", &S7Client::ReadSZLList), + + // Control functions + InstanceMethod("_PlcHotStart", &S7Client::PlcHotStart), + InstanceMethod("_PlcColdStart", &S7Client::PlcColdStart), + InstanceMethod("_PlcStop", &S7Client::PlcStop), + InstanceMethod("_CopyRamToRom", &S7Client::CopyRamToRom), + InstanceMethod("_Compress", &S7Client::Compress), + + // Security functions + InstanceMethod("_GetProtection", &S7Client::GetProtection), + InstanceMethod("_SetSessionPassword", &S7Client::SetSessionPassword), + InstanceMethod("_ClearSessionPassword", &S7Client::ClearSessionPassword), + + // Properties + InstanceMethod("ExecTime", &S7Client::ExecTime), + InstanceMethod("PDURequested", &S7Client::PDURequested), + InstanceMethod("PDULength", &S7Client::PDULength), + InstanceMethod("_PlcStatus", &S7Client::PlcStatus), + InstanceMethod("Connected", &S7Client::Connected), + + // Error to text function + InstanceMethod("ErrorText", &S7Client::ErrorText), + + // Error codes + InstanceValue("errNegotiatingPDU", Napi::Value::From(env, errNegotiatingPDU)), + InstanceValue("errCliInvalidParams", Napi::Value::From(env, errCliInvalidParams)), + InstanceValue("errCliJobPending", Napi::Value::From(env, errCliJobPending)), + InstanceValue("errCliTooManyItems", Napi::Value::From(env, errCliTooManyItems)), + InstanceValue("errCliInvalidWordLen", Napi::Value::From(env, errCliInvalidWordLen)), + InstanceValue("errCliPartialDataWritten", + Napi::Value::From(env, errCliPartialDataWritten)), + InstanceValue("errCliSizeOverPDU", Napi::Value::From(env, errCliSizeOverPDU)), + InstanceValue("errCliInvalidPlcAnswer", Napi::Value::From(env, errCliInvalidPlcAnswer)), + InstanceValue("errCliAddressOutOfRange", Napi::Value::From(env, errCliAddressOutOfRange)), + InstanceValue("errCliInvalidTransportSize", + Napi::Value::From(env, errCliInvalidTransportSize)), + InstanceValue("errCliWriteDataSizeMismatch", + Napi::Value::From(env, errCliWriteDataSizeMismatch)), + InstanceValue("errCliItemNotAvailable", Napi::Value::From(env, errCliItemNotAvailable)), + InstanceValue("errCliInvalidValue", Napi::Value::From(env, errCliInvalidValue)), + InstanceValue("errCliCannotStartPLC", Napi::Value::From(env, errCliCannotStartPLC)), + InstanceValue("errCliAlreadyRun", Napi::Value::From(env, errCliAlreadyRun)), + InstanceValue("errCliCannotStopPLC", Napi::Value::From(env, errCliCannotStopPLC)), + InstanceValue("errCliCannotCopyRamToRom", + Napi::Value::From(env, errCliCannotCopyRamToRom)), + InstanceValue("errCliCannotCompress", Napi::Value::From(env, errCliCannotCompress)), + InstanceValue("errCliAlreadyStop", Napi::Value::From(env, errCliAlreadyStop)), + InstanceValue("errCliFunNotAvailable", Napi::Value::From(env, errCliFunNotAvailable)), + InstanceValue("errCliUploadSequenceFailed", + Napi::Value::From(env, errCliUploadSequenceFailed)), + InstanceValue("errCliInvalidDataSizeRecvd", + Napi::Value::From(env, errCliInvalidDataSizeRecvd)), + InstanceValue("errCliInvalidBlockType", Napi::Value::From(env, errCliInvalidBlockType)), + InstanceValue("errCliInvalidBlockNumber", + Napi::Value::From(env, errCliInvalidBlockNumber)), + InstanceValue("errCliInvalidBlockSize", Napi::Value::From(env, errCliInvalidBlockSize)), + InstanceValue("errCliDownloadSequenceFailed", + Napi::Value::From(env, errCliDownloadSequenceFailed)), + InstanceValue("errCliInsertRefused", Napi::Value::From(env, errCliInsertRefused)), + InstanceValue("errCliDeleteRefused", Napi::Value::From(env, errCliDeleteRefused)), + InstanceValue("errCliNeedPassword", Napi::Value::From(env, errCliNeedPassword)), + InstanceValue("errCliInvalidPassword", Napi::Value::From(env, errCliInvalidPassword)), + InstanceValue("errCliNoPasswordToSetOrClear", + Napi::Value::From(env, errCliNoPasswordToSetOrClear)), + InstanceValue("errCliJobTimeout", Napi::Value::From(env, errCliJobTimeout)), + InstanceValue("errCliPartialDataRead", Napi::Value::From(env, errCliPartialDataRead)), + InstanceValue("errCliBufferTooSmall", Napi::Value::From(env, errCliBufferTooSmall)), + InstanceValue("errCliFunctionRefused", Napi::Value::From(env, errCliFunctionRefused)), + InstanceValue("errCliDestroying", Napi::Value::From(env, errCliDestroying)), + InstanceValue("errCliInvalidParamNumber", + Napi::Value::From(env, errCliInvalidParamNumber)), + InstanceValue("errCliCannotChangeParam", Napi::Value::From(env, errCliCannotChangeParam)), + + // Client Connection Type + InstanceValue("CONNTYPE_PG", Napi::Value::From(env, CONNTYPE_PG)), + InstanceValue("CONNTYPE_OP", Napi::Value::From(env, CONNTYPE_OP)), + InstanceValue("CONNTYPE_BASIC", Napi::Value::From(env, CONNTYPE_BASIC)), + + // CPU Status codes + InstanceValue("S7CpuStatusUnknown", Napi::Value::From(env, S7CpuStatusUnknown)), + InstanceValue("S7CpuStatusRun", Napi::Value::From(env, S7CpuStatusRun)), + InstanceValue("S7CpuStatusStop", Napi::Value::From(env, S7CpuStatusStop)), + + // Area ID + InstanceValue("S7AreaPE", Napi::Value::From(env, S7AreaPE)), + InstanceValue("S7AreaPA", Napi::Value::From(env, S7AreaPA)), + InstanceValue("S7AreaMK", Napi::Value::From(env, S7AreaMK)), + InstanceValue("S7AreaDB", Napi::Value::From(env, S7AreaDB)), + InstanceValue("S7AreaCT", Napi::Value::From(env, S7AreaCT)), + InstanceValue("S7AreaTM", Napi::Value::From(env, S7AreaTM)), + + // Word Length + InstanceValue("S7WLBit", Napi::Value::From(env, S7WLBit)), + InstanceValue("S7WLByte", Napi::Value::From(env, S7WLByte)), + InstanceValue("S7WLWord", Napi::Value::From(env, S7WLWord)), + InstanceValue("S7WLDWord", Napi::Value::From(env, S7WLDWord)), + InstanceValue("S7WLReal", Napi::Value::From(env, S7WLReal)), + InstanceValue("S7WLCounter", Napi::Value::From(env, S7WLCounter)), + InstanceValue("S7WLTimer", Napi::Value::From(env, S7WLTimer)), + + // Block type + InstanceValue("Block_OB", Napi::Value::From(env, Block_OB)), + InstanceValue("Block_DB", Napi::Value::From(env, Block_DB)), + InstanceValue("Block_SDB", Napi::Value::From(env, Block_SDB)), + InstanceValue("Block_FC", Napi::Value::From(env, Block_FC)), + InstanceValue("Block_SFC", Napi::Value::From(env, Block_SFC)), + InstanceValue("Block_FB", Napi::Value::From(env, Block_FB)), + InstanceValue("Block_SFB", Napi::Value::From(env, Block_SFB)), + + // Sub Block Type + InstanceValue("SubBlk_OB", Napi::Value::From(env, SubBlk_OB)), + InstanceValue("SubBlk_SDB", Napi::Value::From(env, SubBlk_SDB)), + InstanceValue("SubBlk_FC", Napi::Value::From(env, SubBlk_FC)), + InstanceValue("SubBlk_SFC", Napi::Value::From(env, SubBlk_SFC)), + InstanceValue("SubBlk_FB", Napi::Value::From(env, SubBlk_FB)), + InstanceValue("SubBlk_SFB", Napi::Value::From(env, SubBlk_SFB)), + + // Block languages + InstanceValue("BlockLangAWL", Napi::Value::From(env, BlockLangAWL)), + InstanceValue("BlockLangKOP", Napi::Value::From(env, BlockLangKOP)), + InstanceValue("BlockLangFUP", Napi::Value::From(env, BlockLangFUP)), + InstanceValue("BlockLangSCL", Napi::Value::From(env, BlockLangSCL)), + InstanceValue("BlockLangDB", Napi::Value::From(env, BlockLangDB)), + InstanceValue("BlockLangGRAPH", Napi::Value::From(env, BlockLangGRAPH)), + + // Parameter + InstanceValue("RemotePort", Napi::Value::From(env, p_u16_RemotePort)), + InstanceValue("PingTimeout", Napi::Value::From(env, p_i32_PingTimeout)), + InstanceValue("SendTimeout", Napi::Value::From(env, p_i32_SendTimeout)), + InstanceValue("RecvTimeout", Napi::Value::From(env, p_i32_RecvTimeout)), + InstanceValue("SrcRef", Napi::Value::From(env, p_u16_SrcRef)), + InstanceValue("DstRef", Napi::Value::From(env, p_u16_DstRef)), + InstanceValue("SrcTSap", Napi::Value::From(env, p_u16_SrcTSap)), + InstanceValue("PDURequest", Napi::Value::From(env, p_i32_PDURequest))}); + + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); + + exports.Set("S7Client", func); + return exports; } -NAN_METHOD(S7Client::New) { - if (info.IsConstructCall()) { - S7Client *s7client = new S7Client(); - - s7client->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - v8::Local constructorHandle; - constructorHandle = Nan::New(constructor); - info.GetReturnValue().Set( - Nan::NewInstance(Nan::GetFunction(constructorHandle).ToLocalChecked()).ToLocalChecked()); - } -} - -S7Client::S7Client() { - snap7Client = new TS7Client(); - uv_mutex_init(&mutex); +S7Client::S7Client(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + snap7Client = new TS7Client(); } S7Client::~S7Client() { - snap7Client->Disconnect(); - delete snap7Client; - constructor.Reset(); - uv_mutex_destroy(&mutex); + snap7Client->Disconnect(); + delete snap7Client; } int S7Client::GetByteCountFromWordLen(int WordLen) { - switch (WordLen) { - case S7WLBit: - case S7WLByte: - return 1; - case S7WLWord: - case S7WLCounter: - case S7WLTimer: - return 2; - case S7WLReal: - case S7WLDWord: - return 4; - default: - return 0; - } + switch (WordLen) { + case S7WLBit: + case S7WLByte: + return 1; + case S7WLWord: + case S7WLCounter: + case S7WLTimer: + return 2; + case S7WLReal: + case S7WLDWord: + return 4; + default: + return 0; + } } -void S7Client::FreeCallback(char *data, void *hint) { - delete[] data; +Napi::Error S7Client::MakeError(Napi::Env env, const std::string& context, int code) { + std::string text = CliErrorText(code); + std::ostringstream msg; + msg << context << " (" << code << ")"; + if (!text.empty()) { + msg << ": " << text; + } + Napi::Error err = Napi::Error::New(env, msg.str()); + err.Set("name", Napi::String::New(env, "Snap7Error")); + err.Set("code", Napi::String::New(env, "SNAP7_CLIENT_CODE_" + std::to_string(code))); + err.Set("errno", Napi::Number::New(env, code)); + return err; } -void S7Client::FreeCallbackSZL(char *data, void *hint) { - delete reinterpret_cast(data); +void S7Client::FreeCallback(Napi::Env, char* finalizeData) { + delete[] finalizeData; } -// Control functions -NAN_METHOD(S7Client::Connect) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - int ret = s7client->snap7Client->Connect(); - info.GetReturnValue().Set(Nan::New(ret == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, CONNECT)); - info.GetReturnValue().SetUndefined(); - } +void S7Client::FreeCallbackSZL(Napi::Env, char* finalizeData) { + delete reinterpret_cast(finalizeData); } -NAN_METHOD(S7Client::ConnectTo) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +// Control functions +Napi::Value S7Client::Connect(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (info.Length() < 3) { - return Nan::ThrowTypeError("Wrong number of arguments"); - } + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::CONNECT); + worker->Queue(); + return worker->GetPromise(); + } - if (!info[0]->IsString() || !info[1]->IsInt32() || !info[2]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + int ret = snap7Client->Connect(); + if (ret == 0) { + return env.Undefined(); + } - Nan::Utf8String *remAddress = new Nan::Utf8String(info[0]); - if (!info[3]->IsFunction()) { - int ret = s7client->snap7Client->ConnectTo( - **remAddress - , Nan::To(info[1]).FromJust() - , Nan::To(info[2]).FromJust()); - delete remAddress; - info.GetReturnValue().Set(Nan::New(ret == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[3].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, CONNECTTO - , remAddress, Nan::To(info[1]).FromJust(), Nan::To(info[2]).FromJust())); - info.GetReturnValue().SetUndefined(); - } + MakeError(env, "Connect failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::SetConnectionParams) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::ConnectTo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsString() || !info[1]->IsUint32() || - !info[2]->IsUint32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsString); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); - Nan::Utf8String remAddress(info[0]); - word LocalTSAP = Nan::To(info[1]).FromJust(); - word RemoteTSAP = Nan::To(info[2]).FromJust(); + std::string* remAddress = new std::string(info[0].As().Utf8Value()); + int rack = info[1].As().Int32Value(); + int slot = info[2].As().Int32Value(); - int ret = s7client->snap7Client->SetConnectionParams( - *remAddress - , LocalTSAP - , RemoteTSAP); - info.GetReturnValue().Set(Nan::New(ret == 0)); + if (info.Length() > 3 && info[3].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::CONNECTTO, remAddress, rack, slot); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->ConnectTo(remAddress->c_str(), rack, slot); + delete remAddress; + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "ConnectTo failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::SetConnectionType) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::SetConnectionParams(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsString); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); - word type = Nan::To(info[0]).FromJust(); + std::string remAddress = info[0].As().Utf8Value(); + word LocalTSAP = info[1].As().Uint32Value(); + word RemoteTSAP = info[2].As().Uint32Value(); + int ret = snap7Client->SetConnectionParams(remAddress.c_str(), LocalTSAP, RemoteTSAP); + if (ret == 0) { + return env.Undefined(); + } else { + MakeError(env, "SetConnectionParams failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } +} - int ret = s7client->snap7Client->SetConnectionType(type); - info.GetReturnValue().Set(Nan::New(ret == 0)); +Napi::Value S7Client::SetConnectionType(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); + + word type = info[0].As().Uint32Value(); + int ret = snap7Client->SetConnectionType(type); + if (ret == 0) { + return env.Undefined(); + } else { + MakeError(env, "SetConnectionType failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -NAN_METHOD(S7Client::Disconnect) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::Disconnect(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - int ret = s7client->snap7Client->Disconnect(); - info.GetReturnValue().Set(Nan::New(ret == 0)); + int ret = snap7Client->Disconnect(); + if (ret == 0) { + return env.Undefined(); + } else { + MakeError(env, "Disconnect failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -NAN_METHOD(S7Client::GetParam) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::GetParam(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int pData; - int returnValue = s7client->snap7Client->GetParam(Nan::To(info[0]).FromJust() - , &pData); + int pData; + int ret = snap7Client->GetParam(info[0].As().Int32Value(), &pData); - if (returnValue == 0) { - info.GetReturnValue().Set(Nan::New(pData)); - } else { - info.GetReturnValue().Set(Nan::New(returnValue)); - } + if (ret == 0) { + return Napi::Number::New(env, pData); + } else { + MakeError(env, "GetParam failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -NAN_METHOD(S7Client::SetParam) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::SetParam(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!(info[0]->IsInt32() || info[1]->IsInt32())) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); - int pData = Nan::To(info[1]).FromJust(); - int ret = s7client->snap7Client->SetParam(Nan::To(info[0]).FromJust(), &pData); - info.GetReturnValue().Set(Nan::New(ret == 0)); + int pData = info[1].As().Int32Value(); + int ret = snap7Client->SetParam(info[0].As().Int32Value(), &pData); + if (ret == 0) { + return env.Undefined(); + } else { + MakeError(env, "SetParam failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } // Data I/O Main functions -void IOWorker::Execute() { - uv_mutex_lock(&s7client->mutex); - - switch (caller) { - case CONNECTTO: - returnValue = s7client->snap7Client->ConnectTo( - **static_cast(pData), int1, int2); - break; - - case CONNECT: - returnValue = s7client->snap7Client->Connect(); - break; - - case READAREA: - returnValue = s7client->snap7Client->ReadArea(int1, int2, int3, int4 - , int5, pData); - break; - - case WRITEAREA: - returnValue = s7client->snap7Client->WriteArea(int1, int2, int3, int4 - , int5, pData); - break; - - case READMULTI: - returnValue = s7client->snap7Client->ReadMultiVars( - static_cast(pData), int1); - break; - - case WRITEMULTI: - returnValue = s7client->snap7Client->WriteMultiVars( - static_cast(pData), int1); - break; - - case PLCSTATUS: - returnValue = s7client->snap7Client->PlcStatus(); - if ((returnValue == S7CpuStatusUnknown) || - (returnValue == S7CpuStatusStop) || - (returnValue == S7CpuStatusRun)) { - int1 = returnValue; - returnValue = 0; - } - break; - - case CLEARSESSIONPW: - returnValue = s7client->snap7Client->ClearSessionPassword(); - break; - - case SETSESSIONPW: - returnValue = s7client->snap7Client->SetSessionPassword( - **static_cast(pData)); - break; - - case GETPROTECTION: - returnValue = s7client->snap7Client->GetProtection( - static_cast(pData)); - break; - - case PLCSTOP: - returnValue = s7client->snap7Client->PlcStop(); - break; - - case PLCCOLDSTART: - returnValue = s7client->snap7Client->PlcColdStart(); - break; - - case PLCHOTSTART: - returnValue = s7client->snap7Client->PlcHotStart(); - break; - - case GETCPINFO: - returnValue = s7client->snap7Client->GetCpInfo( - static_cast(pData)); - break; - - case GETCPUINFO: - returnValue = s7client->snap7Client->GetCpuInfo( - static_cast(pData)); - break; - - case GETORDERCODE: - returnValue = s7client->snap7Client->GetOrderCode( - static_cast(pData)); - break; - - case SETPLCSYSTEMDATETIME: - returnValue = s7client->snap7Client->SetPlcSystemDateTime(); - break; - - case GETPLCDATETIME: - returnValue = s7client->snap7Client->GetPlcDateTime( - static_cast(pData)); - break; - - case SETPLCDATETIME: - returnValue = s7client->snap7Client->SetPlcDateTime( - static_cast(pData)); - break; - - case COMPRESS: - returnValue = s7client->snap7Client->Compress(int1); - break; - - case COPYRAMTOROM: - returnValue = s7client->snap7Client->CopyRamToRom(int1); - break; - - case DBFILL: - returnValue = s7client->snap7Client->DBFill(int1, int2); - break; - - case DBGET: - returnValue = s7client->snap7Client->DBGet(int1, pData, &int2); - break; - - case DELETEBLOCK: - returnValue = s7client->snap7Client->Delete(int1, int2); - break; - - case DOWNLOAD: - returnValue = s7client->snap7Client->Download(int1, pData, int2); - break; - - case FULLUPLOAD: - returnValue = s7client->snap7Client->FullUpload(int1, int2, pData, &int3); - break; - - case UPLOAD: - returnValue = s7client->snap7Client->Upload(int1, int2, pData, &int3); - break; - - case LISTBLOCKSOFTYPE: - returnValue = s7client->snap7Client->ListBlocksOfType(int1 - , static_cast(pData), &int2); - break; - - case GETAGBLOCKINFO: - returnValue = s7client->snap7Client->GetAgBlockInfo(int1, int2 - , static_cast(pData)); - break; - - case LISTBLOCKS: - returnValue = s7client->snap7Client->ListBlocks( - static_cast(pData)); - break; - - case READSZLLIST: - returnValue = s7client->snap7Client->ReadSZLList( - static_cast(pData), &int1); - break; - - case READSZL: - returnValue = s7client->snap7Client->ReadSZL(int1, int2 - , static_cast(pData), &int3); - break; - } - - uv_mutex_unlock(&s7client->mutex); +void IOWorkerClient::Execute() { + std::lock_guard lock(s7client->mutex); + + switch (caller) { + case DataIOFunction::CONNECTTO: + ret = + s7client->snap7Client->ConnectTo(static_cast(pData)->c_str(), int1, int2); + break; + + case DataIOFunction::CONNECT: + ret = s7client->snap7Client->Connect(); + break; + + case DataIOFunction::READAREA: + ret = s7client->snap7Client->ReadArea(int1, int2, int3, int4, int5, pData); + break; + + case DataIOFunction::WRITEAREA: + ret = s7client->snap7Client->WriteArea(int1, int2, int3, int4, int5, pData); + break; + + case DataIOFunction::READMULTI: + ret = s7client->snap7Client->ReadMultiVars(static_cast(pData), int1); + break; + + case DataIOFunction::WRITEMULTI: + ret = s7client->snap7Client->WriteMultiVars(static_cast(pData), int1); + break; + + case DataIOFunction::PLCSTATUS: + ret = s7client->snap7Client->PlcStatus(); + if ((ret == S7CpuStatusUnknown) || (ret == S7CpuStatusStop) || (ret == S7CpuStatusRun)) { + int1 = ret; + ret = 0; + } + break; + + case DataIOFunction::CLEARSESSIONPW: + ret = s7client->snap7Client->ClearSessionPassword(); + break; + + case DataIOFunction::SETSESSIONPW: + ret = + s7client->snap7Client->SetSessionPassword(&*static_cast(pData)->begin()); + break; + + case DataIOFunction::GETPROTECTION: + ret = s7client->snap7Client->GetProtection(static_cast(pData)); + break; + + case DataIOFunction::PLCSTOP: + ret = s7client->snap7Client->PlcStop(); + break; + + case DataIOFunction::PLCCOLDSTART: + ret = s7client->snap7Client->PlcColdStart(); + break; + + case DataIOFunction::PLCHOTSTART: + ret = s7client->snap7Client->PlcHotStart(); + break; + + case DataIOFunction::GETCPINFO: + ret = s7client->snap7Client->GetCpInfo(static_cast(pData)); + break; + + case DataIOFunction::GETCPUINFO: + ret = s7client->snap7Client->GetCpuInfo(static_cast(pData)); + break; + + case DataIOFunction::GETORDERCODE: + ret = s7client->snap7Client->GetOrderCode(static_cast(pData)); + break; + + case DataIOFunction::SETPLCSYSTEMDATETIME: + ret = s7client->snap7Client->SetPlcSystemDateTime(); + break; + + case DataIOFunction::GETPLCDATETIME: + ret = s7client->snap7Client->GetPlcDateTime(static_cast(pData)); + break; + + case DataIOFunction::SETPLCDATETIME: + ret = s7client->snap7Client->SetPlcDateTime(static_cast(pData)); + break; + + case DataIOFunction::COMPRESS: + ret = s7client->snap7Client->Compress(int1); + break; + + case DataIOFunction::COPYRAMTOROM: + ret = s7client->snap7Client->CopyRamToRom(int1); + break; + + case DataIOFunction::DBFILL: + ret = s7client->snap7Client->DBFill(int1, int2); + break; + + case DataIOFunction::DBGET: + ret = s7client->snap7Client->DBGet(int1, pData, &int2); + break; + + case DataIOFunction::DELETEBLOCK: + ret = s7client->snap7Client->Delete(int1, int2); + break; + + case DataIOFunction::DOWNLOAD: + ret = s7client->snap7Client->Download(int1, pData, int2); + break; + + case DataIOFunction::FULLUPLOAD: + ret = s7client->snap7Client->FullUpload(int1, int2, pData, &int3); + break; + + case DataIOFunction::UPLOAD: + ret = s7client->snap7Client->Upload(int1, int2, pData, &int3); + break; + + case DataIOFunction::LISTBLOCKSOFTYPE: + ret = s7client->snap7Client->ListBlocksOfType(int1, + static_cast(pData), + &int2); + break; + + case DataIOFunction::GETAGBLOCKINFO: + ret = s7client->snap7Client->GetAgBlockInfo(int1, int2, static_cast(pData)); + break; + + case DataIOFunction::LISTBLOCKS: + ret = s7client->snap7Client->ListBlocks(static_cast(pData)); + break; + + case DataIOFunction::READSZLLIST: + ret = s7client->snap7Client->ReadSZLList(static_cast(pData), &int1); + break; + + case DataIOFunction::READSZL: + ret = s7client->snap7Client->ReadSZL(int1, int2, static_cast(pData), &int3); + break; + } } -void IOWorker::HandleOKCallback() { - Nan::HandleScope scope; - - v8::Local argv1[1]; - v8::Local argv2[2]; - - if (returnValue == 0) { - argv2[0] = argv1[0] = Nan::Null(); - } else { - argv2[0] = argv1[0] = Nan::New(returnValue); - } - - switch (caller) { - case CONNECTTO: - case SETSESSIONPW: - delete static_cast(pData); - callback->Call(1, argv1, async_resource); - break; - - case CONNECT: - case WRITEAREA: - case CLEARSESSIONPW: - case PLCSTOP: - case PLCCOLDSTART: - case PLCHOTSTART: - case SETPLCSYSTEMDATETIME: - case COPYRAMTOROM: - case COMPRESS: - case DBFILL: - case DELETEBLOCK: - case DOWNLOAD: - callback->Call(1, argv1, async_resource); - break; - - case READAREA: - if (returnValue == 0) { - argv2[1] = Nan::NewBuffer( - static_cast(pData) - , int4 * s7client->GetByteCountFromWordLen(int5) - , S7Client::FreeCallback, - NULL).ToLocalChecked(); - } else { - argv2[1] = Nan::Null(); - delete[] static_cast(pData); - } - callback->Call(2, argv2, async_resource); - break; - - case READMULTI: - if (returnValue == 0) { - argv2[1] = s7client->S7DataItemToArray(static_cast(pData) - , int1, true); - } else { - for (int i = 0; i < int1; i++) { - delete[] static_cast(static_cast(pData)[i].pdata); +void IOWorkerClient::OnOK() { + Napi::Value val = Env().Null(); + + switch (caller) { + case DataIOFunction::CONNECTTO: + case DataIOFunction::SETSESSIONPW: + delete static_cast(pData); + break; + + case DataIOFunction::CONNECT: + case DataIOFunction::WRITEAREA: + case DataIOFunction::CLEARSESSIONPW: + case DataIOFunction::PLCSTOP: + case DataIOFunction::PLCCOLDSTART: + case DataIOFunction::PLCHOTSTART: + case DataIOFunction::SETPLCSYSTEMDATETIME: + case DataIOFunction::COPYRAMTOROM: + case DataIOFunction::COMPRESS: + case DataIOFunction::DBFILL: + case DataIOFunction::DELETEBLOCK: + case DataIOFunction::DOWNLOAD: + break; + + case DataIOFunction::READAREA: + if (ret == 0) { + val = Napi::Buffer::NewOrCopy(Env(), + static_cast(pData), + static_cast(int4) * + s7client->GetByteCountFromWordLen(int5), + S7Client::FreeCallback); + } else { + delete[] static_cast(pData); } - delete[] static_cast(pData); - argv2[1] = Nan::Null(); - } - callback->Call(2, argv2, async_resource); - break; - - case WRITEMULTI: - if (returnValue == 0) { - argv2[1] = s7client->S7DataItemToArray(static_cast(pData) - , int1, false); - } else { - delete[] static_cast(pData); - argv2[1] = Nan::Null(); - } - callback->Call(2, argv2, async_resource); - break; - - case GETPROTECTION: - if (returnValue == 0) { - argv2[1] = s7client->S7ProtectionToObject( - static_cast(pData)); - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case GETCPINFO: - if (returnValue == 0) { - argv2[1] = s7client->S7CpInfoToObject( - static_cast(pData)); - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case GETCPUINFO: - if (returnValue == 0) { - argv2[1] = s7client->S7CpuInfoToObject( - static_cast(pData)); - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case GETORDERCODE: - if (returnValue == 0) { - argv2[1] = s7client->S7OrderCodeToObject( - static_cast(pData)); - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case GETPLCDATETIME: - if (returnValue == 0) { - double timestamp = static_cast(mktime(static_cast(pData))); - argv2[1] = Nan::New(timestamp * 1000).ToLocalChecked(); - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case SETPLCDATETIME: - delete static_cast(pData); - callback->Call(1, argv1, async_resource); - break; - - case PLCSTATUS: - if (returnValue == 0) { - argv2[1] = Nan::New(int1); - } else { - argv2[1] = Nan::Null(); - } - callback->Call(2, argv2, async_resource); - break; - - case DBGET: - if (returnValue == 0) { - argv2[1] = Nan::NewBuffer( - static_cast(pData) - , int2 - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - } else { - argv2[1] = Nan::Null(); - delete[] static_cast(pData); - } - callback->Call(2, argv2, async_resource); - break; - - case FULLUPLOAD: - case UPLOAD: - if (returnValue == 0) { - argv2[1] = Nan::NewBuffer( - static_cast(pData) - , int3 - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - } else { - argv2[1] = Nan::Null(); - delete[] static_cast(pData); - } - callback->Call(2, argv2, async_resource); - break; - - case LISTBLOCKSOFTYPE: - if (returnValue == 0) { - argv2[1] = s7client->S7BlocksOfTypeToArray( - static_cast(pData), int2); - } else { - argv2[1] = Nan::Null(); - } - delete[] static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case GETAGBLOCKINFO: - if (returnValue == 0) { - v8::Local block_info = s7client->S7BlockInfoToObject( - static_cast(pData)); - argv2[1] = block_info; - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case LISTBLOCKS: - if (returnValue == 0) { - v8::Local blocks_list = s7client->S7BlocksListToObject( - static_cast(pData)); - argv2[1] = blocks_list; - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case READSZLLIST: - if (returnValue == 0) { - v8::Local szl_list = s7client->S7SZLListToArray( - static_cast(pData), int1); - argv2[1] = szl_list; - } else { - argv2[1] = Nan::Null(); - } - delete static_cast(pData); - callback->Call(2, argv2, async_resource); - break; - - case READSZL: - if (returnValue == 0) { - argv2[1] = Nan::NewBuffer( - reinterpret_cast(static_cast(pData)) - , int3 - , S7Client::FreeCallbackSZL - , NULL).ToLocalChecked(); - } else { - argv2[1] = Nan::Null(); - delete static_cast(pData); - } - callback->Call(2, argv2, async_resource); - break; - } -} + break; + + case DataIOFunction::READMULTI: + if (ret == 0) { + val = s7client->S7DataItemToArray(static_cast(pData), int1, true); + } else { + for (int i = 0; i < int1; i++) { + delete[] static_cast(static_cast(pData)[i].pdata); + } + delete[] static_cast(pData); + } + break; -NAN_METHOD(S7Client::ReadArea) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (info.Length() < 5) - return Nan::ThrowTypeError("Wrong number of Arguments"); - - if (!info[0]->IsInt32() || !info[1]->IsInt32() || - !info[2]->IsInt32() || !info[3]->IsInt32() || - !info[4]->IsInt32()) - return Nan::ThrowTypeError("Wrong arguments"); - - int amount = Nan::To(info[3]).FromJust(); - int byteCount = s7client->GetByteCountFromWordLen(Nan::To(info[4]).FromJust()); - int size = amount * byteCount; - char *bufferData = new char[size]; - - if (!info[5]->IsFunction()) { - int returnValue = s7client->snap7Client->ReadArea( - Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust() - , Nan::To(info[2]).FromJust(), Nan::To(info[3]).FromJust() - , Nan::To(info[4]).FromJust(), bufferData); - - if (returnValue == 0) { - v8::Local ret = Nan::NewBuffer( - bufferData - , size - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - info.GetReturnValue().Set(ret); + case DataIOFunction::WRITEMULTI: + if (ret == 0) { + val = s7client->S7DataItemToArray(static_cast(pData), int1, false); + } else { + delete[] static_cast(pData); + } + break; + + case DataIOFunction::GETPROTECTION: + if (ret == 0) { + val = s7client->S7ProtectionToObject(static_cast(pData)); + } + delete static_cast(pData); + break; + + case DataIOFunction::GETCPINFO: + if (ret == 0) { + val = s7client->S7CpInfoToObject(static_cast(pData)); + } + delete static_cast(pData); + break; + + case DataIOFunction::GETCPUINFO: + if (ret == 0) { + val = s7client->S7CpuInfoToObject(static_cast(pData)); + } + delete static_cast(pData); + break; + + case DataIOFunction::GETORDERCODE: + if (ret == 0) { + val = s7client->S7OrderCodeToObject(static_cast(pData)); + } + delete static_cast(pData); + break; + + case DataIOFunction::GETPLCDATETIME: + if (ret == 0) { + double timestamp = static_cast(mktime(static_cast(pData))); + val = Napi::Date::New(Env(), timestamp * 1000); + } + delete static_cast(pData); + break; + + case DataIOFunction::SETPLCDATETIME: + delete static_cast(pData); + break; + + case DataIOFunction::PLCSTATUS: + if (ret == 0) { + val = Napi::Number::New(Env(), int1); + } + break; + + case DataIOFunction::DBGET: + if (ret == 0) { + val = Napi::Buffer::NewOrCopy(Env(), + static_cast(pData), + int2, + S7Client::FreeCallback); + } else { + delete[] static_cast(pData); + } + break; + + case DataIOFunction::FULLUPLOAD: + case DataIOFunction::UPLOAD: + if (ret == 0) { + val = Napi::Buffer::NewOrCopy(Env(), + static_cast(pData), + int3, + S7Client::FreeCallback); + } else { + delete[] static_cast(pData); + } + break; + + case DataIOFunction::LISTBLOCKSOFTYPE: + if (ret == 0) { + val = s7client->S7BlocksOfTypeToArray(static_cast(pData), int2); + } + delete[] static_cast(pData); + break; + + case DataIOFunction::GETAGBLOCKINFO: + if (ret == 0) { + Napi::Object block_info = + s7client->S7BlockInfoToObject(static_cast(pData)); + val = block_info; + } + delete static_cast(pData); + break; + + case DataIOFunction::LISTBLOCKS: + if (ret == 0) { + Napi::Object blocks_list = + s7client->S7BlocksListToObject(static_cast(pData)); + val = blocks_list; + } + delete static_cast(pData); + break; + + case DataIOFunction::READSZLLIST: + if (ret == 0) { + Napi::Array szl_list = s7client->S7SZLListToArray(static_cast(pData), int1); + val = szl_list; + } + delete static_cast(pData); + break; + + case DataIOFunction::READSZL: + if (ret == 0) { + val = Napi::Buffer::NewOrCopy(Env(), + reinterpret_cast(static_cast(pData)), + int3, + S7Client::FreeCallbackSZL); + } else { + delete static_cast(pData); + } + break; + } + + if (ret == 0) { + m_deferred.Resolve(val); } else { - delete[] bufferData; - info.GetReturnValue().Set(Nan::False()); + Napi::Error err = S7Client::MakeError(Env(), "Snap7 operation failed", ret); + m_deferred.Reject(err.Value()); + } +} + +Napi::Value S7Client::ReadArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 5); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); + REQUIRE_ARG(env, info, 3, IsNumber); + REQUIRE_ARG(env, info, 4, IsNumber); + + int area = info[0].As().Int32Value(); + int dbNumber = info[1].As().Int32Value(); + int start = info[2].As().Int32Value(); + int amount = info[3].As().Int32Value(); + int wordLen = info[4].As().Int32Value(); + + int byteCount = GetByteCountFromWordLen(wordLen); + if (byteCount == 0) { + Napi::TypeError::New(env, "Invalid WordLen").ThrowAsJavaScriptException(); + return env.Undefined(); + } + size_t size = amount * byteCount; + char* bufferData = new char[size]; + + if (info.Length() > 5 && info[5].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::READAREA, + bufferData, + area, + dbNumber, + start, + amount, + wordLen); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->ReadArea(area, dbNumber, start, amount, wordLen, bufferData); + if (ret == 0) { + return Napi::Buffer::NewOrCopy(env, bufferData, size, S7Client::FreeCallback); } - } else { - Nan::Callback *callback = new Nan::Callback(info[5].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, READAREA - , bufferData, Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust() - , Nan::To(info[2]).FromJust(), Nan::To(info[3]).FromJust(), Nan::To(info[4]).FromJust())); - info.GetReturnValue().SetUndefined(); - } + delete[] bufferData; + MakeError(env, "ReadArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::WriteArea) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (info.Length() < 6) - return Nan::ThrowTypeError("Wrong number of Arguments"); - - if (!info[0]->IsInt32() || !info[1]->IsInt32() || - !info[2]->IsInt32() || !info[3]->IsInt32() || - !info[4]->IsInt32() || !node::Buffer::HasInstance(info[5])) - return Nan::ThrowTypeError("Wrong arguments"); - - if (!info[6]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->WriteArea(Nan::To(info[0]).FromJust() - , Nan::To(info[1]).FromJust(), Nan::To(info[2]).FromJust() - , Nan::To(info[3]).FromJust(), Nan::To(info[4]).FromJust() - , node::Buffer::Data(info[5].As())) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[6].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, WRITEAREA - , node::Buffer::Data(info[5].As()), Nan::To(info[0]).FromJust() - , Nan::To(info[1]).FromJust(), Nan::To(info[2]).FromJust(), Nan::To(info[3]).FromJust() - , Nan::To(info[4]).FromJust())); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::WriteArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 6); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); + REQUIRE_ARG(env, info, 3, IsNumber); + REQUIRE_ARG(env, info, 4, IsNumber); + REQUIRE_ARG(env, info, 5, IsBuffer); + + int area = info[0].As().Int32Value(); + int dbNumber = info[1].As().Int32Value(); + int start = info[2].As().Int32Value(); + int amount = info[3].As().Int32Value(); + int wordLen = info[4].As().Int32Value(); + Napi::Buffer buffer = info[5].As>(); + + int byteCount = GetByteCountFromWordLen(wordLen); + if (byteCount == 0) { + Napi::TypeError::New(env, "Invalid WordLen").ThrowAsJavaScriptException(); + return env.Undefined(); + } + size_t size = static_cast(amount) * byteCount; + + if (buffer.Length() < size) { + Napi::TypeError::New(env, "Buffer too small for requested write") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + + if (info.Length() > 6 && info[6].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::WRITEAREA, + buffer.Data(), + area, + dbNumber, + start, + amount, + wordLen); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->WriteArea(area, dbNumber, start, amount, wordLen, buffer.Data()); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "WriteArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::ReadMultiVars) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (info.Length() < 1) { - return Nan::ThrowTypeError("Wrong number of arguments"); - } - - if (!info[0]->IsArray()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - v8::Local data_arr = v8::Local::Cast(info[0]); - int len = data_arr->Length(); - if (len == 0) { - return Nan::ThrowTypeError("Array needs at least 1 item"); - } else if (len > MaxVars) { - std::stringstream err; - err << "Array exceeds max variables (" << MaxVars - << ") that can be transferred with ReadMultiVars()"; - return Nan::ThrowTypeError(err.str().c_str()); - } - - for (int i = 0; i < len; i++) { - if (!Nan::Get(data_arr, i).ToLocalChecked()->IsObject()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else { - v8::Local data_obj = Nan::To(Nan::Get(data_arr, i).ToLocalChecked()).ToLocalChecked(); - if (!Nan::Has(data_obj, Nan::New("Area").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("WordLen").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("Start").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("Amount").ToLocalChecked()).FromJust()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else if (!Nan::Get(data_obj, Nan::New("Area").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("WordLen").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("Start").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("Amount").ToLocalChecked()).ToLocalChecked()->IsInt32()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else if (Nan::To(Nan::Get(data_obj, Nan::New("Area").ToLocalChecked()).ToLocalChecked()).FromJust() == S7AreaDB) { - if (!Nan::Has(data_obj, Nan::New("DBNumber").ToLocalChecked()).FromJust()) { - return Nan::ThrowTypeError("Wrong argument structure"); +Napi::Value S7Client::ReadMultiVars(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsArray); + + const Napi::Array data_arr = info[0].As(); + uint32_t len = data_arr.Length(); + if (len == 0) { + Napi::TypeError::New(env, "Array needs at least 1 item").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (len > MaxVars) { + std::stringstream err; + err << "Array exceeds max variables (" << MaxVars + << ") that can be transferred with ReadMultiVars()"; + Napi::TypeError::New(env, err.str()).ThrowAsJavaScriptException(); + return env.Undefined(); + } + + for (uint32_t i = 0; i < len; i++) { + if (!data_arr[i].IsObject()) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else { + Napi::Object data_obj = data_arr[i].As(); + if (!data_obj.Has("Area") || !data_obj.Has("WordLen") || !data_obj.Has("Start") || + !data_obj.Has("Amount")) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (!data_obj.Get("Area").IsNumber() || !data_obj.Get("WordLen").IsNumber() || + !data_obj.Get("Start").IsNumber() || !data_obj.Get("Amount").IsNumber()) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (data_obj.Get("Area").As().Int32Value() == S7AreaDB) { + if (!data_obj.Has("DBNumber")) { + Napi::TypeError::New(env, "Wrong argument structure") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + } else { + data_obj.Set("DBNumber", Napi::Number::New(env, 0)); + } } - } else { - Nan::Set(data_obj, Nan::New("DBNumber").ToLocalChecked(), Nan::New(0)); - } - } - } - - PS7DataItem Items = new TS7DataItem[len]; - v8::Local data_obj; - int byteCount, size; - - for (int i = 0; i < len; i++) { - data_obj = Nan::To(Nan::Get(data_arr, i).ToLocalChecked()).ToLocalChecked(); - - Items[i].Area = Nan::To(Nan::Get(data_obj, - Nan::New("Area").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].WordLen = Nan::To(Nan::Get(data_obj, - Nan::New("WordLen").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].DBNumber = Nan::To(Nan::Get(data_obj, - Nan::New("DBNumber").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].Start = Nan::To(Nan::Get(data_obj, - Nan::New("Start").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].Amount = Nan::To(Nan::Get(data_obj, - Nan::New("Amount").ToLocalChecked()).ToLocalChecked()).FromJust(); - - byteCount = s7client->GetByteCountFromWordLen(Items[i].WordLen); - size = Items[i].Amount * byteCount; - Items[i].pdata = new char[size]; - } - - if (!info[1]->IsFunction()) { - int returnValue = s7client->snap7Client->ReadMultiVars(Items, len); - - if (returnValue == 0) { - info.GetReturnValue().Set(s7client->S7DataItemToArray(Items, len, true)); - } else { - for (int i = 0; i < len; i++) { + } + + PS7DataItem Items = new TS7DataItem[len]; + Napi::Object data_obj; + int byteCount, size; + + for (uint32_t i = 0; i < len; i++) { + data_obj = data_arr[i].As(); + + Items[i].Area = data_obj.Get("Area").As().Int32Value(); + Items[i].WordLen = data_obj.Get("WordLen").As().Int32Value(); + Items[i].DBNumber = data_obj.Get("DBNumber").As().Int32Value(); + Items[i].Start = data_obj.Get("Start").As().Int32Value(); + Items[i].Amount = data_obj.Get("Amount").As().Int32Value(); + + byteCount = GetByteCountFromWordLen(Items[i].WordLen); + size = Items[i].Amount * byteCount; + Items[i].pdata = new char[size]; + } + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::READMULTI, Items, len); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->ReadMultiVars(Items, len); + if (ret == 0) { + Napi::Array res_arr = S7DataItemToArray(Items, len, true); + return res_arr; + } + + for (uint32_t i = 0; i < len; i++) { delete[] static_cast(Items[i].pdata); - } - delete[] Items; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, READMULTI - , Items, len)); - info.GetReturnValue().SetUndefined(); - } + } + delete[] Items; + MakeError(env, "ReadMultiVars failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7DataItemToArray( - PS7DataItem Items - , int len - , bool readMulti -) { - Nan::EscapableHandleScope scope; - - v8::Local res_arr = Nan::New(len); - v8::Local res_obj; - int byteCount, size; - - for (int i = 0; i < len; i++) { - res_obj = Nan::New(); - Nan::Set(res_obj, Nan::New("Result").ToLocalChecked() - , Nan::New(Items[i].Result)); - - if (readMulti == true) { - if (Items[i].Result == 0) { - byteCount = S7Client::GetByteCountFromWordLen(Items[i].WordLen); - size = byteCount * Items[i].Amount; - Nan::Set( - res_obj - , Nan::New("Data").ToLocalChecked() - , Nan::NewBuffer( - static_cast(Items[i].pdata) - , size - , S7Client::FreeCallback - , NULL).ToLocalChecked()); - } else { - delete[] static_cast(Items[i].pdata); - Nan::Set(res_obj, Nan::New("Data").ToLocalChecked(), Nan::Null()); - } +Napi::Array S7Client::S7DataItemToArray(PS7DataItem Items, int len, bool readMulti) { + Napi::EscapableHandleScope scope(Env()); + + Napi::Array res_arr = Napi::Array::New(Env(), len); + Napi::Object res_obj; + int byteCount, size; + + for (int i = 0; i < len; i++) { + res_obj = Napi::Object::New(Env()); + res_obj.Set("Result", Napi::Number::New(Env(), Items[i].Result)); + + if (readMulti == true) { + if (Items[i].Result == 0) { + byteCount = S7Client::GetByteCountFromWordLen(Items[i].WordLen); + size = byteCount * Items[i].Amount; + res_obj.Set("Data", + Napi::Buffer::NewOrCopy(Env(), + static_cast(Items[i].pdata), + size, + S7Client::FreeCallback)); + } else { + delete[] static_cast(Items[i].pdata); + res_obj.Set("Data", Env().Null()); + } + } + res_arr.Set(i, res_obj); } - Nan::Set(res_arr, i, res_obj); - } - delete[] Items; + delete[] Items; - return scope.Escape(res_arr); + return scope.Escape(res_arr).As(); } -NAN_METHOD(S7Client::WriteMultiVars) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (info.Length() < 1) { - return Nan::ThrowTypeError("Wrong number of arguments"); - } - - if (!info[0]->IsArray()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - v8::Local data_arr = v8::Local::Cast(info[0]); - int len = data_arr->Length(); - if (len == 0) { - return Nan::ThrowTypeError("Array needs at least 1 item"); - } else if (len > MaxVars) { - std::stringstream err; - err << "Array exceeds max variables (" << MaxVars - << ") that can be transferred with WriteMultiVars()"; - return Nan::ThrowTypeError(err.str().c_str()); - } - - for (int i = 0; i < len; i++) { - if (!Nan::Get(data_arr, i).ToLocalChecked()->IsObject()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else { - v8::Local data_obj = Nan::To(Nan::Get(data_arr, i).ToLocalChecked()).ToLocalChecked(); - if (!Nan::Has(data_obj, Nan::New("Area").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("WordLen").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("Start").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("Amount").ToLocalChecked()).FromJust() || - !Nan::Has(data_obj, Nan::New("Data").ToLocalChecked()).FromJust()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else if (!Nan::Get(data_obj, Nan::New("Area").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("WordLen").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("Start").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(data_obj, Nan::New("Amount").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !node::Buffer::HasInstance(Nan::Get(data_obj, Nan::New("Data").ToLocalChecked()).ToLocalChecked())) { - return Nan::ThrowTypeError("Wrong argument structure"); - } else if (Nan::To(Nan::Get(data_obj, Nan::New("Area").ToLocalChecked()).ToLocalChecked()).FromJust() == S7AreaDB) { - if (!Nan::Has(data_obj, Nan::New("DBNumber").ToLocalChecked()).FromJust()) { - return Nan::ThrowTypeError("Wrong argument structure"); +Napi::Value S7Client::WriteMultiVars(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsArray); + + const Napi::Array data_arr = info[0].As(); + uint32_t len = data_arr.Length(); + if (len == 0) { + Napi::TypeError::New(env, "Array needs at least 1 item").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (len > MaxVars) { + std::stringstream err; + err << "Array exceeds max variables (" << MaxVars + << ") that can be transferred with WriteMultiVars()"; + Napi::TypeError::New(env, err.str()).ThrowAsJavaScriptException(); + return env.Undefined(); + } + + for (uint32_t i = 0; i < len; i++) { + if (!data_arr[i].IsObject()) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else { + Napi::Object data_obj = data_arr[i].As(); + if (!data_obj.Has("Area") || !data_obj.Has("WordLen") || !data_obj.Has("Start") || + !data_obj.Has("Amount") || !data_obj.Has("Data")) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (!data_obj.Get("Area").IsNumber() || !data_obj.Get("WordLen").IsNumber() || + !data_obj.Get("Start").IsNumber() || !data_obj.Get("Amount").IsNumber() || + !data_obj.Get("Data").IsBuffer()) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } else if (data_obj.Get("Area").As().Int32Value() == S7AreaDB) { + if (!data_obj.Has("DBNumber")) { + Napi::TypeError::New(env, "Wrong argument structure") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + } else { + data_obj.Set("DBNumber", Napi::Number::New(env, 0)); + } } - } else { - Nan::Set(data_obj, Nan::New("DBNumber").ToLocalChecked(), Nan::New(0)); - } - } - } - - PS7DataItem Items = new TS7DataItem[len]; - v8::Local data_obj; - for (int i = 0; i < len; i++) { - data_obj = Nan::To(Nan::Get(data_arr, i).ToLocalChecked()).ToLocalChecked(); - - Items[i].Area = Nan::To(Nan::Get(data_obj, - Nan::New("Area").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].WordLen = Nan::To(Nan::Get(data_obj, - Nan::New("WordLen").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].DBNumber = Nan::To(Nan::Get(data_obj, - Nan::New("DBNumber").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].Start = Nan::To(Nan::Get(data_obj, - Nan::New("Start").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].Amount = Nan::To(Nan::Get(data_obj, - Nan::New("Amount").ToLocalChecked()).ToLocalChecked()).FromJust(); - Items[i].pdata = node::Buffer::Data(Nan::Get(data_obj, - Nan::New("Data").ToLocalChecked()).ToLocalChecked().As()); - } - - if (!info[1]->IsFunction()) { - int returnValue = s7client->snap7Client->WriteMultiVars(Items, len); - - if (returnValue == 0) { - info.GetReturnValue().Set(s7client->S7DataItemToArray(Items, len, false)); - } else { - delete[] Items; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, WRITEMULTI - , Items, len)); - info.GetReturnValue().SetUndefined(); - } + } + + PS7DataItem Items = new TS7DataItem[len]; + Napi::Object data_obj; + for (uint32_t i = 0; i < len; i++) { + data_obj = data_arr[i].As(); + + Items[i].Area = data_obj.Get("Area").As().Int32Value(); + Items[i].WordLen = data_obj.Get("WordLen").As().Int32Value(); + Items[i].DBNumber = data_obj.Get("DBNumber").As().Int32Value(); + Items[i].Start = data_obj.Get("Start").As().Int32Value(); + Items[i].Amount = data_obj.Get("Amount").As().Int32Value(); + + int byteCount = S7Client::GetByteCountFromWordLen(Items[i].WordLen); + if (byteCount == 0) { + delete[] Items; + Napi::TypeError::New(env, "Invalid WordLen").ThrowAsJavaScriptException(); + return env.Undefined(); + } + size_t expectedSize = static_cast(Items[i].Amount) * byteCount; + Napi::Buffer dataBuf = data_obj.Get("Data").As>(); + if (dataBuf.Length() < expectedSize) { + delete[] Items; + Napi::TypeError::New(env, "Data buffer too small for requested write") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + + Items[i].pdata = dataBuf.Data(); + } + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::WRITEMULTI, Items, len); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->WriteMultiVars(Items, len); + if (ret == 0) { + Napi::Array res_arr = S7DataItemToArray(Items, len, false); + return res_arr; + } + + delete[] Items; + MakeError(env, "WriteMultiVars failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } // Directory functions -NAN_METHOD(S7Client::ListBlocks) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::ListBlocks(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - PS7BlocksList BlocksList = new TS7BlocksList; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->ListBlocks(BlocksList); + REQUIRE_MIN_ARGS(env, info, 0); - v8::Local blocks_list = s7client->S7BlocksListToObject( - BlocksList); + PS7BlocksList BlocksList = new TS7BlocksList; + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::LISTBLOCKS, BlocksList); + worker->Queue(); + return worker->GetPromise(); + } - if (returnValue == 0) { - delete BlocksList; - info.GetReturnValue().Set(blocks_list); - } else { - delete BlocksList; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, LISTBLOCKS - , BlocksList)); - info.GetReturnValue().SetUndefined(); - } -} + int ret = snap7Client->ListBlocks(BlocksList); + if (ret == 0) { + Napi::Object blocks_list = S7BlocksListToObject(BlocksList); + delete BlocksList; + return blocks_list; + } -v8::Local S7Client::S7BlocksListToObject( - PS7BlocksList BlocksList -) { - Nan::EscapableHandleScope scope; - - v8::Local blocks_list = Nan::New(); - Nan::Set(blocks_list, Nan::New("OBCount").ToLocalChecked() - , Nan::New(BlocksList->OBCount)); - Nan::Set(blocks_list, Nan::New("FBCount").ToLocalChecked() - , Nan::New(BlocksList->FBCount)); - Nan::Set(blocks_list, Nan::New("FCCount").ToLocalChecked() - , Nan::New(BlocksList->FCCount)); - Nan::Set(blocks_list, Nan::New("SFBCount").ToLocalChecked() - , Nan::New(BlocksList->SFBCount)); - Nan::Set(blocks_list, Nan::New("SFCCount").ToLocalChecked() - , Nan::New(BlocksList->SFCCount)); - Nan::Set(blocks_list, Nan::New("DBCount").ToLocalChecked() - , Nan::New(BlocksList->DBCount)); - Nan::Set(blocks_list, Nan::New("SDBCount").ToLocalChecked() - , Nan::New(BlocksList->SDBCount)); - - return scope.Escape(blocks_list); + delete BlocksList; + MakeError(env, "ListBlocks failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } +Napi::Object S7Client::S7BlocksListToObject(PS7BlocksList BlocksList) { + Napi::EscapableHandleScope scope(Env()); -NAN_METHOD(S7Client::GetAgBlockInfo) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); + Napi::Object blocks_list = Napi::Object::New(Env()); + blocks_list.Set("OBCount", Napi::Number::New(Env(), BlocksList->OBCount)); + blocks_list.Set("FBCount", Napi::Number::New(Env(), BlocksList->FBCount)); + blocks_list.Set("FCCount", Napi::Number::New(Env(), BlocksList->FCCount)); + blocks_list.Set("SFBCount", Napi::Number::New(Env(), BlocksList->SFBCount)); + blocks_list.Set("SFCCount", Napi::Number::New(Env(), BlocksList->SFCCount)); + blocks_list.Set("DBCount", Napi::Number::New(Env(), BlocksList->DBCount)); + blocks_list.Set("SDBCount", Napi::Number::New(Env(), BlocksList->SDBCount)); - if (!info[0]->IsInt32() || !info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + return scope.Escape(blocks_list).As(); +} - PS7BlockInfo BlockInfo = new TS7BlockInfo; - if (!info[2]->IsFunction()) { - int returnValue = s7client->snap7Client->GetAgBlockInfo( - Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), BlockInfo); +Napi::Value S7Client::GetAgBlockInfo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + + int BlockType = info[0].As().Int32Value(); + int BlockNum = info[1].As().Int32Value(); + + PS7BlockInfo BlockInfo = new TS7BlockInfo; + if (info.Length() > 2 && info[2].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::GETAGBLOCKINFO, + BlockInfo, + BlockType, + BlockNum); + worker->Queue(); + return worker->GetPromise(); + } - if (returnValue == 0) { - v8::Local block_info = s7client->S7BlockInfoToObject( - BlockInfo); + int ret = snap7Client->GetAgBlockInfo(BlockType, BlockNum, BlockInfo); + if (ret == 0) { + Napi::Object block_info = S7BlockInfoToObject(BlockInfo); + delete BlockInfo; + return block_info; + } - delete BlockInfo; - info.GetReturnValue().Set(block_info); - } else { - delete BlockInfo; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETAGBLOCKINFO - , BlockInfo, Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust())); - info.GetReturnValue().SetUndefined(); - } + delete BlockInfo; + MakeError(env, "GetAgBlockInfo failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::GetPgBlockInfo) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::GetPgBlockInfo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!node::Buffer::HasInstance(info[0])) { - return Nan::ThrowTypeError("Argument should be a Buffer"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsBuffer); - PS7BlockInfo BlockInfo = new TS7BlockInfo; + PS7BlockInfo BlockInfo = new TS7BlockInfo; + Napi::Buffer buffer = info[0].As>(); - int returnValue = s7client->snap7Client->GetPgBlockInfo( - node::Buffer::Data(info[0].As()), BlockInfo - , static_cast(node::Buffer::Length(info[0].As()))); + int ret = snap7Client->GetPgBlockInfo(buffer.Data(), BlockInfo, buffer.Length()); - if (returnValue == 0) { - v8::Local block_info = s7client->S7BlockInfoToObject(BlockInfo); - delete BlockInfo; - info.GetReturnValue().Set(block_info); - } else { - delete BlockInfo; - info.GetReturnValue().Set(Nan::False()); - } + if (ret == 0) { + Napi::Object block_info = S7BlockInfoToObject(BlockInfo); + delete BlockInfo; + return block_info; + } else { + delete BlockInfo; + MakeError(env, "GetPgBlockInfo failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -v8::Local S7Client::S7BlockInfoToObject(PS7BlockInfo BlockInfo) { - Nan::EscapableHandleScope scope; - - v8::Local block_info = Nan::New(); - Nan::Set(block_info, Nan::New("BlkType").ToLocalChecked() - , Nan::New(BlockInfo->BlkType)); - Nan::Set(block_info, Nan::New("BlkNumber").ToLocalChecked() - , Nan::New(BlockInfo->BlkNumber)); - Nan::Set(block_info, Nan::New("BlkLang").ToLocalChecked() - , Nan::New(BlockInfo->BlkLang)); - Nan::Set(block_info, Nan::New("BlkFlags").ToLocalChecked() - , Nan::New(BlockInfo->BlkFlags)); - Nan::Set(block_info, Nan::New("MC7Size").ToLocalChecked() - , Nan::New(BlockInfo->MC7Size)); - Nan::Set(block_info, Nan::New("LoadSize").ToLocalChecked() - , Nan::New(BlockInfo->LoadSize)); - Nan::Set(block_info, Nan::New("LocalData").ToLocalChecked() - , Nan::New(BlockInfo->LocalData)); - Nan::Set(block_info, Nan::New("SBBLength").ToLocalChecked() - , Nan::New(BlockInfo->SBBLength)); - Nan::Set(block_info, Nan::New("CheckSum").ToLocalChecked() - , Nan::New(BlockInfo->CheckSum)); - Nan::Set(block_info, Nan::New("Version").ToLocalChecked() - , Nan::New(BlockInfo->Version)); - Nan::Set(block_info, Nan::New("CodeDate").ToLocalChecked() - , Nan::New(BlockInfo->CodeDate).ToLocalChecked()); - Nan::Set(block_info, Nan::New("IntfDate").ToLocalChecked() - , Nan::New(BlockInfo->IntfDate).ToLocalChecked()); - Nan::Set(block_info, Nan::New("Author").ToLocalChecked() - , Nan::New(BlockInfo->Author).ToLocalChecked()); - Nan::Set(block_info, Nan::New("Family").ToLocalChecked() - , Nan::New(BlockInfo->Family).ToLocalChecked()); - Nan::Set(block_info, Nan::New("Header").ToLocalChecked() - , Nan::New(BlockInfo->Header).ToLocalChecked()); - - return scope.Escape(block_info); +Napi::Object S7Client::S7BlockInfoToObject(PS7BlockInfo BlockInfo) { + Napi::EscapableHandleScope scope(Env()); + + Napi::Object block_info = Napi::Object::New(Env()); + block_info.Set("BlkType", Napi::Number::New(Env(), BlockInfo->BlkType)); + block_info.Set("BlkNumber", Napi::Number::New(Env(), BlockInfo->BlkNumber)); + block_info.Set("BlkLang", Napi::Number::New(Env(), BlockInfo->BlkLang)); + block_info.Set("BlkFlags", Napi::Number::New(Env(), BlockInfo->BlkFlags)); + block_info.Set("MC7Size", Napi::Number::New(Env(), BlockInfo->MC7Size)); + block_info.Set("LoadSize", Napi::Number::New(Env(), BlockInfo->LoadSize)); + block_info.Set("LocalData", Napi::Number::New(Env(), BlockInfo->LocalData)); + block_info.Set("SBBLength", Napi::Number::New(Env(), BlockInfo->SBBLength)); + block_info.Set("CheckSum", Napi::Number::New(Env(), BlockInfo->CheckSum)); + block_info.Set("Version", Napi::Number::New(Env(), BlockInfo->Version)); + block_info.Set("CodeDate", Napi::String::New(Env(), BlockInfo->CodeDate)); + block_info.Set("IntfDate", Napi::String::New(Env(), BlockInfo->IntfDate)); + block_info.Set("Author", Napi::String::New(Env(), BlockInfo->Author)); + block_info.Set("Family", Napi::String::New(Env(), BlockInfo->Family)); + block_info.Set("Header", Napi::String::New(Env(), BlockInfo->Header)); + + return scope.Escape(block_info).As(); } -NAN_METHOD(S7Client::ListBlocksOfType) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } +Napi::Value S7Client::ListBlocksOfType(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); + + int BlockType = info[0].As().Int32Value(); + int BlockNum = sizeof(TS7BlocksOfType) / sizeof(word); + PS7BlocksOfType BlockList = new TS7BlocksOfType[BlockNum]; + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::LISTBLOCKSOFTYPE, + BlockList, + BlockType, + BlockNum); + worker->Queue(); + return worker->GetPromise(); + } - int BlockNum = sizeof(TS7BlocksOfType) / sizeof(PS7BlocksOfType); - PS7BlocksOfType BlockList = new TS7BlocksOfType[BlockNum]; - if (!info[1]->IsFunction()) { - int returnValue = s7client->snap7Client->ListBlocksOfType( - Nan::To(info[0]).FromJust(), BlockList, &BlockNum); + int ret = snap7Client->ListBlocksOfType(BlockType, BlockList, &BlockNum); + if (ret == 0) { + Napi::Array block_list = S7BlocksOfTypeToArray(BlockList, BlockNum); + delete[] BlockList; + return block_list; + } - if (returnValue == 0) { - v8::Local block_list = s7client->S7BlocksOfTypeToArray( - BlockList, BlockNum); - delete[] BlockList; - info.GetReturnValue().Set(block_list); - } else { - delete[] BlockList; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, LISTBLOCKSOFTYPE - , BlockList, Nan::To(info[0]).FromJust(), BlockNum)); - info.GetReturnValue().SetUndefined(); - } + delete[] BlockList; + MakeError(env, "ListBlocksOfType failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7BlocksOfTypeToArray( - PS7BlocksOfType BlocksList - , int count -) { - Nan::EscapableHandleScope scope; +Napi::Array S7Client::S7BlocksOfTypeToArray(PS7BlocksOfType BlocksList, int count) { + Napi::EscapableHandleScope scope(Env()); - v8::Local block_list = Nan::New(count); - for (int i = 0; i < count; i++) { - Nan::Set(block_list, i, Nan::New((*BlocksList)[i])); - } + Napi::Array block_list = Napi::Array::New(Env(), count); + for (int i = 0; i < count; i++) { + block_list.Set(i, Napi::Number::New(Env(), (*BlocksList)[i])); + } - return scope.Escape(block_list); + return scope.Escape(block_list).As(); } // Blocks functions -NAN_METHOD(S7Client::Upload) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32() || !info[1]->IsInt32() || !info[2]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - char *bufferData = new char[Nan::To(info[2]).FromJust()]; - int size = Nan::To(info[2]).FromJust(); - if (!info[3]->IsFunction()) { - int returnValue = s7client->snap7Client->Upload( - Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), bufferData, &size); - - if (returnValue == 0) { - v8::Local ret_buf; - ret_buf = Nan::NewBuffer( - bufferData - , size - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - info.GetReturnValue().Set(ret_buf); - } else { - delete[] bufferData; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[3].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, UPLOAD - , bufferData, Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), size)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::Upload(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); + + int blockType = info[0].As().Int32Value(); + int blockNum = info[1].As().Int32Value(); + int size = info[2].As().Int32Value(); + char* bufferData = new char[size]; + + if (info.Length() > 3 && info[3].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::UPLOAD, + bufferData, + blockType, + blockNum, + size); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->Upload(blockType, blockNum, bufferData, &size); + if (ret == 0) { + return Napi::Buffer::NewOrCopy(env, bufferData, size, S7Client::FreeCallback); + } + + delete[] bufferData; + MakeError(env, "Upload failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::FullUpload) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32() || !info[1]->IsInt32() || !info[2]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - int size = Nan::To(info[2]).FromJust(); - char *bufferData = new char[size]; - if (!info[3]->IsFunction()) { - int returnValue = s7client->snap7Client->FullUpload( - Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), bufferData, &size); - - if (returnValue == 0) { - v8::Local ret_buf; - ret_buf = Nan::NewBuffer( - bufferData - , size - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - info.GetReturnValue().Set(ret_buf); - } else { - delete[] bufferData; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[3].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, FULLUPLOAD - , bufferData, Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), size)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::FullUpload(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsNumber); + + int blockType = info[0].As().Int32Value(); + int blockNum = info[1].As().Int32Value(); + int size = info[2].As().Int32Value(); + char* bufferData = new char[size]; + + if (info.Length() > 3 && info[3].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::FULLUPLOAD, + bufferData, + blockType, + blockNum, + size); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->FullUpload(blockType, blockNum, bufferData, &size); + if (ret == 0) { + return Napi::Buffer::NewOrCopy(env, bufferData, size, S7Client::FreeCallback); + } + + delete[] bufferData; + MakeError(env, "FullUpload failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::Download) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32() || !node::Buffer::HasInstance(info[1])) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - if (!info[2]->IsFunction()) { - info.GetReturnValue().Set(Nan::New(s7client->snap7Client->Download( - Nan::To(info[0]).FromJust(), node::Buffer::Data(info[1].As()) - , static_cast(node::Buffer::Length(info[1].As()))) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, DOWNLOAD - , node::Buffer::Data(info[1].As()), Nan::To(info[0]).FromJust() - , static_cast(node::Buffer::Length(info[1].As())))); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::Download(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsBuffer); + + int blockNum = info[0].As().Int32Value(); + Napi::Buffer buffer = info[1].As>(); + + if (info.Length() > 2 && info[2].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, + this, + DataIOFunction::DOWNLOAD, + buffer.Data(), + blockNum, + buffer.Length()); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->Download(blockNum, buffer.Data(), buffer.Length()); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "Download failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::Delete) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32() || !info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - if (!info[2]->IsFunction()) { - info.GetReturnValue().Set(Nan::New(s7client->snap7Client->Delete( - Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust()) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, DELETEBLOCK - , Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust())); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::Delete(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + + int blockType = info[0].As().Int32Value(); + int blockNum = info[1].As().Int32Value(); + + if (info.Length() > 2 && info[2].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::DELETEBLOCK, blockType, blockNum); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->Delete(blockType, blockNum); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "Delete failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::DBGet) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - int size = 65536; - char *bufferData = new char[size]; - if (!info[1]->IsFunction()) { - int returnValue = s7client->snap7Client->DBGet( - Nan::To(info[0]).FromJust(), bufferData, &size); - - if (returnValue == 0) { - v8::Local ret_buf; - ret_buf = Nan::NewBuffer( - bufferData - , size - , S7Client::FreeCallback - , NULL).ToLocalChecked(); - info.GetReturnValue().Set(ret_buf); - } else { - delete[] bufferData; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, DBGET - , bufferData, Nan::To(info[0]).FromJust(), size)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::DBGet(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); + + int dbNumber = info[0].As().Int32Value(); + int size = 65536; + char* bufferData = new char[size]; + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::DBGET, bufferData, dbNumber, size); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->DBGet(dbNumber, bufferData, &size); + if (ret == 0) { + return Napi::Buffer::NewOrCopy(env, bufferData, size, S7Client::FreeCallback); + } + + delete[] bufferData; + MakeError(env, "DBGet failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::DBFill) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32() || !(info[1]->IsInt32() || info[1]->IsString())) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - int fill; - if (info[1]->IsInt32()) { - fill = Nan::To(info[1]).FromJust(); - } else { - Nan::Utf8String fillstr(info[1]); - fill = static_cast(**fillstr); - } - - if (!info[2]->IsFunction()) { - info.GetReturnValue().Set(Nan::New(s7client->snap7Client->DBFill( - Nan::To(info[0]).FromJust(), fill) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, DBFILL - , Nan::To(info[0]).FromJust(), fill)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::DBFill(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG_ANY(env, info, 1, IsNumber, IsString); + + int dbNumber = info[0].As().Int32Value(); + int fill; + if (info[1].IsNumber()) { + fill = info[1].As().Int32Value(); + } else { + std::string fillstr = info[1].As().Utf8Value(); + fill = static_cast(*fillstr.c_str()); + } + + if (info.Length() > 2 && info[2].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::DBFILL, dbNumber, fill); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->DBFill(dbNumber, fill); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "DBFill failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } // Date/Time functions -NAN_METHOD(S7Client::GetPlcDateTime) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::GetPlcDateTime(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + tm* DateTime = new tm; + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::GETPLCDATETIME, DateTime); + worker->Queue(); + return worker->GetPromise(); + } - tm *DateTime = new tm; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->GetPlcDateTime(DateTime); - double timestamp = static_cast(mktime(DateTime)); - delete DateTime; + int ret = snap7Client->GetPlcDateTime(DateTime); + if (ret == 0) { + double timestamp = static_cast(mktime(DateTime)); + delete DateTime; + return Napi::Date::New(env, timestamp * 1000); + } - if (returnValue == 0) - info.GetReturnValue().Set(Nan::New(timestamp * 1000).ToLocalChecked()); - else - info.GetReturnValue().Set(Nan::False()); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETPLCDATETIME - , DateTime)); - info.GetReturnValue().SetUndefined(); - } + delete DateTime; + MakeError(env, "GetPlcDateTime failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::SetPlcDateTime) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!(info[0]->IsObject() || info[0]->IsDate())) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - tm *DateTime = new tm; - if (info[0]->IsDate()) { - v8::Local date = v8::Local::Cast(Nan::To(info[0]).ToLocalChecked()); - time_t timestamp = static_cast(Nan::To(date).FromJust() / 1000); - *DateTime = *localtime(×tamp); - } else { - v8::Local date_time = Nan::To(info[0]).ToLocalChecked(); - DateTime->tm_year = Nan::To(Nan::Get(date_time, - Nan::New("year").ToLocalChecked()).ToLocalChecked()).FromJust() - 1900; - DateTime->tm_mon = Nan::To(Nan::Get(date_time, - Nan::New("month").ToLocalChecked()).ToLocalChecked()).FromJust(); - DateTime->tm_mday = Nan::To(Nan::Get(date_time, - Nan::New("day").ToLocalChecked()).ToLocalChecked()).FromJust(); - DateTime->tm_hour = Nan::To(Nan::Get(date_time, - Nan::New("hours").ToLocalChecked()).ToLocalChecked()).FromJust(); - DateTime->tm_min = Nan::To(Nan::Get(date_time, - Nan::New("minutes").ToLocalChecked()).ToLocalChecked()).FromJust(); - DateTime->tm_sec = Nan::To(Nan::Get(date_time, - Nan::New("seconds").ToLocalChecked()).ToLocalChecked()).FromJust(); - } - - if (!info[1]->IsFunction()) { - v8::Local ret = Nan::New( - s7client->snap7Client->SetPlcDateTime(DateTime) == 0); +Napi::Value S7Client::SetPlcDateTime(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG_ANY(env, info, 0, IsObject, IsDate); + + tm* DateTime = new tm; + if (info[0].IsDate()) { + Napi::Date date = info[0].As(); + time_t timestamp = static_cast(date.ValueOf()) / 1000; + *DateTime = *localtime(×tamp); + } else { + Napi::Object date_time = info[0].As(); + DateTime->tm_year = date_time.Get("year").As().DoubleValue() - 1900; + DateTime->tm_mon = date_time.Get("month").As().DoubleValue(); + DateTime->tm_mday = date_time.Get("day").As().DoubleValue(); + DateTime->tm_hour = date_time.Get("hours").As().DoubleValue(); + DateTime->tm_min = date_time.Get("minutes").As().DoubleValue(); + DateTime->tm_sec = date_time.Get("seconds").As().DoubleValue(); + } + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::SETPLCDATETIME, DateTime); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->SetPlcDateTime(DateTime); delete DateTime; - info.GetReturnValue().Set(ret); - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, SETPLCDATETIME - , DateTime)); - info.GetReturnValue().SetUndefined(); - } + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "SetPlcDateTime failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::SetPlcSystemDateTime) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->SetPlcSystemDateTime() == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, SETPLCSYSTEMDATETIME)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::SetPlcSystemDateTime(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::SETPLCSYSTEMDATETIME); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->SetPlcSystemDateTime(); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "SetPlcSystemDateTime failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } // System Info functions -NAN_METHOD(S7Client::GetOrderCode) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::GetOrderCode(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + PS7OrderCode OrderCode = new TS7OrderCode; + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::GETORDERCODE, OrderCode); + worker->Queue(); + return worker->GetPromise(); + } - PS7OrderCode OrderCode = new TS7OrderCode; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->GetOrderCode(OrderCode); + int ret = snap7Client->GetOrderCode(OrderCode); + if (ret == 0) { + Napi::Object order_code = S7OrderCodeToObject(OrderCode); + delete OrderCode; + return order_code; + } - if (returnValue == 0) { - v8::Local order_code = s7client->S7OrderCodeToObject(OrderCode); - delete OrderCode; - info.GetReturnValue().Set(order_code); - } else { - delete OrderCode; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETORDERCODE - , OrderCode)); - info.GetReturnValue().SetUndefined(); - } + delete OrderCode; + MakeError(env, "GetOrderCode failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7OrderCodeToObject(PS7OrderCode OrderCode) { - Nan::EscapableHandleScope scope; +Napi::Object S7Client::S7OrderCodeToObject(PS7OrderCode OrderCode) { + Napi::EscapableHandleScope scope(Env()); - v8::Local order_code = Nan::New(); - Nan::Set(order_code, Nan::New("Code").ToLocalChecked() - , Nan::New(OrderCode->Code).ToLocalChecked()); - Nan::Set(order_code, Nan::New("V1").ToLocalChecked() - , Nan::New(OrderCode->V1)); - Nan::Set(order_code, Nan::New("V2").ToLocalChecked() - , Nan::New(OrderCode->V2)); - Nan::Set(order_code, Nan::New("V3").ToLocalChecked() - , Nan::New(OrderCode->V3)); + Napi::Object order_code = Napi::Object::New(Env()); + order_code.Set("Code", Napi::String::New(Env(), OrderCode->Code)); + order_code.Set("V1", Napi::Number::New(Env(), OrderCode->V1)); + order_code.Set("V2", Napi::Number::New(Env(), OrderCode->V2)); + order_code.Set("V3", Napi::Number::New(Env(), OrderCode->V3)); - return scope.Escape(order_code); + return scope.Escape(order_code).As(); } +Napi::Value S7Client::GetCpuInfo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); -NAN_METHOD(S7Client::GetCpuInfo) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); + PS7CpuInfo CpuInfo = new TS7CpuInfo; + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::GETCPUINFO, CpuInfo); + worker->Queue(); + return worker->GetPromise(); + } - PS7CpuInfo CpuInfo = new TS7CpuInfo; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->GetCpuInfo(CpuInfo); + int ret = snap7Client->GetCpuInfo(CpuInfo); + if (ret == 0) { + Napi::Object cpu_info = S7CpuInfoToObject(CpuInfo); + delete CpuInfo; + return cpu_info; + } - if (returnValue == 0) { - v8::Local cpu_info = s7client->S7CpuInfoToObject(CpuInfo); - delete CpuInfo; - info.GetReturnValue().Set(cpu_info); - } else { - delete CpuInfo; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETCPUINFO, CpuInfo)); - info.GetReturnValue().SetUndefined(); - } + delete CpuInfo; + MakeError(env, "GetCpuInfo failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7CpuInfoToObject(PS7CpuInfo CpuInfo) { - Nan::EscapableHandleScope scope; - - v8::Local cpu_info = Nan::New(); - Nan::Set(cpu_info, Nan::New("ModuleTypeName").ToLocalChecked() - , Nan::New(CpuInfo->ModuleTypeName).ToLocalChecked()); - Nan::Set(cpu_info, Nan::New("SerialNumber").ToLocalChecked() - , Nan::New(CpuInfo->SerialNumber).ToLocalChecked()); - Nan::Set(cpu_info, Nan::New("ASName").ToLocalChecked() - , Nan::New(CpuInfo->ASName).ToLocalChecked()); - Nan::Set(cpu_info, Nan::New("Copyright").ToLocalChecked() - , Nan::New(CpuInfo->Copyright).ToLocalChecked()); - Nan::Set(cpu_info, Nan::New("ModuleName").ToLocalChecked() - , Nan::New(CpuInfo->ModuleName).ToLocalChecked()); - - return scope.Escape(cpu_info); +Napi::Object S7Client::S7CpuInfoToObject(PS7CpuInfo CpuInfo) { + Napi::EscapableHandleScope scope(Env()); + + Napi::Object cpu_info = Napi::Object::New(Env()); + cpu_info.Set("ModuleTypeName", Napi::String::New(Env(), CpuInfo->ModuleTypeName)); + cpu_info.Set("SerialNumber", Napi::String::New(Env(), CpuInfo->SerialNumber)); + cpu_info.Set("ASName", Napi::String::New(Env(), CpuInfo->ASName)); + cpu_info.Set("Copyright", Napi::String::New(Env(), CpuInfo->Copyright)); + cpu_info.Set("ModuleName", Napi::String::New(Env(), CpuInfo->ModuleName)); + + return scope.Escape(cpu_info).As(); } +Napi::Value S7Client::GetCpInfo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); -NAN_METHOD(S7Client::GetCpInfo) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); + PS7CpInfo CpInfo = new TS7CpInfo; + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::GETCPINFO, CpInfo); + worker->Queue(); + return worker->GetPromise(); + } - PS7CpInfo CpInfo = new TS7CpInfo; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->GetCpInfo(CpInfo); + int ret = snap7Client->GetCpInfo(CpInfo); + if (ret == 0) { + Napi::Object cp_info = S7CpInfoToObject(CpInfo); + delete CpInfo; + return cp_info; + } - if (returnValue == 0) { - v8::Local cp_info = s7client->S7CpInfoToObject(CpInfo); - delete CpInfo; - info.GetReturnValue().Set(cp_info); - } else { - delete CpInfo; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETCPINFO, CpInfo)); - info.GetReturnValue().SetUndefined(); - } + delete CpInfo; + MakeError(env, "GetCpInfo failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7CpInfoToObject(PS7CpInfo CpInfo) { - Nan::EscapableHandleScope scope; +Napi::Object S7Client::S7CpInfoToObject(PS7CpInfo CpInfo) { + Napi::EscapableHandleScope scope(Env()); - v8::Local cp_info = Nan::New(); - Nan::Set(cp_info, Nan::New("MaxPduLength").ToLocalChecked() - , Nan::New(CpInfo->MaxPduLengt)); - Nan::Set(cp_info, Nan::New("MaxConnections").ToLocalChecked() - , Nan::New(CpInfo->MaxConnections)); - Nan::Set(cp_info, Nan::New("MaxMpiRate").ToLocalChecked() - , Nan::New(CpInfo->MaxMpiRate)); - Nan::Set(cp_info, Nan::New("MaxBusRate").ToLocalChecked() - , Nan::New(CpInfo->MaxBusRate)); + Napi::Object cp_info = Napi::Object::New(Env()); + cp_info.Set("MaxPduLength", Napi::Number::New(Env(), CpInfo->MaxPduLengt)); + cp_info.Set("MaxConnections", Napi::Number::New(Env(), CpInfo->MaxConnections)); + cp_info.Set("MaxMpiRate", Napi::Number::New(Env(), CpInfo->MaxMpiRate)); + cp_info.Set("MaxBusRate", Napi::Number::New(Env(), CpInfo->MaxBusRate)); - return scope.Escape(cp_info); + return scope.Escape(cp_info).As(); } -NAN_METHOD(S7Client::ReadSZL) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::ReadSZL(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); - if (!(info[0]->IsInt32() || info[1]->IsInt32())) { - return Nan::ThrowTypeError("Wrong arguments"); - } + int id = info[0].As().Int32Value(); + int index = info[1].As().Int32Value(); + PS7SZL SZL = new TS7SZL; + int size = sizeof(TS7SZL); - PS7SZL SZL = new TS7SZL; - int size = sizeof(TS7SZL); - if (!info[2]->IsFunction()) { - int returnValue = s7client->snap7Client->ReadSZL(Nan::To(info[0]).FromJust() - , Nan::To(info[1]).FromJust(), SZL, &size); + if (info.Length() > 2 && info[2].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::READSZL, SZL, id, index, size); + worker->Queue(); + return worker->GetPromise(); + } - if (returnValue == 0) { - v8::Local ret_buf; - ret_buf = Nan::NewBuffer( - reinterpret_cast(SZL) - , size - , S7Client::FreeCallbackSZL - , NULL).ToLocalChecked(); + int ret = snap7Client->ReadSZL(id, index, SZL, &size); + if (ret == 0) { + Napi::Buffer ret_buffer = Napi::Buffer::NewOrCopy(env, + reinterpret_cast(SZL), + size, + S7Client::FreeCallbackSZL); + return ret_buffer; + } - info.GetReturnValue().Set(ret_buf); - } else { - delete SZL; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[2].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, READSZL, SZL - , Nan::To(info[0]).FromJust(), Nan::To(info[1]).FromJust(), size)); - info.GetReturnValue().SetUndefined(); - } + delete SZL; + MakeError(env, "ReadSZL failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::ReadSZLList) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::ReadSZLList(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + PS7SZLList SZLList = new TS7SZLList; + int size = sizeof(TS7SZLList); - PS7SZLList SZLList = new TS7SZLList; - int size = sizeof(TS7SZLList); - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->ReadSZLList(SZLList, &size); + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::READSZLLIST, SZLList, size); + worker->Queue(); + return worker->GetPromise(); + } - if (returnValue == 0) { - v8::Local szl_list = s7client->S7SZLListToArray(SZLList, size); + int ret = snap7Client->ReadSZLList(SZLList, &size); + if (ret == 0) { + Napi::Array szl_list = S7SZLListToArray(SZLList, size); + delete SZLList; + return szl_list; + } - delete SZLList; - info.GetReturnValue().Set(szl_list); - } else { - delete SZLList; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, READSZLLIST, SZLList - , size)); - info.GetReturnValue().SetUndefined(); - } + delete SZLList; + MakeError(env, "ReadSZLList failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7SZLListToArray(PS7SZLList SZLList, int count) { - Nan::EscapableHandleScope scope; +Napi::Array S7Client::S7SZLListToArray(PS7SZLList SZLList, int count) { + Napi::EscapableHandleScope scope(Env()); - v8::Local szl_list = Nan::New(count); - for (int i = 0; i < count; i++) { - Nan::Set(szl_list, i, Nan::New((*SZLList).List[i])); - } + Napi::Array szl_list = Napi::Array::New(Env(), count); + for (int i = 0; i < count; i++) { + szl_list.Set(i, Napi::Number::New(Env(), (*SZLList).List[i])); + } - return scope.Escape(szl_list); + return scope.Escape(szl_list).As(); } // Control functions -NAN_METHOD(S7Client::PlcHotStart) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->PlcHotStart() == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, PLCHOTSTART)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::PlcHotStart(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::PLCHOTSTART); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->PlcHotStart(); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "PlcHotStart failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::PlcColdStart) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->PlcColdStart() == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, PLCCOLDSTART)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::PlcColdStart(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::PLCCOLDSTART); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->PlcColdStart(); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "PlcColdStart failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::PlcStop) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->PlcStop() == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, PLCSTOP)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::PlcStop(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::PLCSTOP); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->PlcStop(); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "PlcStop failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::CopyRamToRom) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - if (!info[1]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->CopyRamToRom(Nan::To(info[0]).FromJust()) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, COPYRAMTOROM - , Nan::To(info[0]).FromJust())); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::CopyRamToRom(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); + + int timeout = info[0].As().Int32Value(); + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::COPYRAMTOROM, timeout); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->CopyRamToRom(timeout); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "CopyRamToRom failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::Compress) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - if (!info[1]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->Compress(Nan::To(info[0]).FromJust()) == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, COMPRESS - , Nan::To(info[0]).FromJust())); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::Compress(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); + + int timeout = info[0].As().Int32Value(); + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::COMPRESS, timeout); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->Compress(timeout); + if (ret == 0) { + return env.Undefined(); + } + + MakeError(env, "Compress failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } // Security functions -NAN_METHOD(S7Client::GetProtection) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - PS7Protection S7Protection = new TS7Protection; - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->GetProtection(S7Protection); - - if (returnValue == 0) { - v8::Local protection = s7client->S7ProtectionToObject( - S7Protection); - delete S7Protection; - info.GetReturnValue().Set(protection); - } else { - delete S7Protection; - info.GetReturnValue().Set(Nan::False()); - } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, GETPROTECTION - , S7Protection)); - info.GetReturnValue().SetUndefined(); - } +Napi::Value S7Client::GetProtection(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + PS7Protection S7Protection = new TS7Protection; + + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::GETPROTECTION, S7Protection); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->GetProtection(S7Protection); + if (ret == 0) { + Napi::Object protection = S7ProtectionToObject(S7Protection); + delete S7Protection; + return protection; + } + + delete S7Protection; + MakeError(env, "GetProtection failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -v8::Local S7Client::S7ProtectionToObject( - PS7Protection S7Protection -) { - Nan::EscapableHandleScope scope; - - v8::Local protection = Nan::New(); - Nan::Set(protection, Nan::New("sch_schal").ToLocalChecked() - , Nan::New(S7Protection->sch_schal)); - Nan::Set(protection, Nan::New("sch_par").ToLocalChecked() - , Nan::New(S7Protection->sch_par)); - Nan::Set(protection, Nan::New("sch_rel").ToLocalChecked() - , Nan::New(S7Protection->sch_rel)); - Nan::Set(protection, Nan::New("bart_sch").ToLocalChecked() - , Nan::New(S7Protection->bart_sch)); - Nan::Set(protection, Nan::New("anl_sch").ToLocalChecked() - , Nan::New(S7Protection->anl_sch)); - - return scope.Escape(protection); +Napi::Object S7Client::S7ProtectionToObject(PS7Protection S7Protection) { + Napi::EscapableHandleScope scope(Env()); + + Napi::Object protection = Napi::Object::New(Env()); + protection.Set("sch_schal", Napi::Number::New(Env(), S7Protection->sch_schal)); + protection.Set("sch_par", Napi::Number::New(Env(), S7Protection->sch_par)); + protection.Set("sch_rel", Napi::Number::New(Env(), S7Protection->sch_rel)); + protection.Set("bart_sch", Napi::Number::New(Env(), S7Protection->bart_sch)); + protection.Set("anl_sch", Napi::Number::New(Env(), S7Protection->anl_sch)); + + return scope.Escape(protection).As(); } -NAN_METHOD(S7Client::SetSessionPassword) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::SetSessionPassword(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsString); - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + std::string* password = new std::string(info[0].As().Utf8Value()); + + if (info.Length() > 1 && info[1].IsFunction()) { + IOWorkerClient* worker = + new IOWorkerClient(env, this, DataIOFunction::SETSESSIONPW, password); + worker->Queue(); + return worker->GetPromise(); + } - Nan::Utf8String *password = new Nan::Utf8String(info[0]); - if (!info[1]->IsFunction()) { - v8::Local ret = Nan::New( - s7client->snap7Client->SetSessionPassword(**password) == 0); + int ret = snap7Client->SetSessionPassword(&*password->begin()); delete password; - info.GetReturnValue().Set(ret); - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, SETSESSIONPW - , password)); - info.GetReturnValue().SetUndefined(); - } -} + if (ret == 0) { + return env.Undefined(); + } -NAN_METHOD(S7Client::ClearSessionPassword) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - if (!info[0]->IsFunction()) { - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->ClearSessionPassword() == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, CLEARSESSIONPW)); - info.GetReturnValue().SetUndefined(); - } + MakeError(env, "SetSessionPassword failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -// Properties -NAN_METHOD(S7Client::ExecTime) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); - - int returnValue = s7client->snap7Client->ExecTime(); - if (returnValue == errLibInvalidObject) { - info.GetReturnValue().Set(Nan::False()); - } else { - info.GetReturnValue().Set(Nan::New(returnValue)); - } -} +Napi::Value S7Client::ClearSessionPassword(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); -NAN_METHOD(S7Client::LastError) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::CLEARSESSIONPW); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->ClearSessionPassword(); + if (ret == 0) { + return env.Undefined(); + } - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->LastError())); + MakeError(env, "ClearSessionPassword failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::PDURequested) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +// Properties +Napi::Value S7Client::ExecTime(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - int returnValue = s7client->snap7Client->PDURequested(); - if (returnValue == 0) { - info.GetReturnValue().Set(Nan::False()); - } else { - info.GetReturnValue().Set(Nan::New(returnValue)); - } + int ret = snap7Client->ExecTime(); + if (ret == errLibInvalidObject) { + MakeError(env, "ExecTime failed", errLibInvalidObject).ThrowAsJavaScriptException(); + return env.Undefined(); + } else { + return Napi::Number::New(env, ret); + } } -NAN_METHOD(S7Client::PDULength) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::PDURequested(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - int returnValue = s7client->snap7Client->PDULength(); - if (returnValue == 0) { - info.GetReturnValue().Set(Nan::False()); - } else { - info.GetReturnValue().Set(Nan::New(returnValue)); - } + int ret = snap7Client->PDURequested(); + if (ret == 0) { + Napi::Error::New(env, "PDURequested failed.").ThrowAsJavaScriptException(); + return env.Undefined(); + } else { + return Napi::Number::New(env, ret); + } } -NAN_METHOD(S7Client::PlcStatus) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::PDULength(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsFunction()) { - int returnValue = s7client->snap7Client->PlcStatus(); - if ((returnValue == S7CpuStatusUnknown) || - (returnValue == S7CpuStatusStop) || - (returnValue == S7CpuStatusRun)) { - info.GetReturnValue().Set(Nan::New(returnValue)); + int ret = snap7Client->PDULength(); + if (ret == 0) { + Napi::Error::New(env, "PDULength failed.").ThrowAsJavaScriptException(); + return env.Undefined(); } else { - info.GetReturnValue().Set(Nan::False()); + return Napi::Number::New(env, ret); } - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorker(callback, s7client, PLCSTATUS)); - info.GetReturnValue().SetUndefined(); - } } -NAN_METHOD(S7Client::Connected) { - S7Client *s7client = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Client::PlcStatus(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - info.GetReturnValue().Set(Nan::New( - s7client->snap7Client->Connected())); + if (info.Length() > 0 && info[0].IsFunction()) { + IOWorkerClient* worker = new IOWorkerClient(env, this, DataIOFunction::PLCSTATUS); + worker->Queue(); + return worker->GetPromise(); + } + + int ret = snap7Client->PlcStatus(); + if ((ret == S7CpuStatusUnknown) || (ret == S7CpuStatusStop) || (ret == S7CpuStatusRun)) { + return Napi::Number::New(env, ret); + } + + MakeError(env, "PlcStatus failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Client::ErrorText) { - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } +Napi::Value S7Client::Connected(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + return Napi::Boolean::New(env, snap7Client->Connected()); +} + +Napi::Value S7Client::ErrorText(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - info.GetReturnValue().Set(Nan::New( - CliErrorText(Nan::To(info[0]).FromJust()).c_str()).ToLocalChecked()); + return Napi::String::New(env, CliErrorText(info[0].As().Int32Value()).c_str()); } -} // namespace node_snap7 +} // namespace node_snap7 diff --git a/src/node_snap7_client.h b/src/node_snap7_client.h index 94ea396..5ac4169 100644 --- a/src/node_snap7_client.h +++ b/src/node_snap7_client.h @@ -1,162 +1,179 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ #ifndef SRC_NODE_SNAP7_CLIENT_H_ #define SRC_NODE_SNAP7_CLIENT_H_ +#include +#include #include -#include -#include namespace node_snap7 { -enum DataIOFunction { READAREA = 1, WRITEAREA, READMULTI, WRITEMULTI - , PLCSTATUS, GETPROTECTION, CLEARSESSIONPW, SETSESSIONPW, PLCSTOP - , PLCCOLDSTART, PLCHOTSTART, GETCPINFO, GETCPUINFO, GETORDERCODE - , SETPLCSYSTEMDATETIME, GETPLCDATETIME, COMPRESS, COPYRAMTOROM - , SETPLCDATETIME, DBFILL, DBGET, DELETEBLOCK, DOWNLOAD, FULLUPLOAD - , UPLOAD, LISTBLOCKSOFTYPE, GETAGBLOCKINFO, LISTBLOCKS, CONNECT - , CONNECTTO, READSZLLIST, READSZL +// clang-format off +enum class DataIOFunction { + READAREA, WRITEAREA, READMULTI, WRITEMULTI, PLCSTATUS, GETPROTECTION, CLEARSESSIONPW, + SETSESSIONPW, PLCSTOP, PLCCOLDSTART, PLCHOTSTART, GETCPINFO, GETCPUINFO, GETORDERCODE, + SETPLCSYSTEMDATETIME, GETPLCDATETIME, COMPRESS, COPYRAMTOROM, SETPLCDATETIME, DBFILL, DBGET, + DELETEBLOCK, DOWNLOAD, FULLUPLOAD, UPLOAD, LISTBLOCKSOFTYPE, GETAGBLOCKINFO, LISTBLOCKS, + CONNECT, CONNECTTO, READSZLLIST, READSZL }; - -class S7Client : public Nan::ObjectWrap { - public: - S7Client(); - static NAN_MODULE_INIT(Init); - static NAN_METHOD(New); - // Control functions - static NAN_METHOD(Connect); - static NAN_METHOD(ConnectTo); - static NAN_METHOD(SetConnectionParams); - static NAN_METHOD(SetConnectionType); - static NAN_METHOD(Disconnect); - static NAN_METHOD(GetParam); - static NAN_METHOD(SetParam); - // Data I/O Main functions - static NAN_METHOD(ReadArea); - static NAN_METHOD(WriteArea); - static NAN_METHOD(ReadMultiVars); - static NAN_METHOD(WriteMultiVars); - // Directory functions - static NAN_METHOD(ListBlocks); - static NAN_METHOD(GetAgBlockInfo); - static NAN_METHOD(GetPgBlockInfo); - static NAN_METHOD(ListBlocksOfType); - // Blocks functions - static NAN_METHOD(Upload); - static NAN_METHOD(FullUpload); - static NAN_METHOD(Download); - static NAN_METHOD(Delete); - static NAN_METHOD(DBGet); - static NAN_METHOD(DBFill); - // Date/Time functions - static NAN_METHOD(GetPlcDateTime); - static NAN_METHOD(SetPlcDateTime); - static NAN_METHOD(SetPlcSystemDateTime); - // System Info functions - static NAN_METHOD(GetOrderCode); - static NAN_METHOD(GetCpuInfo); - static NAN_METHOD(GetCpInfo); - static NAN_METHOD(ReadSZL); - static NAN_METHOD(ReadSZLList); - // Control functions - static NAN_METHOD(PlcHotStart); - static NAN_METHOD(PlcColdStart); - static NAN_METHOD(PlcStop); - static NAN_METHOD(CopyRamToRom); - static NAN_METHOD(Compress); - // Security functions - static NAN_METHOD(GetProtection); - static NAN_METHOD(SetSessionPassword); - static NAN_METHOD(ClearSessionPassword); - // Properties - static NAN_METHOD(ExecTime); - static NAN_METHOD(LastError); - static NAN_METHOD(PDURequested); - static NAN_METHOD(PDULength); - static NAN_METHOD(PlcStatus); - static NAN_METHOD(Connected); - - static NAN_METHOD(ErrorText); - // Internal Helper functions - static int GetByteCountFromWordLen(int WordLen); - v8::Local S7DataItemToArray(PS7DataItem Items, int len - , bool readMulti); - v8::Local S7ProtectionToObject(PS7Protection S7Protection); - v8::Local S7CpInfoToObject(PS7CpInfo CpInfo); - v8::Local S7CpuInfoToObject(PS7CpuInfo CpuInfo); - v8::Local S7OrderCodeToObject(PS7OrderCode OrderCode); - v8::Local S7BlockInfoToObject(PS7BlockInfo BlockInfo); - v8::Local S7BlocksListToObject(PS7BlocksList BlocksList); - v8::Local S7BlocksOfTypeToArray(PS7BlocksOfType BlocksList - , int count); - v8::Local S7SZLListToArray(PS7SZLList SZLList, int count); - - static void FreeCallback(char *data, void* hint); - static void FreeCallbackSZL(char *data, void* hint); - - - uv_mutex_t mutex; - TS7Client *snap7Client; - - private: - ~S7Client(); - static Nan::Persistent constructor; +// clang-format on + +class S7Client : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + explicit S7Client(const Napi::CallbackInfo& info); + ~S7Client(); + + // Internal Helper functions + static int GetByteCountFromWordLen(int WordLen); + Napi::Array S7DataItemToArray(PS7DataItem Items, int len, bool readMulti); + Napi::Object S7ProtectionToObject(PS7Protection S7Protection); + Napi::Object S7CpInfoToObject(PS7CpInfo CpInfo); + Napi::Object S7CpuInfoToObject(PS7CpuInfo CpuInfo); + Napi::Object S7OrderCodeToObject(PS7OrderCode OrderCode); + Napi::Object S7BlockInfoToObject(PS7BlockInfo BlockInfo); + Napi::Object S7BlocksListToObject(PS7BlocksList BlocksList); + Napi::Array S7BlocksOfTypeToArray(PS7BlocksOfType BlocksList, int count); + Napi::Array S7SZLListToArray(PS7SZLList SZLList, int count); + static Napi::Error MakeError(Napi::Env env, const std::string& context, int code); + + static void FreeCallback(Napi::Env env, char* finalizeData); + static void FreeCallbackSZL(Napi::Env env, char* finalizeData); + + std::mutex mutex; + PS7Client snap7Client; + + private: + // Control functions + Napi::Value Connect(const Napi::CallbackInfo& info); + Napi::Value ConnectTo(const Napi::CallbackInfo& info); + Napi::Value SetConnectionParams(const Napi::CallbackInfo& info); + Napi::Value SetConnectionType(const Napi::CallbackInfo& info); + Napi::Value Disconnect(const Napi::CallbackInfo& info); + Napi::Value GetParam(const Napi::CallbackInfo& info); + Napi::Value SetParam(const Napi::CallbackInfo& info); + // Data I/O Main functions + Napi::Value ReadArea(const Napi::CallbackInfo& info); + Napi::Value WriteArea(const Napi::CallbackInfo& info); + Napi::Value ReadMultiVars(const Napi::CallbackInfo& info); + Napi::Value WriteMultiVars(const Napi::CallbackInfo& info); + // Directory functions + Napi::Value ListBlocks(const Napi::CallbackInfo& info); + Napi::Value GetAgBlockInfo(const Napi::CallbackInfo& info); + Napi::Value GetPgBlockInfo(const Napi::CallbackInfo& info); + Napi::Value ListBlocksOfType(const Napi::CallbackInfo& info); + // Blocks functions + Napi::Value Upload(const Napi::CallbackInfo& info); + Napi::Value FullUpload(const Napi::CallbackInfo& info); + Napi::Value Download(const Napi::CallbackInfo& info); + Napi::Value Delete(const Napi::CallbackInfo& info); + Napi::Value DBGet(const Napi::CallbackInfo& info); + Napi::Value DBFill(const Napi::CallbackInfo& info); + // Date/Time functions + Napi::Value GetPlcDateTime(const Napi::CallbackInfo& info); + Napi::Value SetPlcDateTime(const Napi::CallbackInfo& info); + Napi::Value SetPlcSystemDateTime(const Napi::CallbackInfo& info); + // System Info functions + Napi::Value GetOrderCode(const Napi::CallbackInfo& info); + Napi::Value GetCpuInfo(const Napi::CallbackInfo& info); + Napi::Value GetCpInfo(const Napi::CallbackInfo& info); + Napi::Value ReadSZL(const Napi::CallbackInfo& info); + Napi::Value ReadSZLList(const Napi::CallbackInfo& info); + // Control functions + Napi::Value PlcHotStart(const Napi::CallbackInfo& info); + Napi::Value PlcColdStart(const Napi::CallbackInfo& info); + Napi::Value PlcStop(const Napi::CallbackInfo& info); + Napi::Value CopyRamToRom(const Napi::CallbackInfo& info); + Napi::Value Compress(const Napi::CallbackInfo& info); + // Security functions + Napi::Value GetProtection(const Napi::CallbackInfo& info); + Napi::Value SetSessionPassword(const Napi::CallbackInfo& info); + Napi::Value ClearSessionPassword(const Napi::CallbackInfo& info); + // Properties + Napi::Value ExecTime(const Napi::CallbackInfo& info); + Napi::Value PDURequested(const Napi::CallbackInfo& info); + Napi::Value PDULength(const Napi::CallbackInfo& info); + Napi::Value PlcStatus(const Napi::CallbackInfo& info); + Napi::Value Connected(const Napi::CallbackInfo& info); + + Napi::Value ErrorText(const Napi::CallbackInfo& info); }; -class IOWorker : public Nan::AsyncWorker { - public: - // No args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) {} - // 1 args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , void *arg1) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , pData(arg1) {} - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , int arg1) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , int1(arg1) {} - // 2 args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , void *arg1, int arg2) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , pData(arg1), int1(arg2) {} - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , int arg1, int arg2) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , int1(arg1), int2(arg2) {} - // 3 args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , void *arg1, int arg2, int arg3) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , pData(arg1), int1(arg2), int2(arg3) {} - // 4 args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , void *arg1, int arg2, int arg3, int arg4) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , pData(arg1), int1(arg2), int2(arg3), int3(arg4) {} - // 6 args - IOWorker(Nan::Callback *callback, S7Client *s7client, DataIOFunction caller - , void *arg1, int arg2, int arg3, int arg4, int arg5, int arg6) - : Nan::AsyncWorker(callback), s7client(s7client), caller(caller) - , pData(arg1), int1(arg2), int2(arg3), int3(arg4), int4(arg5), int5(arg6) {} - - ~IOWorker() {} - - private: - void Execute(); - void HandleOKCallback(); - - S7Client *s7client; - DataIOFunction caller; - void *pData; - int int1, int2, int3, int4, int5, returnValue; +class IOWorkerClient : public Napi::AsyncWorker { + public: + // No args + IOWorkerClient(const Napi::Env& env, S7Client* s7client, DataIOFunction caller) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller) {} + // 1 args + IOWorkerClient(const Napi::Env& env, S7Client* s7client, DataIOFunction caller, void* arg1) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), pData(arg1) {} + IOWorkerClient(const Napi::Env& env, S7Client* s7client, DataIOFunction caller, int arg1) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), int1(arg1) {} + // 2 args + IOWorkerClient( + const Napi::Env& env, S7Client* s7client, DataIOFunction caller, void* arg1, int arg2) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), pData(arg1), int1(arg2) {} + IOWorkerClient( + const Napi::Env& env, S7Client* s7client, DataIOFunction caller, int arg1, int arg2) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), int1(arg1), int2(arg2) {} + // 3 args + IOWorkerClient(const Napi::Env& env, + S7Client* s7client, + DataIOFunction caller, + void* arg1, + int arg2, + int arg3) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), pData(arg1), int1(arg2), int2(arg3) {} + // 4 args + IOWorkerClient(const Napi::Env& env, + S7Client* s7client, + DataIOFunction caller, + void* arg1, + int arg2, + int arg3, + int arg4) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), pData(arg1), int1(arg2), int2(arg3), int3(arg4) {} + // 6 args + IOWorkerClient(const Napi::Env& env, + S7Client* s7client, + DataIOFunction caller, + void* arg1, + int arg2, + int arg3, + int arg4, + int arg5, + int arg6) + : Napi::AsyncWorker(env, "IOWorkerClient"), m_deferred(env), s7client(s7client), + caller(caller), pData(arg1), int1(arg2), int2(arg3), int3(arg4), int4(arg5), int5(arg6) {} + + Napi::Promise GetPromise() { + return m_deferred.Promise(); + } + + protected: + void Execute(); + void OnOK(); + ~IOWorkerClient() {} + + private: + Napi::Promise::Deferred m_deferred; + S7Client* s7client; + DataIOFunction caller; + void* pData = nullptr; + int int1 = 0, int2 = 0, int3 = 0, int4 = 0, int5 = 0, ret = 0; }; -} // namespace node_snap7 +} // namespace node_snap7 -#endif // SRC_NODE_SNAP7_CLIENT_H_ +#endif // SRC_NODE_SNAP7_CLIENT_H_ diff --git a/src/node_snap7_helpers.h b/src/node_snap7_helpers.h new file mode 100644 index 0000000..7ad1ed2 --- /dev/null +++ b/src/node_snap7_helpers.h @@ -0,0 +1,100 @@ +#ifndef SRC_NODE_SNAP7_HELPERS_H_ +#define SRC_NODE_SNAP7_HELPERS_H_ + +#include +#include +#include + +namespace node_snap7 { + +inline std::string ValueTypeName(const Napi::Value& value) { + switch (value.Type()) { + case napi_undefined: + return "undefined"; + case napi_null: + return "null"; + case napi_boolean: + return "boolean"; + case napi_number: + return "number"; + case napi_string: + return "string"; + case napi_symbol: + return "symbol"; + case napi_object: + return "object"; + case napi_function: + return "function"; + case napi_external: + return "external"; + case napi_bigint: + return "bigint"; + default: + return "unknown"; + } +} + +inline void ThrowArgCountError(Napi::Env env, size_t expected, size_t actual) { + std::ostringstream msg; + msg << "Expected at least " << expected << " argument"; + if (expected != 1) msg << "s"; + msg << ", got " << actual; + Napi::TypeError::New(env, msg.str()).ThrowAsJavaScriptException(); +} + +inline void ThrowArgTypeError(Napi::Env env, + const Napi::CallbackInfo& info, + size_t index, + const char* expected) { + std::string actual = (info.Length() > index) ? ValueTypeName(info[index]) : "missing"; + std::ostringstream msg; + msg << "Argument " << (index + 1) << " must be " << expected << ", got " << actual; + Napi::TypeError::New(env, msg.str()).ThrowAsJavaScriptException(); +} + +inline std::string ExpectedNameFrom(const char* methodName) { + std::string name(methodName); + if (name.rfind("Is", 0) == 0 && name.size() > 2) { + name = name.substr(2); + } + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return name; +} + +inline std::string JoinExpected(const std::initializer_list& methods) { + std::ostringstream os; + bool first = true; + for (const char* m : methods) { + if (!first) { + os << " or "; + } + os << ExpectedNameFrom(m); + first = false; + } + return os.str(); +} + +#define REQUIRE_MIN_ARGS(env, info, n) \ + if ((info).Length() < (n)) { \ + ThrowArgCountError((env), (n), (info).Length()); \ + return (env).Undefined(); \ + } + +#define REQUIRE_ARG(env, info, idx, method) \ + if ((info).Length() <= (idx) || !(info)[(idx)].method()) { \ + ThrowArgTypeError((env), (info), (idx), ExpectedNameFrom(#method).c_str()); \ + return (env).Undefined(); \ + } + +#define REQUIRE_ARG_ANY(env, info, idx, m1, m2) \ + if ((info).Length() <= (idx) || (!((info)[(idx)].m1() || (info)[(idx)].m2()))) { \ + auto _expected = JoinExpected({#m1, #m2}); \ + ThrowArgTypeError((env), (info), (idx), _expected.c_str()); \ + return (env).Undefined(); \ + } + +} // namespace node_snap7 + +#endif // SRC_NODE_SNAP7_HELPERS_H_ diff --git a/src/node_snap7_server.cpp b/src/node_snap7_server.cpp index 56ac3e2..3daf8cb 100644 --- a/src/node_snap7_server.cpp +++ b/src/node_snap7_server.cpp @@ -1,1357 +1,860 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ +#include #include namespace node_snap7 { -static uv_async_t event_async_g; -static uv_async_t rw_async_g; -static uv_mutex_t mutex_rw; -static uv_mutex_t mutex_event; -static uv_sem_t sem_rw; -static std::deque event_list_g; - -static struct rw_event_baton_t { - int Sender; - int Operation; - TS7Tag Tag; - void *pUsrData; -} rw_event_baton_g; - -void S7API EventCallBack(void *usrPtr, PSrvEvent PEvent, int Size) { - uv_mutex_lock(&mutex_event); - event_list_g.push_back(*PEvent); - uv_mutex_unlock(&mutex_event); - - uv_async_send(&event_async_g); -} - -int S7API RWAreaCallBack(void *usrPtr, int Sender, int Operation, PS7Tag PTag - , void *pUsrData -) { - uv_mutex_lock(&mutex_rw); - rw_event_baton_g.Sender = Sender; - rw_event_baton_g.Operation = Operation; - rw_event_baton_g.Tag = *PTag; - rw_event_baton_g.pUsrData = pUsrData; - - uv_async_send(&rw_async_g); - - uv_sem_wait(&sem_rw); - uv_mutex_unlock(&mutex_rw); +Napi::Object S7Server::Init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + + Napi::Function func = DefineClass( + env, + "S7Server", + {// Setup the prototype + InstanceMethod("Start", &S7Server::Start), + InstanceMethod("StartTo", &S7Server::StartTo), + InstanceMethod("Stop", &S7Server::Stop), + InstanceMethod("SetParam", &S7Server::SetParam), + InstanceMethod("GetParam", &S7Server::GetParam), + InstanceMethod("SetResourceless", &S7Server::SetResourceless), + InstanceMethod("RegisterArea", &S7Server::RegisterArea), + InstanceMethod("UnregisterArea", &S7Server::UnregisterArea), + InstanceMethod("LockArea", &S7Server::LockArea), + InstanceMethod("UnlockArea", &S7Server::UnlockArea), + InstanceMethod("SetArea", &S7Server::SetArea), + InstanceMethod("GetArea", &S7Server::GetArea), + InstanceMethod("SetEventsMask", &S7Server::SetEventsMask), + InstanceMethod("GetEventsMask", &S7Server::GetEventsMask), + InstanceMethod("ErrorText", &S7Server::ErrorText), + InstanceMethod("EventText", &S7Server::EventText), + InstanceMethod("ServerStatus", &S7Server::ServerStatus), + InstanceMethod("ClientsCount", &S7Server::ClientsCount), + InstanceMethod("GetCpuStatus", &S7Server::GetCpuStatus), + InstanceMethod("SetCpuStatus", &S7Server::SetCpuStatus), + + // Error codes + InstanceValue("errSrvCannotStart", Napi::Value::From(env, errSrvCannotStart)), + InstanceValue("errSrvDBNullPointer", Napi::Value::From(env, errSrvDBNullPointer)), + InstanceValue("errSrvAreaAlreadyExists", Napi::Value::From(env, errSrvAreaAlreadyExists)), + InstanceValue("errSrvUnknownArea", Napi::Value::From(env, errSrvUnknownArea)), + InstanceValue("errSrvInvalidParams", Napi::Value::From(env, errSrvInvalidParams)), + InstanceValue("errSrvTooManyDB", Napi::Value::From(env, errSrvTooManyDB)), + InstanceValue("errSrvInvalidParamNumber", + Napi::Value::From(env, errSrvInvalidParamNumber)), + InstanceValue("errSrvCannotChangeParam", Napi::Value::From(env, errSrvCannotChangeParam)), + + // Server area IDs + InstanceValue("srvAreaPE", Napi::Value::From(env, srvAreaPE)), + InstanceValue("srvAreaPA", Napi::Value::From(env, srvAreaPA)), + InstanceValue("srvAreaMK", Napi::Value::From(env, srvAreaMK)), + InstanceValue("srvAreaCT", Napi::Value::From(env, srvAreaCT)), + InstanceValue("srvAreaTM", Napi::Value::From(env, srvAreaTM)), + InstanceValue("srvAreaDB", Napi::Value::From(env, srvAreaDB)), + InstanceValue("operationWrite", Napi::Value::From(env, OperationWrite)), + InstanceValue("operationRead", Napi::Value::From(env, OperationRead)), + + // TCP server event codes + InstanceValue("evcServerStarted", Napi::Value::From(env, evcServerStarted)), + InstanceValue("evcServerStopped", Napi::Value::From(env, evcServerStopped)), + InstanceValue("evcListenerCannotStart", Napi::Value::From(env, evcListenerCannotStart)), + InstanceValue("evcClientAdded", Napi::Value::From(env, evcClientAdded)), + InstanceValue("evcClientRejected", Napi::Value::From(env, evcClientRejected)), + InstanceValue("evcClientNoRoom", Napi::Value::From(env, evcClientNoRoom)), + InstanceValue("evcClientException", Napi::Value::From(env, evcClientException)), + InstanceValue("evcClientDisconnected", Napi::Value::From(env, evcClientDisconnected)), + InstanceValue("evcClientTerminated", Napi::Value::From(env, evcClientTerminated)), + InstanceValue("evcClientsDropped", Napi::Value::From(env, evcClientsDropped)), + + // S7 server event codes + InstanceValue("evcPDUincoming", Napi::Value::From(env, evcPDUincoming)), + InstanceValue("evcDataRead", Napi::Value::From(env, evcDataRead)), + InstanceValue("evcDataWrite", Napi::Value::From(env, evcDataWrite)), + InstanceValue("evcNegotiatePDU", Napi::Value::From(env, evcNegotiatePDU)), + InstanceValue("evcReadSZL", Napi::Value::From(env, evcReadSZL)), + InstanceValue("evcClock", Napi::Value::From(env, evcClock)), + InstanceValue("evcUpload", Napi::Value::From(env, evcUpload)), + InstanceValue("evcDownload", Napi::Value::From(env, evcDownload)), + InstanceValue("evcDirectory", Napi::Value::From(env, evcDirectory)), + InstanceValue("evcSecurity", Napi::Value::From(env, evcSecurity)), + InstanceValue("evcControl", Napi::Value::From(env, evcControl)), + + // Masks to enable/disable all events + InstanceValue("evcAll", Napi::Value::From(env, evcAll)), + InstanceValue("evcNone", Napi::Value::From(env, evcNone)), + + // Event subcodes + InstanceValue("evsUnknown", Napi::Value::From(env, evsUnknown)), + InstanceValue("evsStartUpload", Napi::Value::From(env, evsStartUpload)), + InstanceValue("evsStartDownload", Napi::Value::From(env, evsStartDownload)), + InstanceValue("evsGetBlockList", Napi::Value::From(env, evsGetBlockList)), + InstanceValue("evsStartListBoT", Napi::Value::From(env, evsStartListBoT)), + InstanceValue("evsListBoT", Napi::Value::From(env, evsListBoT)), + InstanceValue("evsGetBlockInfo", Napi::Value::From(env, evsGetBlockInfo)), + InstanceValue("evsGetClock", Napi::Value::From(env, evsGetClock)), + InstanceValue("evsSetClock", Napi::Value::From(env, evsSetClock)), + InstanceValue("evsSetPassword", Napi::Value::From(env, evsSetPassword)), + InstanceValue("evsClrPassword", Napi::Value::From(env, evsClrPassword)), + + // Event params : functions group + InstanceValue("grProgrammer", Napi::Value::From(env, grProgrammer)), + InstanceValue("grCyclicData", Napi::Value::From(env, grCyclicData)), + InstanceValue("grBlocksInfo", Napi::Value::From(env, grBlocksInfo)), + InstanceValue("grSZL", Napi::Value::From(env, grSZL)), + InstanceValue("grPassword", Napi::Value::From(env, grPassword)), + InstanceValue("grBSend", Napi::Value::From(env, grBSend)), + InstanceValue("grClock", Napi::Value::From(env, grClock)), + InstanceValue("grSecurity", Napi::Value::From(env, grSecurity)), + + // Event params : control codes + InstanceValue("CodeControlUnknown", Napi::Value::From(env, CodeControlUnknown)), + InstanceValue("CodeControlColdStart", Napi::Value::From(env, CodeControlColdStart)), + InstanceValue("CodeControlWarmStart", Napi::Value::From(env, CodeControlWarmStart)), + InstanceValue("CodeControlStop", Napi::Value::From(env, CodeControlStop)), + InstanceValue("CodeControlCompress", Napi::Value::From(env, CodeControlCompress)), + InstanceValue("CodeControlCpyRamRom", Napi::Value::From(env, CodeControlCpyRamRom)), + InstanceValue("CodeControlInsDel", Napi::Value::From(env, CodeControlInsDel)), + + // Event results + InstanceValue("evrNoError", Napi::Value::From(env, evrNoError)), + InstanceValue("evrFragmentRejected", Napi::Value::From(env, evrFragmentRejected)), + InstanceValue("evrMalformedPDU", Napi::Value::From(env, evrMalformedPDU)), + InstanceValue("evrSparseBytes", Napi::Value::From(env, evrSparseBytes)), + InstanceValue("evrCannotHandlePDU", Napi::Value::From(env, evrCannotHandlePDU)), + InstanceValue("evrNotImplemented", Napi::Value::From(env, evrNotImplemented)), + InstanceValue("evrErrException", Napi::Value::From(env, evrErrException)), + InstanceValue("evrErrAreaNotFound", Napi::Value::From(env, evrErrAreaNotFound)), + InstanceValue("evrErrOutOfRange", Napi::Value::From(env, evrErrOutOfRange)), + InstanceValue("evrErrOverPDU", Napi::Value::From(env, evrErrOverPDU)), + InstanceValue("evrErrTransportSize", Napi::Value::From(env, evrErrTransportSize)), + InstanceValue("evrInvalidGroupUData", Napi::Value::From(env, evrInvalidGroupUData)), + InstanceValue("evrInvalidSZL", Napi::Value::From(env, evrInvalidSZL)), + InstanceValue("evrDataSizeMismatch", Napi::Value::From(env, evrDataSizeMismatch)), + InstanceValue("evrCannotUpload", Napi::Value::From(env, evrCannotUpload)), + InstanceValue("evrCannotDownload", Napi::Value::From(env, evrCannotDownload)), + InstanceValue("evrUploadInvalidID", Napi::Value::From(env, evrUploadInvalidID)), + InstanceValue("evrResNotFound", Napi::Value::From(env, evrResNotFound)), + + // Server parameter + InstanceValue("LocalPort", Napi::Value::From(env, p_u16_LocalPort)), + InstanceValue("WorkInterval", Napi::Value::From(env, p_i32_WorkInterval)), + InstanceValue("PDURequest", Napi::Value::From(env, p_i32_PDURequest)), + InstanceValue("MaxClients", Napi::Value::From(env, p_i32_MaxClients)), + + // CPU status codes + InstanceValue("S7CpuStatusUnknown", Napi::Value::From(env, S7CpuStatusUnknown)), + InstanceValue("S7CpuStatusRun", Napi::Value::From(env, S7CpuStatusRun)), + InstanceValue("S7CpuStatusStop", Napi::Value::From(env, S7CpuStatusStop)), + + // Server status codes + InstanceValue("SrvStopped", Napi::Value::From(env, 0)), + InstanceValue("SrvRunning", Napi::Value::From(env, 1)), + InstanceValue("SrvError", Napi::Value::From(env, 2))}); + + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); + + exports.Set("S7Server", func); + return exports; +} + +void S7API S7Server::EventCallBack(void* usrPtr, PSrvEvent PEvent, int Size) { + TSFN& tsfn = static_cast(usrPtr)->tsfn; + PSrvEvent data = new TSrvEvent(); + memcpy(data, PEvent, Size); + + tsfn.Acquire(); + tsfn.BlockingCall(data); + tsfn.Release(); +} + +int S7API +S7Server::RWAreaCallBack(void* usrPtr, int Sender, int Operation, PS7Tag PTag, void* pUsrData) { + S7Server* s7server = static_cast(usrPtr); + TSFNRW& tsfnrw = s7server->tsfnrw; + + PRWEvent RWEvent = new TRWEvent(); + RWEvent->Sender = Sender; + RWEvent->Operation = Operation; + RWEvent->PTag = new TS7Tag(); + memcpy(RWEvent->PTag, PTag, sizeof(TS7Tag)); + RWEvent->pUsrData = pUsrData; + RWEvent->s7server = s7server; + + tsfnrw.Acquire(); + tsfnrw.BlockingCall(RWEvent); + s7server->sem_rw.acquire(); + tsfnrw.Release(); - return 0; + return 0; } -Nan::Persistent S7Server::constructor; - -NAN_MODULE_INIT(S7Server::Init) { - Nan::HandleScope scope; - - v8::Local tpl; - tpl = Nan::New(S7Server::New); - - v8::Local name = Nan::New("S7Server") - .ToLocalChecked(); - - tpl->SetClassName(name); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - // Setup the prototype - Nan::SetPrototypeMethod( - tpl - , "Start" - , S7Server::Start); - Nan::SetPrototypeMethod( - tpl - , "StartTo" - , S7Server::StartTo); - Nan::SetPrototypeMethod( - tpl - , "Stop" - , S7Server::Stop); - Nan::SetPrototypeMethod( - tpl - , "SetParam" - , S7Server::SetParam); - Nan::SetPrototypeMethod( - tpl - , "GetParam" - , S7Server::GetParam); - Nan::SetPrototypeMethod( - tpl - , "SetResourceless" - , S7Server::SetResourceless); - Nan::SetPrototypeMethod( - tpl - , "RegisterArea" - , S7Server::RegisterArea); - Nan::SetPrototypeMethod( - tpl - , "UnregisterArea" - , S7Server::UnregisterArea); - Nan::SetPrototypeMethod( - tpl - , "LockArea" - , S7Server::LockArea); - Nan::SetPrototypeMethod( - tpl - , "UnlockArea" - , S7Server::UnlockArea); - Nan::SetPrototypeMethod( - tpl - , "SetArea" - , S7Server::SetArea); - Nan::SetPrototypeMethod( - tpl - , "GetArea" - , S7Server::GetArea); - Nan::SetPrototypeMethod( - tpl - , "SetEventMask" - , S7Server::SetEventsMask); - Nan::SetPrototypeMethod( - tpl - , "GetEventsMask" - , S7Server::GetEventsMask); - Nan::SetPrototypeMethod( - tpl - , "ErrorText" - , S7Server::ErrorText); - Nan::SetPrototypeMethod( - tpl - , "LastError" - , S7Server::LastError); - Nan::SetPrototypeMethod( - tpl - , "EventText" - , S7Server::EventText); - Nan::SetPrototypeMethod( - tpl - , "ServerStatus" - , S7Server::ServerStatus); - Nan::SetPrototypeMethod( - tpl - , "ClientsCount" - , S7Server::ClientsCount); - Nan::SetPrototypeMethod( - tpl - , "GetCpuStatus" - , S7Server::GetCpuStatus); - Nan::SetPrototypeMethod( - tpl - , "SetCpuStatus" - , S7Server::SetCpuStatus); - - // Error codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvCannotStart").ToLocalChecked() - , Nan::New(errSrvCannotStart) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvDBNullPointer").ToLocalChecked() - , Nan::New(errSrvDBNullPointer) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvAreaAlreadyExists").ToLocalChecked() - , Nan::New(errSrvAreaAlreadyExists) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvUnknownArea").ToLocalChecked() - , Nan::New(errSrvUnknownArea) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvInvalidParams").ToLocalChecked() - , Nan::New(errSrvInvalidParams) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvTooManyDB").ToLocalChecked() - , Nan::New(errSrvTooManyDB) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvInvalidParamNumber").ToLocalChecked() - , Nan::New(errSrvInvalidParamNumber) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("errSrvCannotChangeParam").ToLocalChecked() - , Nan::New(errSrvCannotChangeParam) - , v8::ReadOnly); - - // Server area IDs - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaPE").ToLocalChecked() - , Nan::New(srvAreaPE) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaPA").ToLocalChecked() - , Nan::New(srvAreaPA) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaMK").ToLocalChecked() - , Nan::New(srvAreaMK) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaCT").ToLocalChecked() - , Nan::New(srvAreaCT) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaTM").ToLocalChecked() - , Nan::New(srvAreaTM) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("srvAreaDB").ToLocalChecked() - , Nan::New(srvAreaDB) - , v8::ReadOnly); - - Nan::SetPrototypeTemplate( - tpl - , Nan::New("operationWrite").ToLocalChecked() - , Nan::New(OperationWrite) - , v8::ReadOnly); - - Nan::SetPrototypeTemplate( - tpl - , Nan::New("operationRead").ToLocalChecked() - , Nan::New(OperationRead) - , v8::ReadOnly); - - // TCP server event codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcServerStarted").ToLocalChecked() - , Nan::New(evcServerStarted) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcServerStopped").ToLocalChecked() - , Nan::New(evcServerStopped) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcListenerCannotStart").ToLocalChecked() - , Nan::New(evcListenerCannotStart) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientAdded").ToLocalChecked() - , Nan::New(evcClientAdded) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientRejected").ToLocalChecked() - , Nan::New(evcClientRejected) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientNoRoom").ToLocalChecked() - , Nan::New(evcClientNoRoom) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientException").ToLocalChecked() - , Nan::New(evcClientException) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientDisconnected").ToLocalChecked() - , Nan::New(evcClientDisconnected) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientTerminated").ToLocalChecked() - , Nan::New(evcClientTerminated) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClientsDropped").ToLocalChecked() - , Nan::New(evcClientsDropped) - , v8::ReadOnly); - - // S7 server event codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcPDUincoming").ToLocalChecked() - , Nan::New(evcPDUincoming) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcDataRead").ToLocalChecked() - , Nan::New(evcDataRead) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcDataWrite").ToLocalChecked() - , Nan::New(evcDataWrite) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcNegotiatePDU").ToLocalChecked() - , Nan::New(evcNegotiatePDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcReadSZL").ToLocalChecked() - , Nan::New(evcReadSZL) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcClock").ToLocalChecked() - , Nan::New(evcClock) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcUpload").ToLocalChecked() - , Nan::New(evcUpload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcDownload").ToLocalChecked() - , Nan::New(evcDownload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcDirectory").ToLocalChecked() - , Nan::New(evcDirectory) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcSecurity").ToLocalChecked() - , Nan::New(evcSecurity) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcControl").ToLocalChecked() - , Nan::New(evcControl) - , v8::ReadOnly); - - // Masks to enable/disable all events - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcAll").ToLocalChecked() - , Nan::New(evcAll) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evcNone").ToLocalChecked() - , Nan::New(evcNone) - , v8::ReadOnly); - - // Event subcodes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsUnknown").ToLocalChecked() - , Nan::New(evsUnknown) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsStartUpload").ToLocalChecked() - , Nan::New(evsStartUpload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsStartDownload").ToLocalChecked() - , Nan::New(evsStartDownload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsGetBlockList").ToLocalChecked() - , Nan::New(evsGetBlockList) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsStartListBoT").ToLocalChecked() - , Nan::New(evsStartListBoT) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsListBoT").ToLocalChecked() - , Nan::New(evsListBoT) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsGetBlockInfo").ToLocalChecked() - , Nan::New(evsGetBlockInfo) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsGetClock").ToLocalChecked() - , Nan::New(evsGetClock) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsSetClock").ToLocalChecked() - , Nan::New(evsSetClock) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsSetPassword").ToLocalChecked() - , Nan::New(evsSetPassword) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evsClrPassword").ToLocalChecked() - , Nan::New(evsClrPassword) - , v8::ReadOnly); - - // Event params : functions group - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grProgrammer").ToLocalChecked() - , Nan::New(grProgrammer) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grCyclicData").ToLocalChecked() - , Nan::New(grCyclicData) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grBlocksInfo").ToLocalChecked() - , Nan::New(grBlocksInfo) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grSZL").ToLocalChecked() - , Nan::New(grSZL) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grPassword").ToLocalChecked() - , Nan::New(grPassword) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grBSend").ToLocalChecked() - , Nan::New(grBSend) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grClock").ToLocalChecked() - , Nan::New(grClock) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("grSecurity").ToLocalChecked() - , Nan::New(grSecurity) - , v8::ReadOnly); - - // Event params : control codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlUnknown").ToLocalChecked() - , Nan::New(CodeControlUnknown) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlColdStart").ToLocalChecked() - , Nan::New(CodeControlColdStart) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlWarmStart").ToLocalChecked() - , Nan::New(CodeControlWarmStart) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlStop").ToLocalChecked() - , Nan::New(CodeControlStop) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlCompress").ToLocalChecked() - , Nan::New(CodeControlCompress) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlCpyRamRom").ToLocalChecked() - , Nan::New(CodeControlCpyRamRom) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("CodeControlInsDel").ToLocalChecked() - , Nan::New(CodeControlInsDel) - , v8::ReadOnly); - - // Event results - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrNoError").ToLocalChecked() - , Nan::New(evrNoError) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrFragmentRejected").ToLocalChecked() - , Nan::New(evrFragmentRejected) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrMalformedPDU").ToLocalChecked() - , Nan::New(evrMalformedPDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrSparseBytes").ToLocalChecked() - , Nan::New(evrSparseBytes) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrCannotHandlePDU").ToLocalChecked() - , Nan::New(evrCannotHandlePDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrNotImplemented").ToLocalChecked() - , Nan::New(evrNotImplemented) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrErrException").ToLocalChecked() - , Nan::New(evrErrException) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrErrAreaNotFound").ToLocalChecked() - , Nan::New(evrErrAreaNotFound) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrErrOutOfRange").ToLocalChecked() - , Nan::New(evrErrOutOfRange) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrErrOverPDU").ToLocalChecked() - , Nan::New(evrErrOverPDU) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrErrTransportSize").ToLocalChecked() - , Nan::New(evrErrTransportSize) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrInvalidGroupUData").ToLocalChecked() - , Nan::New(evrInvalidGroupUData) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrInvalidSZL").ToLocalChecked() - , Nan::New(evrInvalidSZL) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrDataSizeMismatch").ToLocalChecked() - , Nan::New(evrDataSizeMismatch) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrCannotUpload").ToLocalChecked() - , Nan::New(evrCannotUpload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrCannotDownload").ToLocalChecked() - , Nan::New(evrCannotDownload) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrUploadInvalidID").ToLocalChecked() - , Nan::New(evrUploadInvalidID) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("evrResNotFound").ToLocalChecked() - , Nan::New(evrResNotFound) - , v8::ReadOnly); - - // Server parameter - Nan::SetPrototypeTemplate( - tpl - , Nan::New("LocalPort").ToLocalChecked() - , Nan::New(p_u16_LocalPort) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("WorkInterval").ToLocalChecked() - , Nan::New(p_i32_WorkInterval) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("PDURequest").ToLocalChecked() - , Nan::New(p_i32_PDURequest) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("MaxClients").ToLocalChecked() - , Nan::New(p_i32_MaxClients) - , v8::ReadOnly); - - // CPU status codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusUnknown").ToLocalChecked() - , Nan::New(S7CpuStatusUnknown) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusRun").ToLocalChecked() - , Nan::New(S7CpuStatusRun) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("S7CpuStatusStop").ToLocalChecked() - , Nan::New(S7CpuStatusStop) - , v8::ReadOnly); - - // Server status codes - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SrvStopped").ToLocalChecked() - , Nan::New(0) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SrvRunning").ToLocalChecked() - , Nan::New(1) - , v8::ReadOnly); - Nan::SetPrototypeTemplate( - tpl - , Nan::New("SrvError").ToLocalChecked() - , Nan::New(2) - , v8::ReadOnly); - - constructor.Reset(tpl); - Nan::Set(target, name, Nan::GetFunction(tpl).ToLocalChecked()); -} +void CallJsEvent(Napi::Env env, Napi::Function callback, Context* context, DataTypeEvent* data) { + if (!callback.IsEmpty()) { + in_addr sin; + sin.s_addr = data->EvtSender; + char addr[INET_ADDRSTRLEN] = {0}; +#ifdef _WIN32 + InetNtopA(AF_INET, &sin, addr, sizeof(addr)); +#else + inet_ntop(AF_INET, &sin, addr, sizeof(addr)); +#endif + double time = static_cast(data->EvtTime * 1000); + + Napi::Object event_obj = Napi::Object::New(env); + event_obj.Set("EvtTime", Napi::Date::New(env, time)); + event_obj.Set("EvtSender", Napi::String::New(env, addr)); + event_obj.Set("EvtCode", Napi::Number::New(env, data->EvtCode)); + event_obj.Set("EvtRetCode", Napi::Number::New(env, data->EvtRetCode)); + event_obj.Set("EvtParam1", Napi::Number::New(env, data->EvtParam1)); + event_obj.Set("EvtParam2", Napi::Number::New(env, data->EvtParam2)); + event_obj.Set("EvtParam3", Napi::Number::New(env, data->EvtParam3)); + event_obj.Set("EvtParam4", Napi::Number::New(env, data->EvtParam4)); + + callback.Call(context->Value(), {Napi::String::New(env, "event"), event_obj}); + } -NAN_METHOD(S7Server::New) { - if (info.IsConstructCall()) { - S7Server *s7Server = new S7Server(info.This()); - - s7Server->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); - } else { - v8::Local constructorHandle; - constructorHandle = Nan::New(constructor); - info.GetReturnValue().Set( - Nan::NewInstance(Nan::GetFunction(constructorHandle).ToLocalChecked()).ToLocalChecked()); - } + if (data != nullptr) { + delete data; + } } -S7Server::S7Server(v8::Local resource) - : async_resource("S7Server:emit", resource) { - lastError = 0; - snap7Server = new TS7Server(); - - event_async_g.data = rw_async_g.data = this; +void CallJsRW(Napi::Env env, Napi::Function callback, Context* context, DataTypeRW* data) { + if (!callback.IsEmpty()) { + in_addr sin; + sin.s_addr = data->Sender; + char addr[INET_ADDRSTRLEN] = {0}; +#ifdef _WIN32 + InetNtopA(AF_INET, &sin, addr, sizeof(addr)); +#else + inet_ntop(AF_INET, &sin, addr, sizeof(addr)); +#endif - uv_async_init(uv_default_loop(), &event_async_g, S7Server::HandleEvent); - uv_async_init(uv_default_loop(), &rw_async_g, S7Server::HandleReadWriteEvent); + Napi::Object rw_tag_obj = Napi::Object::New(env); + rw_tag_obj.Set("Area", Napi::Number::New(env, data->PTag->Area)); + rw_tag_obj.Set("DBNumber", Napi::Number::New(env, data->PTag->DBNumber)); + rw_tag_obj.Set("Start", Napi::Number::New(env, data->PTag->Start)); + rw_tag_obj.Set("Size", Napi::Number::New(env, data->PTag->Size)); + rw_tag_obj.Set("WordLen", Napi::Number::New(env, data->PTag->WordLen)); + + int byteCount; + size_t size; + byteCount = S7Server::GetByteCountFromWordLen(data->PTag->WordLen); + size = byteCount * data->PTag->Size; + int operation = data->Operation; + void* pUsrData = data->pUsrData; + S7Server* s7server = data->s7server; + + Napi::Buffer buffer; + if (operation == OperationWrite) { + // Copy into a Node-managed buffer so JS handlers can't outlive the native storage. + buffer = Napi::Buffer::Copy(env, static_cast(pUsrData), size); + } else { + buffer = Napi::Buffer::New(env, size); + } + + callback.Call( + context->Value(), + {Napi::String::New(env, "readWrite"), + Napi::String::New(env, addr), + Napi::Number::New(env, operation), + rw_tag_obj, + buffer, + Napi::Function::New( + env, + [pUsrData, s7server, size, operation](const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (operation == OperationRead) { + bool copied = false; + size_t actualLength = 0; + + if (info.Length() > 0 && info[0].IsBuffer()) { + Napi::Buffer buf = info[0].As>(); + actualLength = buf.Length(); + if (actualLength >= size) { + memcpy(pUsrData, buf.Data(), size); + copied = true; + } + } + + if (!copied) { + // Surface the misuse but don't throw, to keep the server running. + Napi::Object err = Napi::Object::New(env); + err.Set( + "message", + Napi::String::New(env, + "readWrite callback provided no buffer or one " + "smaller than expected")); + err.Set("operation", Napi::String::New(env, "read")); + err.Set("expectedLength", Napi::Number::New(env, size)); + err.Set("actualLength", Napi::Number::New(env, actualLength)); + + Napi::Value emitVal = s7server->Value().Get("emit"); + if (emitVal.IsFunction()) { + emitVal.As().Call( + s7server->Value(), + {Napi::String::New(env, "error"), err}); + } + } + } + + s7server->sem_rw.release(); + return env.Undefined(); + })}); + } - uv_unref(reinterpret_cast(&event_async_g)); - uv_unref(reinterpret_cast(&rw_async_g)); - uv_mutex_init(&mutex); - uv_mutex_init(&mutex_rw); - uv_mutex_init(&mutex_event); - uv_sem_init(&sem_rw, 0); + if (data->PTag != nullptr) { + delete data->PTag; + } - snap7Server->SetEventsCallback(&EventCallBack, NULL); + if (data != nullptr) { + delete data; + } } -S7Server::~S7Server() { - snap7Server->Stop(); - delete snap7Server; +S7Server::S7Server(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); - constructor.Reset(); + snap7Server = new TS7Server(); - uv_close(reinterpret_cast(&event_async_g), 0); - uv_close(reinterpret_cast(&rw_async_g), 0); - uv_sem_destroy(&sem_rw); - uv_mutex_destroy(&mutex_event); - uv_mutex_destroy(&mutex_rw); - uv_mutex_destroy(&mutex); -} + Napi::Function Emit = info.This().As().Get("emit").As(); + Napi::Reference* TsfnEventCtx = + new Napi::Reference(Napi::Persistent(info.This())); + Napi::Reference* TsfnEventRWCtx = + new Napi::Reference(Napi::Persistent(info.This())); -int S7Server::GetByteCountFromWordLen(int WordLen) { - switch (WordLen) { - case S7WLBit: - case S7WLByte: - return 1; - case S7WLWord: - case S7WLCounter: - case S7WLTimer: - return 2; - case S7WLReal: - case S7WLDWord: - return 4; - default: - return 0; - } -} + tsfn = TSFN::New( + env, // Environment + Emit, // JS function from caller + "TsfnEvent", // Resource name + 0, // Max queue size (0 = unlimited). + 1, // Initial thread count + TsfnEventCtx, + [](Napi::Env, void* finalizeData, Napi::Reference* ctx) { delete ctx; }); -NAN_METHOD(S7Server::RWBufferCallback) { - Nan::HandleScope scope; + tsfnrw = TSFNRW::New( + env, // Environment + Emit, // JS function from caller + "TsfnEventRW", // Resource name + 0, // Max queue size (0 = unlimited). + 1, // Initial thread count + TsfnEventRWCtx, + [](Napi::Env, void* finalizeData, Napi::Reference* ctx) { delete ctx; }); - if (rw_event_baton_g.Operation == OperationRead) { - if (!node::Buffer::HasInstance(info[0])) { - return Nan::ThrowTypeError("Wrong argument"); - } + tsfn.Unref(env); + tsfnrw.Unref(env); - int byteCount, size; - byteCount = S7Server::GetByteCountFromWordLen(rw_event_baton_g.Tag.WordLen); - size = byteCount * rw_event_baton_g.Tag.Size; + snap7Server->SetEventsCallback(reinterpret_cast(&EventCallBack), this); +} - if (node::Buffer::Length(info[0].As()) < size) { - return Nan::ThrowTypeError("Buffer length too small"); +S7Server::~S7Server() { + snap7Server->Stop(); + snap7Server->SetEventsCallback(nullptr, nullptr); + snap7Server->SetRWAreaCallback(nullptr, nullptr); + + for (auto& areaEntry : area2buffer) { + for (auto& bufferEntry : areaEntry.second) { + delete[] bufferEntry.second.pBuffer; + } } + area2buffer.clear(); - memcpy( - rw_event_baton_g.pUsrData - , node::Buffer::Data(info[0].As()) - , size); - } - - uv_sem_post(&sem_rw); + delete snap7Server; } -#if NODE_VERSION_AT_LEAST(0, 11, 13) -void S7Server::HandleEvent(uv_async_t* handle) { -#else -void S7Server::HandleEvent(uv_async_t* handle, int status) { -#endif - Nan::HandleScope scope; - - S7Server *s7server = static_cast(handle->data); - - uv_mutex_lock(&mutex_event); - while (!event_list_g.empty()) { - PSrvEvent Event = &event_list_g.front(); - in_addr sin; - sin.s_addr = Event->EvtSender; - double time = static_cast(Event->EvtTime * 1000); - - v8::Local event_obj = Nan::New(); - Nan::Set(event_obj, Nan::New("EvtTime").ToLocalChecked() - , Nan::New(time).ToLocalChecked()); - Nan::Set(event_obj, Nan::New("EvtSender").ToLocalChecked() - , Nan::New(inet_ntoa(sin)).ToLocalChecked()); - Nan::Set(event_obj, Nan::New("EvtCode").ToLocalChecked() - , Nan::New(Event->EvtCode)); - Nan::Set(event_obj, Nan::New("EvtRetCode").ToLocalChecked() - , Nan::New(Event->EvtRetCode)); - Nan::Set(event_obj, Nan::New("EvtParam1").ToLocalChecked() - , Nan::New(Event->EvtParam1)); - Nan::Set(event_obj, Nan::New("EvtParam2").ToLocalChecked() - , Nan::New(Event->EvtParam2)); - Nan::Set(event_obj, Nan::New("EvtParam3").ToLocalChecked() - , Nan::New(Event->EvtParam3)); - Nan::Set(event_obj, Nan::New("EvtParam4").ToLocalChecked() - , Nan::New(Event->EvtParam4)); - - v8::Local argv[2] = { - Nan::New("event").ToLocalChecked(), - event_obj - }; - - s7server->async_resource.runInAsyncScope(s7server->handle(), "emit", 2, argv); - event_list_g.pop_front(); - } - uv_mutex_unlock(&mutex_event); +int S7Server::GetByteCountFromWordLen(int WordLen) { + switch (WordLen) { + case S7WLBit: + case S7WLByte: + return 1; + case S7WLWord: + case S7WLCounter: + case S7WLTimer: + return 2; + case S7WLReal: + case S7WLDWord: + return 4; + default: + return 0; + } } -#if NODE_VERSION_AT_LEAST(0, 11, 13) -void S7Server::HandleReadWriteEvent(uv_async_t* handle) { -#else -void S7Server::HandleReadWriteEvent(uv_async_t* handle, int status) { -#endif - Nan::HandleScope scope; - - S7Server *s7server = static_cast(handle->data); - in_addr sin; - sin.s_addr = rw_event_baton_g.Sender; - - v8::Local rw_tag_obj = Nan::New(); - Nan::Set(rw_tag_obj, Nan::New("Area").ToLocalChecked() - , Nan::New(rw_event_baton_g.Tag.Area)); - Nan::Set(rw_tag_obj, Nan::New("DBNumber").ToLocalChecked() - , Nan::New(rw_event_baton_g.Tag.DBNumber)); - Nan::Set(rw_tag_obj, Nan::New("Start").ToLocalChecked() - , Nan::New(rw_event_baton_g.Tag.Start)); - Nan::Set(rw_tag_obj, Nan::New("Size").ToLocalChecked() - , Nan::New(rw_event_baton_g.Tag.Size)); - Nan::Set(rw_tag_obj, Nan::New("WordLen").ToLocalChecked() - , Nan::New(rw_event_baton_g.Tag.WordLen)); - - int byteCount, size; - byteCount = S7Server::GetByteCountFromWordLen(rw_event_baton_g.Tag.WordLen); - size = byteCount * rw_event_baton_g.Tag.Size; - - v8::Local buffer; - - if (rw_event_baton_g.Operation == OperationWrite) { - buffer = Nan::CopyBuffer( - static_cast(rw_event_baton_g.pUsrData), - size).ToLocalChecked(); - } else { - buffer = Nan::NewBuffer(size).ToLocalChecked(); - memset(node::Buffer::Data(buffer), 0, size); - } - - v8::Local argv[6] = { - Nan::New("readWrite").ToLocalChecked(), - Nan::New(inet_ntoa(sin)).ToLocalChecked(), - Nan::New(rw_event_baton_g.Operation), - rw_tag_obj, - buffer, - Nan::New(S7Server::RWBufferCallback) - }; - - s7server->async_resource.runInAsyncScope(s7server->handle(), "emit", 6, argv); +Napi::Error S7Server::MakeError(Napi::Env env, const std::string& context, int code) { + std::string text = SrvErrorText(code); + std::ostringstream msg; + msg << context << " (" << code << ")"; + if (!text.empty()) { + msg << ": " << text; + } + Napi::Error err = Napi::Error::New(env, msg.str()); + err.Set("name", Napi::String::New(env, "Snap7Error")); + err.Set("code", Napi::String::New(env, "SNAP7_SERVER_CODE_" + std::to_string(code))); + err.Set("errno", Napi::Number::New(env, code)); + return err; } void IOWorkerServer::Execute() { - uv_mutex_lock(&s7server->mutex); + std::lock_guard lock(s7server->mutex); - switch (caller) { - case STARTTO: - ret = s7server->snap7Server->StartTo( - **static_cast(pData)); + switch (caller) { + case ServerIOFunction::STARTTO: + returnValue = s7server->snap7Server->StartTo(static_cast(pData)->c_str()); + break; - if (ret == 0) { - uv_ref(reinterpret_cast(&event_async_g)); + case ServerIOFunction::START: + returnValue = s7server->snap7Server->Start(); + break; + + case ServerIOFunction::STOP: + returnValue = s7server->snap7Server->Stop(); + break; } - break; +} - case START: - ret = s7server->snap7Server->Start(); +void IOWorkerServer::OnOK() { + switch (caller) { + case ServerIOFunction::STARTTO: + delete static_cast(pData); + break; - if (ret == 0) { - uv_ref(reinterpret_cast(&event_async_g)); + case ServerIOFunction::START: + case ServerIOFunction::STOP: + break; } - break; - - case STOP: - ret = s7server->snap7Server->Stop(); - if (ret == 0) { - uv_unref(reinterpret_cast(&event_async_g)); + if (returnValue == 0) { + m_deferred.Resolve(Env().Null()); + return; } - break; - } - uv_mutex_unlock(&s7server->mutex); + Napi::Error err = S7Server::MakeError(Env(), "Snap7 server operation failed", returnValue); + m_deferred.Reject(err.Value()); } -void IOWorkerServer::HandleOKCallback() { - Nan::HandleScope scope; - - v8::Local argv1[1]; - v8::Local argv2[2]; - - if (ret == 0) { - argv2[0] = argv1[0] = Nan::Null(); - } else { - argv2[0] = argv1[0] = Nan::New(ret); - } - - switch (caller) { - case STARTTO: - delete static_cast(pData); - callback->Call(1, argv1, async_resource); - break; - - case START: - case STOP: - callback->Call(1, argv1, async_resource); - break; - } +Napi::Value S7Server::Start(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + IOWorkerServer* worker = new IOWorkerServer(env, this, ServerIOFunction::START); + worker->Queue(); + + return worker->GetPromise(); } -NAN_METHOD(S7Server::Start) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::StartTo(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsFunction()) { - int ret = s7server->snap7Server->Start(); + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsString); - if (ret == 0) { - uv_ref(reinterpret_cast(&event_async_g)); - } + std::string* address = new std::string(info[0].As().Utf8Value()); + IOWorkerServer* worker = new IOWorkerServer(env, this, ServerIOFunction::STARTTO, address); + worker->Queue(); + + return worker->GetPromise(); +} + +Napi::Value S7Server::Stop(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - s7server->lastError = ret; + IOWorkerServer* worker = new IOWorkerServer(env, this, ServerIOFunction::STOP); + worker->Queue(); - info.GetReturnValue().Set(Nan::New(ret == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorkerServer(callback, s7server, START)); - info.GetReturnValue().SetUndefined(); - } + return worker->GetPromise(); } -NAN_METHOD(S7Server::StartTo) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::SetResourceless(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (info.Length() < 1) { - return Nan::ThrowTypeError("Wrong number of arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsBoolean); - if (!info[0]->IsString()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + bool resourceless = info[0].ToBoolean().Value(); - Nan::Utf8String *address = new Nan::Utf8String(info[0]); - if (!info[1]->IsFunction()) { - int ret = s7server->snap7Server->StartTo(**address); - delete address; + int ret; + if (resourceless) { + ret = snap7Server->SetRWAreaCallback(reinterpret_cast(&RWAreaCallBack), + this); + } else { + ret = snap7Server->SetRWAreaCallback(nullptr, nullptr); + } if (ret == 0) { - uv_ref(reinterpret_cast(&event_async_g)); + return env.Undefined(); } - s7server->lastError = ret; - - info.GetReturnValue().Set(Nan::New(ret == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new IOWorkerServer(callback, s7server, STARTTO - , address)); - info.GetReturnValue().SetUndefined(); - } + MakeError(env, "SetResourceless failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::Stop) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::GetParam(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - if (!info[0]->IsFunction()) { - int ret = s7server->snap7Server->Stop(); + int pData; + int ret = snap7Server->GetParam(info[0].ToNumber().Int32Value(), &pData); if (ret == 0) { - uv_unref(reinterpret_cast(&event_async_g)); + return Napi::Number::New(env, pData); } - s7server->lastError = ret; - - info.GetReturnValue().Set(Nan::New(ret == 0)); - } else { - Nan::Callback *callback = new Nan::Callback(info[0].As()); - Nan::AsyncQueueWorker(new IOWorkerServer(callback, s7server, STOP)); - info.GetReturnValue().SetUndefined(); - } + MakeError(env, "GetParam failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::SetResourceless) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::SetParam(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsBoolean()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); - bool resourceless = Nan::To(info[0]).FromJust(); + int pData = info[1].ToNumber().Int32Value(); + int ret = snap7Server->SetParam(info[0].ToNumber().Int32Value(), &pData); - int ret; - if (resourceless) { - ret = s7server->snap7Server->SetRWAreaCallback(&RWAreaCallBack, NULL); - } else { - ret = s7server->snap7Server->SetRWAreaCallback(NULL, NULL); - } - s7server->lastError = ret; + if (ret == 0) { + return env.Undefined(); + } - info.GetReturnValue().Set(Nan::New(ret == 0)); + MakeError(env, "SetParam failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::GetParam) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::GetEventsMask(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + int ret = snap7Server->GetEventsMask(); - int pData; - int ret = s7server->snap7Server->GetParam(Nan::To(info[0]).FromJust() - , &pData); - s7server->lastError = ret; - - info.GetReturnValue().Set(Nan::New(ret == 0)); + return Napi::Number::New(env, ret); } -NAN_METHOD(S7Server::SetParam) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::SetEventsMask(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!(info[0]->IsInt32() || info[1]->IsInt32())) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int pData = Nan::To(info[1]).FromJust(); - int ret = s7server->snap7Server->SetParam(Nan::To(info[0]).FromJust(), &pData); - s7server->lastError = ret; + snap7Server->SetEventsMask(info[0].ToNumber().Uint32Value()); - info.GetReturnValue().Set(Nan::New(ret == 0)); + return env.Undefined(); } -NAN_METHOD(S7Server::GetEventsMask) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); - - int ret = s7server->snap7Server->GetEventsMask(); +Napi::Value S7Server::RegisterArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - info.GetReturnValue().Set(Nan::New(ret)); -} + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsBuffer); -NAN_METHOD(S7Server::SetEventsMask) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); + int area = info[0].ToNumber().Int32Value(); + int index = 0; - if (!info[0]->IsUint32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + char* pBuffer = info[2].As>().Data(); + size_t len = info[2].As>().Length(); - s7server->snap7Server->SetEventsMask(Nan::To(info[0]).FromJust()); - info.GetReturnValue().SetUndefined(); -} + if (area == srvAreaDB) { + index = info[1].ToNumber().Int32Value(); + } -NAN_METHOD(S7Server::RegisterArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); + word size = static_cast(len); + char* data = new char[size]; + memcpy(data, pBuffer, size); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + int ret = snap7Server->RegisterArea(area, index, data, size); - int index; - char *pBuffer; - size_t len; - int area = Nan::To(info[0]).FromJust(); + if (ret == 0) { + area2buffer[area][index].pBuffer = data; + area2buffer[area][index].size = size; - if (area == srvAreaDB) { - if (!info[1]->IsInt32() || !node::Buffer::HasInstance(info[2])) { - return Nan::ThrowTypeError("Wrong arguments"); + return env.Undefined(); + } else { + delete[] data; } - index = Nan::To(info[1]).FromJust(); - len = node::Buffer::Length(info[2].As()); - pBuffer = node::Buffer::Data(info[2].As()); - } else if (!node::Buffer::HasInstance(info[1])) { - return Nan::ThrowTypeError("Wrong arguments"); - } else { - index = 0; - len = node::Buffer::Length(info[1].As()); - pBuffer = node::Buffer::Data(info[1].As()); - } - - if (len > 0xFFFF) { - return Nan::ThrowRangeError("Max area buffer size is 65535"); - } - - word size = static_cast(len); - char *data = new char[size]; - memcpy(data, pBuffer, size); - - int ret = s7server->snap7Server->RegisterArea(area, index, data, size); - s7server->lastError = ret; - - if (ret == 0) { - s7server->area2buffer[area][index].pBuffer = data; - s7server->area2buffer[area][index].size = size; - } else { - delete[] data; - } - - info.GetReturnValue().Set(Nan::New(ret == 0)); + MakeError(env, "RegisterArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::UnregisterArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::UnregisterArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int index = 0; - int area = Nan::To(info[0]).FromJust(); + int area = info[0].ToNumber().Int32Value(); + int index = 0; - if (area == srvAreaDB) { - if (!info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); + if (!area2buffer.count(area)) { + Napi::Error::New(env, "Unknown area").ThrowAsJavaScriptException(); + return env.Undefined(); } - index = Nan::To(info[1]).FromJust(); - } + if (area == srvAreaDB) { + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 1, IsNumber); - int ret = s7server->snap7Server->UnregisterArea(area, index); - s7server->lastError = ret; + index = info[1].ToNumber().Int32Value(); + if (!area2buffer[area].count(index)) { + Napi::Error::New(env, "DB index not found").ThrowAsJavaScriptException(); + return env.Undefined(); + } + } - if (ret == 0) { - delete[] s7server->area2buffer[area][index].pBuffer; - s7server->area2buffer[area].erase(index); - } + int ret = snap7Server->UnregisterArea(area, index); - info.GetReturnValue().Set(Nan::New(ret == 0)); -} + if (ret == 0) { + delete[] area2buffer[area][index].pBuffer; + area2buffer[area].erase(index); + + if (area2buffer[area].empty()) { + area2buffer.erase(area); + } + } + + if (ret == 0) { + return env.Undefined(); + } -NAN_METHOD(S7Server::SetArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); + MakeError(env, "UnregisterArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); +} - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } +Napi::Value S7Server::SetArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - int area = Nan::To(info[0]).FromJust(); - if (!s7server->area2buffer.count(area)) { - return Nan::ThrowError("Unknown area"); - } + REQUIRE_MIN_ARGS(env, info, 3); + REQUIRE_ARG(env, info, 0, IsNumber); + REQUIRE_ARG(env, info, 1, IsNumber); + REQUIRE_ARG(env, info, 2, IsBuffer); - int index; - char *pBuffer; - size_t len; + int area = info[0].ToNumber().Int32Value(); + int index = 0; - if (area == srvAreaDB) { - if (!info[1]->IsInt32() || !node::Buffer::HasInstance(info[2])) { - return Nan::ThrowTypeError("Wrong arguments"); + if (!area2buffer.count(area)) { + Napi::Error::New(env, "Unknown area").ThrowAsJavaScriptException(); + return env.Undefined(); } - index = Nan::To(info[1]).FromJust(); - if (!s7server->area2buffer[area].count(index)) { - return Nan::ThrowError("DB index not found"); + char* pBuffer = info[2].As>().Data(); + size_t len = info[2].As>().Length(); + + if (area == srvAreaDB) { + index = info[1].ToNumber().Int32Value(); + if (!area2buffer[area].count(index)) { + Napi::Error::New(env, "DB index not found").ThrowAsJavaScriptException(); + return env.Undefined(); + } } - len = node::Buffer::Length(info[2].As()); - pBuffer = node::Buffer::Data(info[2].As()); - } else { - index = 0; - if (node::Buffer::HasInstance(info[1])) { - len = node::Buffer::Length(info[1].As()); - pBuffer = node::Buffer::Data(info[1].As()); - } else if (node::Buffer::HasInstance(info[2])) { - len = node::Buffer::Length(info[2].As()); - pBuffer = node::Buffer::Data(info[2].As()); - } else { - return Nan::ThrowTypeError("Wrong arguments"); + if (len != area2buffer[area][index].size) { + Napi::Error::New(env, "Wrong buffer length").ThrowAsJavaScriptException(); + return env.Undefined(); } - } - if (len != s7server->area2buffer[area][index].size) { - return Nan::ThrowError("Wrong buffer length"); - } + int ret = snap7Server->LockArea(area, index); + if (ret != 0) { + MakeError(env, "SetArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } - s7server->snap7Server->LockArea(area, index); + memcpy(area2buffer[area][index].pBuffer, pBuffer, area2buffer[area][index].size); - memcpy( - s7server->area2buffer[area][index].pBuffer - , pBuffer, s7server->area2buffer[area][index].size); + ret = snap7Server->UnlockArea(area, index); - s7server->snap7Server->UnlockArea(area, index); + if (ret == 0) { + return env.Undefined(); + } - info.GetReturnValue().SetUndefined(); + MakeError(env, "SetArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::GetArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::GetArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int index = 0; - int area = Nan::To(info[0]).FromJust(); + int area = info[0].ToNumber().Int32Value(); + int index = 0; - if (!s7server->area2buffer.count(area)) { - return Nan::ThrowError("Unknown area"); - } + if (!area2buffer.count(area)) { + Napi::Error::New(env, "Unknown area").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + if (area == srvAreaDB) { + REQUIRE_MIN_ARGS(env, info, 2); + REQUIRE_ARG(env, info, 1, IsNumber); - if (area == srvAreaDB) { - if (!info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); + index = info[1].ToNumber().Int32Value(); + if (!area2buffer[area].count(index)) { + Napi::Error::New(env, "DB index not found").ThrowAsJavaScriptException(); + return env.Undefined(); + } } - index = Nan::To(info[1]).FromJust(); - if (!s7server->area2buffer[area].count(index)) { - return Nan::ThrowError("DB index not found"); + int ret = snap7Server->LockArea(area, index); + if (ret != 0) { + MakeError(env, "GetArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } - } - s7server->snap7Server->LockArea(area, index); + Napi::Buffer buffer; + buffer = Napi::Buffer::Copy(env, + area2buffer[area][index].pBuffer, + area2buffer[area][index].size); - v8::Local buffer = Nan::CopyBuffer( - s7server->area2buffer[area][index].pBuffer - , s7server->area2buffer[area][index].size).ToLocalChecked(); + ret = snap7Server->UnlockArea(area, index); - s7server->snap7Server->UnlockArea(area, index); + if (ret == 0) { + return buffer; + } - info.GetReturnValue().Set(buffer); + MakeError(env, "GetArea failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); } -NAN_METHOD(S7Server::LockArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::LockArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int index = 0; - int area = Nan::To(info[0]).FromJust(); + int index = 0; + int area = info[0].ToNumber().Int32Value(); - if (area == srvAreaDB) { - if (!info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + if (area == srvAreaDB) { + REQUIRE_ARG(env, info, 1, IsNumber); - index = Nan::To(info[1]).FromJust(); - } + index = info[1].ToNumber().Int32Value(); + } - int ret = s7server->snap7Server->LockArea(area, index); - s7server->lastError = ret; + int ret = snap7Server->LockArea(area, index); - info.GetReturnValue().Set(Nan::New(ret == 0)); + if (ret != 0) { + MakeError(env, "LockArea failed", ret).ThrowAsJavaScriptException(); + } + return env.Undefined(); } -NAN_METHOD(S7Server::UnlockArea) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::UnlockArea(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int index = 0; - int area = Nan::To(info[0]).FromJust(); + int index = 0; + int area = info[0].ToNumber().Int32Value(); - if (area == srvAreaDB) { - if (!info[1]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + if (area == srvAreaDB) { + REQUIRE_ARG(env, info, 1, IsNumber); - index = Nan::To(info[1]).FromJust(); - } + index = info[1].ToNumber().Int32Value(); + } - int ret = s7server->snap7Server->UnlockArea(area, index); - s7server->lastError = ret; + int ret = snap7Server->UnlockArea(area, index); - info.GetReturnValue().Set(Nan::New(ret == 0)); + if (ret != 0) { + MakeError(env, "UnlockArea failed", ret).ThrowAsJavaScriptException(); + } + return env.Undefined(); } -NAN_METHOD(S7Server::ServerStatus) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); - - int ret = s7server->snap7Server->ServerStatus(); - if ((ret == 0) || (ret == 1) || (ret == 2)) { - s7server->lastError = 0; - info.GetReturnValue().Set(Nan::New(ret)); - } else { - s7server->lastError = ret; - info.GetReturnValue().Set(Nan::False()); - } +Napi::Value S7Server::ServerStatus(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + int ret = snap7Server->ServerStatus(); + if ((ret == 0) || (ret == 1) || (ret == 2)) { + return Napi::Number::New(env, ret); + } else { + MakeError(env, "ServerStatus failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -NAN_METHOD(S7Server::ClientsCount) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::ClientsCount(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - int ret = s7server->snap7Server->ClientsCount(); - info.GetReturnValue().Set(Nan::New(ret)); + int ret = snap7Server->ClientsCount(); + return Napi::Number::New(env, ret); } -NAN_METHOD(S7Server::GetCpuStatus) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); - - int ret = s7server->snap7Server->GetCpuStatus(); - if ((ret == S7CpuStatusUnknown) || - (ret == S7CpuStatusStop) || - (ret == S7CpuStatusRun)) { - s7server->lastError = 0; - info.GetReturnValue().Set(Nan::New(ret)); - } else { - s7server->lastError = ret; - info.GetReturnValue().Set(Nan::False()); - } +Napi::Value S7Server::GetCpuStatus(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + int ret = snap7Server->GetCpuStatus(); + if ((ret == S7CpuStatusUnknown) || (ret == S7CpuStatusStop) || (ret == S7CpuStatusRun)) { + return Napi::Number::New(env, ret); + } else { + MakeError(env, "GetCpuStatus failed", ret).ThrowAsJavaScriptException(); + return env.Undefined(); + } } -NAN_METHOD(S7Server::SetCpuStatus) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::SetCpuStatus(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); - int ret = s7server->snap7Server->SetCpuStatus(Nan::To(info[0]).FromJust()); - s7server->lastError = ret; + int ret = snap7Server->SetCpuStatus(info[0].ToNumber().Int32Value()); - info.GetReturnValue().Set(Nan::New(ret == 0)); + return Napi::Boolean::New(env, ret == 0); } -NAN_METHOD(S7Server::ErrorText) { - if (!info[0]->IsInt32()) { - return Nan::ThrowTypeError("Wrong arguments"); - } +Napi::Value S7Server::ErrorText(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); - info.GetReturnValue().Set(Nan::New( - SrvErrorText(Nan::To(info[0]).FromJust()).c_str()).ToLocalChecked()); -} + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsNumber); -NAN_METHOD(S7Server::EventText) { - TSrvEvent SrvEvent; - - if (!info[0]->IsObject()) { - return Nan::ThrowTypeError("Wrong arguments"); - } - - v8::Local event_obj = v8::Local::Cast(info[0]); - if (!Nan::Has(event_obj, Nan::New("EvtTime").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtSender").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtCode").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtRetCode").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtParam1").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtParam2").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtParam3").ToLocalChecked()).FromJust() || - !Nan::Has(event_obj, Nan::New("EvtParam4").ToLocalChecked()).FromJust()) { - return Nan::ThrowTypeError("Wrong argument structure"); - } - - if (!Nan::Get(event_obj, Nan::New("EvtTime").ToLocalChecked()).ToLocalChecked()->IsDate() || - !Nan::Get(event_obj, Nan::New("EvtSender").ToLocalChecked()).ToLocalChecked()->IsString() || - !Nan::Get(event_obj, Nan::New("EvtCode").ToLocalChecked()).ToLocalChecked()->IsUint32() || - !Nan::Get(event_obj, Nan::New("EvtRetCode").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(event_obj, Nan::New("EvtParam1").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(event_obj, Nan::New("EvtParam2").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(event_obj, Nan::New("EvtParam3").ToLocalChecked()).ToLocalChecked()->IsInt32() || - !Nan::Get(event_obj, Nan::New("EvtParam4").ToLocalChecked()).ToLocalChecked()->IsInt32()) { - return Nan::ThrowTypeError("Wrong argument types"); - } - - Nan::Utf8String *remAddress = new Nan::Utf8String( - Nan::Get( - event_obj - , Nan::New("EvtSender").ToLocalChecked()).ToLocalChecked()); - - SrvEvent.EvtTime = static_cast(Nan::To(v8::Local::Cast(Nan::Get(event_obj, Nan::New("EvtTime").ToLocalChecked()).ToLocalChecked())).FromJust() / 1000); - SrvEvent.EvtSender = inet_addr(**remAddress); - SrvEvent.EvtCode = Nan::To(Nan::Get(event_obj, Nan::New("EvtCode").ToLocalChecked()).ToLocalChecked()).FromJust(); - SrvEvent.EvtRetCode = Nan::To(Nan::Get(event_obj, Nan::New("EvtRetCode").ToLocalChecked()).ToLocalChecked()).FromJust(); - SrvEvent.EvtParam1 = Nan::To(Nan::Get(event_obj, Nan::New("EvtParam1").ToLocalChecked()).ToLocalChecked()).FromJust(); - SrvEvent.EvtParam2 = Nan::To(Nan::Get(event_obj, Nan::New("EvtParam2").ToLocalChecked()).ToLocalChecked()).FromJust(); - SrvEvent.EvtParam3 = Nan::To(Nan::Get(event_obj, Nan::New("EvtParam3").ToLocalChecked()).ToLocalChecked()).FromJust(); - SrvEvent.EvtParam4 = Nan::To(Nan::Get(event_obj, Nan::New("EvtParam4").ToLocalChecked()).ToLocalChecked()).FromJust(); - - delete remAddress; - - info.GetReturnValue().Set(Nan::New( - SrvEventText(&SrvEvent).c_str()).ToLocalChecked()); + return Napi::String::New(env, SrvErrorText(info[0].ToNumber().Int32Value()).c_str()); } -NAN_METHOD(S7Server::LastError) { - S7Server *s7server = ObjectWrap::Unwrap(info.Holder()); +Napi::Value S7Server::EventText(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + TSrvEvent SrvEvent; + + REQUIRE_MIN_ARGS(env, info, 1); + REQUIRE_ARG(env, info, 0, IsObject); + + Napi::Object event_obj = info[0].ToObject(); + if (!event_obj.Has("EvtTime") || !event_obj.Has("EvtSender") || !event_obj.Has("EvtCode") || + !event_obj.Has("EvtRetCode") || !event_obj.Has("EvtParam1") || + !event_obj.Has("EvtParam2") || !event_obj.Has("EvtParam3") || !event_obj.Has("EvtParam4")) { + Napi::TypeError::New(env, "Wrong argument structure").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + if (!event_obj.Get("EvtTime").IsDate() || !event_obj.Get("EvtSender").IsString() || + !event_obj.Get("EvtCode").IsNumber() || !event_obj.Get("EvtRetCode").IsNumber() || + !event_obj.Get("EvtParam1").IsNumber() || !event_obj.Get("EvtParam2").IsNumber() || + !event_obj.Get("EvtParam3").IsNumber() || !event_obj.Get("EvtParam4").IsNumber()) { + Napi::TypeError::New(env, "Wrong argument types").ThrowAsJavaScriptException(); + return env.Undefined(); + } + + Napi::String remAddress = event_obj.Get("EvtSender").ToString(); + + SrvEvent.EvtTime = + static_cast(event_obj.Get("EvtTime").As().ValueOf() / 1000); + SrvEvent.EvtSender = inet_addr(remAddress.Utf8Value().c_str()); + SrvEvent.EvtCode = event_obj.Get("EvtCode").ToNumber().Uint32Value(); + SrvEvent.EvtRetCode = event_obj.Get("EvtRetCode").ToNumber().Uint32Value(); + SrvEvent.EvtParam1 = event_obj.Get("EvtParam1").ToNumber().Uint32Value(); + SrvEvent.EvtParam2 = event_obj.Get("EvtParam2").ToNumber().Uint32Value(); + SrvEvent.EvtParam3 = event_obj.Get("EvtParam3").ToNumber().Uint32Value(); + SrvEvent.EvtParam4 = event_obj.Get("EvtParam4").ToNumber().Uint32Value(); - info.GetReturnValue().Set(Nan::New(s7server->lastError)); + return Napi::String::New(env, SrvEventText(&SrvEvent).c_str()); } -} // namespace node_snap7 +} // namespace node_snap7 diff --git a/src/node_snap7_server.h b/src/node_snap7_server.h index 5de1c77..68ec71e 100644 --- a/src/node_snap7_server.h +++ b/src/node_snap7_server.h @@ -1,108 +1,154 @@ /* - * Copyright (c) 2019, Mathias Küsel + * Copyright (c) 2025, Mathias Küsel * MIT License */ #ifndef SRC_NODE_SNAP7_SERVER_H_ #define SRC_NODE_SNAP7_SERVER_H_ +#include #include -#include -#include -#include + +#ifdef OS_WINDOWS +#include +#include +#endif + +#if defined(PLATFORM_UNIX) || defined(OS_OSX) +#include +#include +#include +#endif + +#include #include -#include +#include namespace node_snap7 { -enum ServerIOFunction { - START = 1, STARTTO, STOP -}; +class S7Server; -typedef struct { - char* pBuffer; - word size; -}TBufferInfo; - -class S7Server : public Nan::ObjectWrap { - public: - explicit S7Server(v8::Local resource); - static NAN_MODULE_INIT(Init); - static NAN_METHOD(New); - - static NAN_METHOD(Start); - static NAN_METHOD(StartTo); - static NAN_METHOD(Stop); - static NAN_METHOD(SetResourceless); - static NAN_METHOD(GetParam); - static NAN_METHOD(SetParam); - static NAN_METHOD(GetEventsMask); - static NAN_METHOD(SetEventsMask); - static NAN_METHOD(RegisterArea); - static NAN_METHOD(UnregisterArea); - static NAN_METHOD(SetArea); - static NAN_METHOD(GetArea); - static NAN_METHOD(LockArea); - static NAN_METHOD(UnlockArea); - static NAN_METHOD(ServerStatus); - static NAN_METHOD(ClientsCount); - static NAN_METHOD(GetCpuStatus); - static NAN_METHOD(SetCpuStatus); - - static NAN_METHOD(ErrorText); - static NAN_METHOD(EventText); - static NAN_METHOD(LastError); - - static int GetByteCountFromWordLen(int WordLen); - -#if NODE_VERSION_AT_LEAST(0, 11, 13) - static void HandleEvent(uv_async_t* handle); -#else - static void HandleEvent(uv_async_t* handle, int status); -#endif +class Semaphore { + public: + explicit Semaphore(int count = 1) : count(count) {} -#if NODE_VERSION_AT_LEAST(0, 11, 13) - static void HandleReadWriteEvent(uv_async_t* handle); -#else - static void HandleReadWriteEvent(uv_async_t* handle, int status); -#endif + void acquire() { + std::unique_lock lock(mtx); + cv.wait(lock, [this]() { return count > 0; }); + --count; + } + + void release() { + std::unique_lock lock(mtx); + ++count; + cv.notify_one(); + } - static NAN_METHOD(RWBufferCallback); + private: + std::mutex mtx; + std::condition_variable cv; + int count; +}; - uv_mutex_t mutex; - TS7Server *snap7Server; - std::map > area2buffer; - int lastError; - Nan::AsyncResource async_resource; +enum class ServerIOFunction { + START, + STARTTO, + STOP +}; - private: - ~S7Server(); - static Nan::Persistent constructor; +typedef struct { + char* pBuffer; + word size; +} TBufferInfo; + +typedef struct { + int Sender; + int Operation; + PS7Tag PTag; + void* pUsrData; + S7Server* s7server; +} TRWEvent, *PRWEvent; + +using Context = Napi::Reference; +using DataTypeEvent = TSrvEvent; +using DataTypeRW = TRWEvent; +void CallJsEvent(Napi::Env env, Napi::Function callback, Context* context, DataTypeEvent* data); +void CallJsRW(Napi::Env env, Napi::Function callback, Context* context, DataTypeRW* data); +using TSFN = Napi::TypedThreadSafeFunction; +using TSFNRW = Napi::TypedThreadSafeFunction; + +class S7Server : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + explicit S7Server(const Napi::CallbackInfo& info); + ~S7Server(); + + Napi::Value Start(const Napi::CallbackInfo& info); + Napi::Value StartTo(const Napi::CallbackInfo& info); + Napi::Value Stop(const Napi::CallbackInfo& info); + Napi::Value SetResourceless(const Napi::CallbackInfo& info); + Napi::Value GetParam(const Napi::CallbackInfo& info); + Napi::Value SetParam(const Napi::CallbackInfo& info); + Napi::Value GetEventsMask(const Napi::CallbackInfo& info); + Napi::Value SetEventsMask(const Napi::CallbackInfo& info); + Napi::Value RegisterArea(const Napi::CallbackInfo& info); + Napi::Value UnregisterArea(const Napi::CallbackInfo& info); + Napi::Value SetArea(const Napi::CallbackInfo& info); + Napi::Value GetArea(const Napi::CallbackInfo& info); + Napi::Value LockArea(const Napi::CallbackInfo& info); + Napi::Value UnlockArea(const Napi::CallbackInfo& info); + Napi::Value ServerStatus(const Napi::CallbackInfo& info); + Napi::Value ClientsCount(const Napi::CallbackInfo& info); + Napi::Value GetCpuStatus(const Napi::CallbackInfo& info); + Napi::Value SetCpuStatus(const Napi::CallbackInfo& info); + static Napi::Error MakeError(Napi::Env env, const std::string& context, int code); + + Napi::Value ErrorText(const Napi::CallbackInfo& info); + Napi::Value EventText(const Napi::CallbackInfo& info); + + static int GetByteCountFromWordLen(int WordLen); + + TS7Server* snap7Server; + std::mutex mutex; + std::map> area2buffer; + Semaphore sem_rw{0}; + + private: + static void S7API EventCallBack(void* usrPtr, PSrvEvent PEvent, int Size); + static int S7API + RWAreaCallBack(void* usrPtr, int Sender, int Operation, PS7Tag PTag, void* pUsrData); + TSFN tsfn; + TSFNRW tsfnrw; }; -class IOWorkerServer : public Nan::AsyncWorker { - public: - // No args - IOWorkerServer(Nan::Callback *callback, S7Server *s7server, ServerIOFunction caller) - : Nan::AsyncWorker(callback), s7server(s7server), caller(caller) {} - // 1 args - IOWorkerServer(Nan::Callback *callback, S7Server *s7server, ServerIOFunction caller - , void *arg1) - : Nan::AsyncWorker(callback), s7server(s7server), caller(caller) - , pData(arg1) {} - - ~IOWorkerServer() {} - - private: - void Execute(); - void HandleOKCallback(); - - S7Server *s7server; - ServerIOFunction caller; - void *pData; - int ret; +class IOWorkerServer : public Napi::AsyncWorker { + public: + // No args + IOWorkerServer(const Napi::Env& env, S7Server* s7server, ServerIOFunction caller) + : Napi::AsyncWorker(env, "IOWorkerServer"), m_deferred(env), s7server(s7server), + caller(caller) {} + // 1 args + IOWorkerServer(const Napi::Env& env, S7Server* s7server, ServerIOFunction caller, void* arg1) + : Napi::AsyncWorker(env, "IOWorkerServer"), m_deferred(env), s7server(s7server), + caller(caller), pData(arg1) {} + + Napi::Promise GetPromise() { + return m_deferred.Promise(); + } + + protected: + void Execute(); + void OnOK(); + ~IOWorkerServer() {} + + private: + Napi::Promise::Deferred m_deferred; + S7Server* s7server; + ServerIOFunction caller; + void* pData = nullptr; + int returnValue = 0; }; -} // namespace node_snap7 +} // namespace node_snap7 -#endif // SRC_NODE_SNAP7_SERVER_H_ +#endif // SRC_NODE_SNAP7_SERVER_H_ diff --git a/test/test-client.js b/test/test-client.js new file mode 100644 index 0000000..24bb77d --- /dev/null +++ b/test/test-client.js @@ -0,0 +1,963 @@ +const snap7 = require('../lib/node-snap7'); +const { test, beforeEach, afterEach } = require('node:test'); +const assert = require('assert'); + +function sleepSync(ms) { + const end = Date.now() + ms; + while (Date.now() < end) { + // Busy-wait + } +} + +let server, client, dynamicPort; +const DB_NUMBER = 1; +const SIZE = 16; +const TM_AMOUNT = 4; +const CT_AMOUNT = 4; +const TM_SIZE = TM_AMOUNT * 2; +const CT_SIZE = CT_AMOUNT * 2; + +const HEADER_SIZE = 40; // TS7CompactBlockInfo +const FOOTER_SIZE = 56; // TS7BlockFooter + +const BLOCK_SIZE = SIZE + HEADER_SIZE + FOOTER_SIZE; +const blockBuf = Buffer.alloc(BLOCK_SIZE); +const FOOTER_OFFSET = HEADER_SIZE + SIZE; + +// --- Header (TS7CompactBlockInfo) --- +// word Cst_pp; // 2 bytes +blockBuf.writeUInt16BE(0x0000, 0); +// byte Uk_01; // 1 byte +blockBuf.writeUInt8(0x00, 2); +// byte BlkFlags; // 1 byte +blockBuf.writeUInt8(0x00, 3); +// byte BlkLang; // 1 byte +blockBuf.writeUInt8(0x00, 4); +// byte SubBlkType; // 1 byte (DB = 0x0A) +blockBuf.writeUInt8(0x0A, 5); +// word BlkNum; // 2 bytes +blockBuf.writeUInt16BE(DB_NUMBER, 6); +// u_int LenLoadMem; // 4 bytes +blockBuf.writeUInt32BE(BLOCK_SIZE, 8); +// u_int BlkSec; // 4 bytes +blockBuf.writeUInt32BE(0, 12); +// u_int CodeTime_ms; // 4 bytes +blockBuf.writeUInt32BE(0, 16); +// word CodeTime_dy; // 2 bytes +blockBuf.writeUInt16BE(0, 20); +// u_int IntfTime_ms; // 4 bytes +blockBuf.writeUInt32BE(0, 22); +// word IntfTime_dy; // 2 bytes +blockBuf.writeUInt16BE(0, 26); +// word SbbLen; // 2 bytes +blockBuf.writeUInt16BE(0, 28); +// word AddLen; // 2 bytes +blockBuf.writeUInt16BE(0, 30); +// word LocDataLen; // 2 bytes +blockBuf.writeUInt16BE(0, 32); +// word MC7Len; // 2 bytes +blockBuf.writeUInt16BE(SIZE, 34); + +// --- Data (MC7) --- +blockBuf.fill(0xAA, HEADER_SIZE, HEADER_SIZE + SIZE); + +// --- Footer --- +// byte Uk_20[20]; +blockBuf.fill(0x00, FOOTER_OFFSET, FOOTER_OFFSET + 20); +// byte Author[8]; +blockBuf.fill(0x00, FOOTER_OFFSET + 20, FOOTER_OFFSET + 28); +// byte Family[8]; +blockBuf.fill(0x00, FOOTER_OFFSET + 28, FOOTER_OFFSET + 36); +// byte Header[8]; +blockBuf.fill(0x00, FOOTER_OFFSET + 36, FOOTER_OFFSET + 44); +// byte B1; // 0x11 +blockBuf.writeUInt8(0x11, FOOTER_OFFSET + 44); +// byte B2; // 0x00 +blockBuf.writeUInt8(0x00, FOOTER_OFFSET + 45); +// word Chksum; +blockBuf.writeUInt16BE(0x0000, FOOTER_OFFSET + 46); +// byte Uk_12[8]; +blockBuf.fill(0x00, FOOTER_OFFSET + 48, FOOTER_OFFSET + 56); + +let dbBuffer, peBuffer, paBuffer, mkBuffer, tmBuffer, ctBuffer; + +beforeEach(async () => { + // Pick a free high port to avoid collisions when tests run concurrently in CI. + dynamicPort = await new Promise((resolve, reject) => { + const net = require('net'); + const s = net.createServer(); + s.on('error', reject); + s.listen(0, '127.0.0.1', () => { + const address = s.address(); + s.close(() => resolve(address.port)); + }); + }); + + server = new snap7.S7Server(); + + dbBuffer = Buffer.alloc(SIZE, 0xAA); + peBuffer = Buffer.alloc(SIZE, 0xBB); + paBuffer = Buffer.alloc(SIZE, 0xCC); + mkBuffer = Buffer.alloc(SIZE, 0xDD); + ctBuffer = Buffer.alloc(CT_SIZE, 0xEE); + tmBuffer = Buffer.alloc(TM_SIZE, 0xFF); + + server.RegisterArea(server.srvAreaPE, 0, peBuffer); + server.RegisterArea(server.srvAreaPA, 0, paBuffer); + server.RegisterArea(server.srvAreaMK, 0, mkBuffer); + server.RegisterArea(server.srvAreaDB, DB_NUMBER, dbBuffer); + server.RegisterArea(server.srvAreaCT, 0, ctBuffer); + server.RegisterArea(server.srvAreaTM, 0, tmBuffer); + + server.SetParam(server.LocalPort, dynamicPort); + await server.Start(); + + client = new snap7.S7Client(); + client.SetParam(client.RemotePort, dynamicPort); + await client.ConnectTo('127.0.0.1', 0, 2); +}); + +afterEach(async () => { + await client.Disconnect(); + client = null; + + await server.Stop(); + + server.UnregisterArea(server.srvAreaPE); + server.UnregisterArea(server.srvAreaPA); + server.UnregisterArea(server.srvAreaMK); + server.UnregisterArea(server.srvAreaDB, DB_NUMBER); + server.UnregisterArea(server.srvAreaCT); + server.UnregisterArea(server.srvAreaTM); + + server = null; +}); +// --- Connection Methods --- +test('ConnectToSync', () => { + client.Disconnect(); + assert.strictEqual(client.Connected(), false); + + // Wait 1000ms synchronously to allow OS to release resources + sleepSync(1000); + + client.SetParam(client.RemotePort, dynamicPort); + + assert.doesNotThrow(() => client.ConnectToSync('127.0.0.1', 0, 2)); + assert.strictEqual(client.Connected(), true); +}); +test('ConnectTo (Promise)', async () => { + client.Disconnect(); + assert.strictEqual(client.Connected(), false); + + // Wait 1000ms after disconnect to allow OS to release resources + await new Promise(resolve => setTimeout(resolve, 1000)); + + client.SetParam(client.RemotePort, dynamicPort); + + await client.ConnectTo('127.0.0.1', 0, 2); + assert.strictEqual(client.Connected(), true); + +}); +test('ConnectTo (Callback)', (t, done) => { + client.Disconnect(); + assert.strictEqual(client.Connected(), false); + + setTimeout(() => { + client.SetParam(client.RemotePort, dynamicPort); + + client.ConnectTo('127.0.0.1', 0, 2, (err) => { + assert.ifError(err); + assert.strictEqual(client.Connected(), true); + done(); + }); + }, 1000); // Wait 1000ms after disconnect to allow OS to release resources +}); +test('Disconnect', () => { + client.Disconnect(); + assert.strictEqual(client.Connected(), false); +}); +test('Connected', () => { + assert.strictEqual(typeof client.Connected(), 'boolean'); +}); +test('SetConnectionParams', () => { + assert.doesNotThrow(() => client.SetConnectionParams('127.0.0.1', 0x0100, 0x0200)); +}); +test('SetConnectionType', () => { + assert.doesNotThrow(() => client.SetConnectionType(client.CONNTYPE_BASIC)); +}); + +// --- Parameter Methods --- +test('SetParam', () => { + assert.doesNotThrow(() => (client.SetParam(client.PDURequest, 480))); +}); +test('GetParam', () => { + const value = client.GetParam(client.PDURequest); + assert.strictEqual(typeof value, 'number'); +}); + +// --- Data I/O Methods (Sync/Promise/Callback) --- +test('ReadAreaSync', () => { + const buf = client.ReadAreaSync(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ReadArea (Promise)', async () => { + const buf = await client.ReadArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ReadArea (Callback)', (t, done) => { + client.ReadArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('WriteAreaSync', () => { + const buf = Buffer.alloc(SIZE, 0x11); + assert.doesNotThrow(() => client.WriteAreaSync(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte, buf)); +}); +test('WriteArea (Promise)', async () => { + const buf = Buffer.alloc(SIZE, 0x12); + await client.WriteArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte, buf); +}); +test('WriteArea (Callback)', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x13); + client.WriteArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, client.S7WLByte, buf, (err) => { + assert.ifError(err); + done(); + }); +}); +test('ReadArea rejects invalid WordLen', () => { + assert.throws( + () => client.ReadAreaSync(client.S7AreaDB, DB_NUMBER, 0, SIZE, -1), + { name: 'TypeError', message: /Invalid WordLen/ } + ); + assert.throws( + () => client.ReadArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, -1), + { name: 'TypeError', message: /Invalid WordLen/ } + ); +}); +test('WriteArea rejects invalid WordLen', () => { + const buf = Buffer.alloc(SIZE, 0x13); + assert.throws( + () => client.WriteAreaSync(client.S7AreaDB, DB_NUMBER, 0, SIZE, -1, buf), + { name: 'TypeError', message: /Invalid WordLen/ } + ); + assert.throws( + () => client.WriteArea(client.S7AreaDB, DB_NUMBER, 0, SIZE, -1, buf), + { name: 'TypeError', message: /Invalid WordLen/ } + ); +}); + +// --- MultiVar Methods --- +test('ReadMultiVarsSync', () => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE }]; + const res = client.ReadMultiVarsSync(items); + assert.ok(Array.isArray(res)); +}); +test('ReadMultiVars (Promise)', async () => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE }]; + const res = await client.ReadMultiVars(items); + assert.ok(Array.isArray(res)); +}); +test('ReadMultiVars (Callback)', (t, done) => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE }]; + client.ReadMultiVars(items, (err, res) => { + assert.ifError(err); + assert.ok(Array.isArray(res)); + done(); + }); +}); +test('WriteMultiVarsSync', () => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE, Data: Buffer.alloc(SIZE, 0x21) }]; + const res = client.WriteMultiVarsSync(items); + assert.ok(Array.isArray(res)); +}); +test('WriteMultiVars (Promise)', async () => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE, Data: Buffer.alloc(SIZE, 0x22) }]; + const res = await client.WriteMultiVars(items); + assert.ok(Array.isArray(res)); +}); +test('WriteMultiVars (Callback)', (t, done) => { + const items = [{ Area: client.S7AreaDB, WordLen: client.S7WLByte, DBNumber: DB_NUMBER, Start: 0, Amount: SIZE, Data: Buffer.alloc(SIZE, 0x23) }]; + client.WriteMultiVars(items, (err, res) => { + assert.ifError(err); + assert.ok(Array.isArray(res)); + done(); + }); +}); + +// --- DBRead/DBWrite --- +test('DBReadSync', () => { + const buf = client.DBReadSync(DB_NUMBER, 0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('DBRead (Promise)', async () => { + const buf = await client.DBRead(DB_NUMBER, 0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('DBRead (Callback)', (t, done) => { + client.DBRead(DB_NUMBER, 0, SIZE, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('DBWriteSync', () => { + const buf = Buffer.alloc(SIZE, 0x31); + assert.doesNotThrow(() => client.DBWriteSync(DB_NUMBER, 0, SIZE, buf)); +}); +test('DBWrite (Promise)', async () => { + const buf = Buffer.alloc(SIZE, 0x32); + await client.DBWrite(DB_NUMBER, 0, SIZE, buf); +}); +test('DBWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x33); + client.DBWrite(DB_NUMBER, 0, SIZE, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- DBWriteSync + DBReadSync --- +test('DBWriteSync + DBReadSync verify', () => { + const buf = Buffer.alloc(SIZE, 0x41); + assert.doesNotThrow(() => client.DBWriteSync(DB_NUMBER, 0, SIZE, buf)); + const readBuf = client.DBReadSync(DB_NUMBER, 0, SIZE); + assert.ok(readBuf.equals(buf)); +}); + +// --- DBWrite (Promise) + DBRead (Promise) --- +test('DBWrite (Promise) + DBRead (Promise) verify', async () => { + const buf = Buffer.alloc(SIZE, 0x42); + await client.DBWrite(DB_NUMBER, 0, SIZE, buf); + const readBuf = await client.DBRead(DB_NUMBER, 0, SIZE); + assert.ok(readBuf.equals(buf)); +}); + +// --- DBWrite (Callback) + DBRead (Callback) --- +test('DBWrite (Callback) + DBRead (Callback) verify', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x43); + client.DBWrite(DB_NUMBER, 0, SIZE, buf, (err) => { + assert.ifError(err); + client.DBRead(DB_NUMBER, 0, SIZE, (err, readBuf) => { + assert.ifError(err); + assert.ok(readBuf.equals(buf)); + done(); + }); + }); +}); + +// --- MBRead/MBWrite --- +test('MBReadSync', () => { + const buf = client.MBReadSync(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('MBRead (Promise)', async () => { + const buf = await client.MBRead(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('MBRead (Callback)', (t, done) => { + client.MBRead(0, SIZE, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('MBWriteSync', () => { + const buf = Buffer.alloc(SIZE, 0x41); + assert.doesNotThrow(() => client.MBWriteSync(0, SIZE, buf)); +}); +test('MBWrite (Promise)', async () => { + const buf = Buffer.alloc(SIZE, 0x42); + await client.MBWrite(0, SIZE, buf); +}); +test('MBWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x43); + client.MBWrite(0, SIZE, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- EBRead/EBWrite --- +test('EBReadSync', () => { + const buf = client.EBReadSync(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('EBRead (Promise)', async () => { + const buf = await client.EBRead(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('EBRead (Callback)', (t, done) => { + client.EBRead(0, SIZE, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('EBWriteSync', () => { + const buf = Buffer.alloc(SIZE, 0x51); + assert.doesNotThrow(() => client.EBWriteSync(0, SIZE, buf)); +}); +test('EBWrite (Promise)', async () => { + const buf = Buffer.alloc(SIZE, 0x52); + await client.EBWrite(0, SIZE, buf); +}); +test('EBWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x53); + client.EBWrite(0, SIZE, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- ABRead/ABWrite --- +test('ABReadSync', () => { + const buf = client.ABReadSync(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ABRead (Promise)', async () => { + const buf = await client.ABRead(0, SIZE); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ABRead (Callback)', (t, done) => { + client.ABRead(0, SIZE, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('ABWriteSync', () => { + const buf = Buffer.alloc(SIZE, 0x61); + assert.doesNotThrow(() => client.ABWriteSync(0, SIZE, buf)); +}); +test('ABWrite (Promise)', async () => { + const buf = Buffer.alloc(SIZE, 0x62); + await client.ABWrite(0, SIZE, buf); +}); +test('ABWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(SIZE, 0x63); + client.ABWrite(0, SIZE, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- TMRead/TMWrite --- +test('TMReadSync', () => { + const buf = client.TMReadSync(0, TM_AMOUNT); + assert.ok(Buffer.isBuffer(buf)); +}); +test('TMRead (Promise)', async () => { + const buf = await client.TMRead(0, TM_AMOUNT); + assert.ok(Buffer.isBuffer(buf)); +}); +test('TMRead (Callback)', (t, done) => { + client.TMRead(0, TM_AMOUNT, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('TMWriteSync', () => { + const buf = Buffer.alloc(TM_SIZE, 0x71); + assert.doesNotThrow(() => client.TMWriteSync(0, TM_AMOUNT, buf)); +}); +test('TMWrite (Promise)', async () => { + const buf = Buffer.alloc(TM_SIZE, 0x72); + await client.TMWrite(0, TM_AMOUNT, buf); +}); +test('TMWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(TM_SIZE, 0x73); + client.TMWrite(0, TM_AMOUNT, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- CTRead/CTWrite --- +test('CTReadSync', () => { + const buf = client.CTReadSync(0, CT_AMOUNT); + assert.ok(Buffer.isBuffer(buf)); +}); +test('CTRead (Promise)', async () => { + const buf = await client.CTRead(0, CT_AMOUNT); + assert.ok(Buffer.isBuffer(buf)); +}); +test('CTRead (Callback)', (t, done) => { + client.CTRead(0, CT_AMOUNT, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('CTWriteSync', () => { + const buf = Buffer.alloc(CT_SIZE, 0x71); + assert.doesNotThrow(() => client.CTWriteSync(0, CT_AMOUNT, buf)); +}); +test('CTWrite (Promise)', async () => { + const buf = Buffer.alloc(CT_SIZE, 0x72); + await client.CTWrite(0, CT_AMOUNT, buf); +}); +test('CTWrite (Callback)', (t, done) => { + const buf = Buffer.alloc(CT_SIZE, 0x73); + client.CTWrite(0, CT_AMOUNT, buf, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- ListBlocks/ListBlocksOfType --- +test('ListBlocksSync', () => { + const res = client.ListBlocksSync(); + assert.ok(typeof res === 'object'); +}); +test('ListBlocks (Promise)', async () => { + const res = await client.ListBlocks(); + assert.ok(typeof res === 'object'); +}); +test('ListBlocks (Callback)', (t, done) => { + client.ListBlocks((err, res) => { + assert.ifError(err); + assert.ok(typeof res === 'object'); + done(); + }); +}); +test('ListBlocksOfTypeSync', () => { + const res = client.ListBlocksOfTypeSync(client.Block_DB); + assert.ok(Array.isArray(res)); +}); +test('ListBlocksOfType (Promise)', async () => { + const res = await client.ListBlocksOfType(client.Block_DB); + assert.ok(Array.isArray(res)); +}); +test('ListBlocksOfType (Callback)', (t, done) => { + client.ListBlocksOfType(client.Block_DB, (err, res) => { + assert.ifError(err); + assert.ok(Array.isArray(res)); + done(); + }); +}); + +// --- Upload/FullUpload/Download/Delete --- +test('UploadSync', () => { + try { + const res = client.UploadSync(client.Block_DB, DB_NUMBER, SIZE); + assert.ok(Buffer.isBuffer(res)); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err.errno, client.errCliNeedPassword); + } +}); +test('Upload (Promise)', async () => { + try { + const res = await client.Upload(client.Block_DB, DB_NUMBER, SIZE); + assert.ok(Buffer.isBuffer(res)); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err && err.errno, client.errCliNeedPassword); + } +}); +test('Upload (Callback)', (t, done) => { + client.Upload(client.Block_DB, DB_NUMBER, SIZE, (err, buf) => { + if (err && err.errno === client.errCliNeedPassword) { + // Accept this error as a valid outcome + done(); + return; + } + + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('FullUploadSync', () => { + try { + const res = client.FullUploadSync(client.Block_DB, DB_NUMBER, SIZE); + assert.ok(Buffer.isBuffer(res)); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err.errno, client.errCliNeedPassword); + } +}); +test('FullUpload (Promise)', async () => { + try { + const res = await client.FullUpload(client.Block_DB, DB_NUMBER, SIZE); + assert.ok(Buffer.isBuffer(res)); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err && err.errno, client.errCliNeedPassword); + } +}); +test('FullUpload (Callback)', (t, done) => { + client.FullUpload(client.Block_DB, DB_NUMBER, SIZE, (err, buf) => { + if (err && err.errno === client.errCliNeedPassword) { + // Accept this error as a valid outcome + done(); + return; + } + + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('DownloadSync', () => { + try { + client.DownloadSync(DB_NUMBER, blockBuf); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err.errno, client.errCliNeedPassword); + } +}); +test('Download (Promise)', async () => { + try { + await client.Download(DB_NUMBER, blockBuf); + } catch (err) { + // Accept this error as a valid outcome + assert.strictEqual(err && err.errno, client.errCliNeedPassword); + } +}); +test('Download (Callback)', (t, done) => { + client.Download(DB_NUMBER, blockBuf, (err) => { + if (err && err.errno === client.errCliNeedPassword) { + // Accept this error as a valid outcome + done(); + return; + } + + assert.ifError(err); + done(); + }); +}); +test('DeleteSync', () => { + assert.doesNotThrow(() => client.DeleteSync(client.Block_DB, DB_NUMBER)); +}); +test('Delete (Promise)', async () => { + await assert.doesNotReject(client.Delete(client.Block_DB, DB_NUMBER)); +}); +test('Delete (Callback)', (t, done) => { + client.Delete(client.Block_DB, DB_NUMBER, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- DBFill/DBGet --- +test('DBFillSync', () => { + assert.doesNotThrow(() => client.DBFillSync(DB_NUMBER, 0x00)); +}); +test('DBFill (Promise)', async () => { + await assert.doesNotReject(client.DBFill(DB_NUMBER, 0x00)); +}); +test('DBFill (Callback)', (t, done) => { + client.DBFill(DB_NUMBER, 0x00, (err) => { + assert.ifError(err); + done(); + }); +}); +test('DBGetSync', () => { + const buf = client.DBGetSync(DB_NUMBER); + assert.ok(Buffer.isBuffer(buf)); +}); +test('DBGet (Promise)', async () => { + const buf = await client.DBGet(DB_NUMBER); + assert.ok(Buffer.isBuffer(buf)); +}); +test('DBGet (Callback)', (t, done) => { + client.DBGet(DB_NUMBER, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); + +// --- PlcHotStart/PlcColdStart/PlcStop --- +test('PlcHotStartSync', () => { + assert.doesNotThrow(() => client.PlcHotStartSync()); +}); +test('PlcHotStart (Promise)', async () => { + await assert.doesNotReject(client.PlcHotStart()); +}); +test('PlcHotStart (Callback)', (t, done) => { + client.PlcHotStart((err) => { + assert.ifError(err); + done(); + }); +}); +test('PlcColdStartSync', () => { + assert.doesNotThrow(() => client.PlcColdStartSync()); +}); +test('PlcColdStart (Promise)', async () => { + await assert.doesNotReject(client.PlcColdStart()); +}); +test('PlcColdStart (Callback)', (t, done) => { + client.PlcColdStart((err) => { + assert.ifError(err); + done(); + }); +}); +test('PlcStopSync', () => { + assert.doesNotThrow(() => client.PlcStopSync()); +}); +test('PlcStop (Promise)', async () => { + await assert.doesNotReject(client.PlcStop()); +}); +test('PlcStop (Callback)', (t, done) => { + client.PlcStop((err) => { + assert.ifError(err); + done(); + }); +}); + +// --- CopyRamToRom/Compress --- +test('CopyRamToRomSync', () => { + assert.doesNotThrow(() => client.CopyRamToRomSync(1000)); +}); +test('CopyRamToRom (Promise)', async () => { + await assert.doesNotReject(client.CopyRamToRom(1000)); +}); +test('CopyRamToRom (Callback)', (t, done) => { + client.CopyRamToRom(1000, (err) => { + assert.ifError(err); + done(); + }); +}); +test('CompressSync', () => { + assert.doesNotThrow(() => client.CompressSync(1000)); +}); +test('Compress (Promise)', async () => { + await assert.doesNotReject(client.Compress(1000)); +}); +test('Compress (Callback)', (t, done) => { + client.Compress(1000, (err) => { + assert.ifError(err); + done(); + }); +}); + +// --- PlcStatus/GetProtection --- +test('PlcStatusSync', () => { + assert.ok([client.S7CpuStatusRun, client.S7CpuStatusStop, client.S7CpuStatusUnknown].includes(client.PlcStatusSync())); +}); +test('PlcStatus (Promise)', async () => { + const status = await client.PlcStatus(); + assert.ok([client.S7CpuStatusRun, client.S7CpuStatusStop, client.S7CpuStatusUnknown].includes(status)); +}); +test('PlcStatus (Callback)', (t, done) => { + client.PlcStatus((err, status) => { + assert.ifError(err); + assert.ok([client.S7CpuStatusRun, client.S7CpuStatusStop, client.S7CpuStatusUnknown].includes(status)); + done(); + }); +}); +test('GetProtectionSync', () => { + assert.ok(typeof client.GetProtectionSync() === 'object'); +}); +test('GetProtection (Promise)', async () => { + assert.ok(typeof (await client.GetProtection()) === 'object'); +}); +test('GetProtection (Callback)', (t, done) => { + client.GetProtection((err, prot) => { + assert.ifError(err); + assert.ok(typeof prot === 'object'); + done(); + }); +}); + +// --- SetSessionPassword/ClearSessionPassword --- +test('SetSessionPasswordSync', () => { + assert.doesNotThrow(() => client.SetSessionPasswordSync('test')); +}); +test('SetSessionPassword (Promise)', async () => { + await assert.doesNotReject(client.SetSessionPassword('test')); +}); +test('SetSessionPassword (Callback)', (t, done) => { + client.SetSessionPassword('test', (err) => { + assert.ifError(err); + done(); + }); +}); +test('ClearSessionPasswordSync', () => { + assert.doesNotThrow(() => client.ClearSessionPasswordSync()); +}); +test('ClearSessionPassword (Promise)', async () => { + await assert.doesNotReject(client.ClearSessionPassword()); +}); +test('ClearSessionPassword (Callback)', (t, done) => { + client.ClearSessionPassword((err) => { + assert.ifError(err); + done(); + }); +}); + +// --- DateTime Functions --- +test('GetPlcDateTimeSync', () => { + assert.ok(client.GetPlcDateTimeSync() instanceof Date); +}); +test('GetPlcDateTime (Promise)', async () => { + assert.ok((await client.GetPlcDateTime()) instanceof Date); +}); +test('GetPlcDateTime (Callback)', (t, done) => { + client.GetPlcDateTime((err, date) => { + assert.ifError(err); + assert.ok(date instanceof Date); + done(); + }); +}); +test('SetPlcDateTimeSync', () => { + assert.doesNotThrow(() => client.SetPlcDateTimeSync(new Date())); +}); +test('SetPlcDateTime (Promise)', async () => { + await assert.doesNotReject(client.SetPlcDateTime(new Date())); +}); +test('SetPlcDateTime (Callback)', (t, done) => { + client.SetPlcDateTime(new Date(), (err) => { + assert.ifError(err); + done(); + }); +}); +test('SetPlcSystemDateTimeSync', () => { + assert.doesNotThrow(() => client.SetPlcSystemDateTimeSync()); +}); +test('SetPlcSystemDateTime (Promise)', async () => { + await assert.doesNotReject(client.SetPlcSystemDateTime()); +}); +test('SetPlcSystemDateTime (Callback)', (t, done) => { + client.SetPlcSystemDateTime((err) => { + assert.ifError(err); + done(); + }); +}); + +// --- SZL Operations --- +test('ReadSZLSync', () => { + const buf = client.ReadSZLSync(0x0011, 0x0000); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ReadSZL (Promise)', async () => { + const buf = await client.ReadSZL(0x0011, 0x0000); + assert.ok(Buffer.isBuffer(buf)); +}); +test('ReadSZL (Callback)', (t, done) => { + client.ReadSZL(0x0011, 0x0000, (err, buf) => { + assert.ifError(err); + assert.ok(Buffer.isBuffer(buf)); + done(); + }); +}); +test('ReadSZLListSync', () => { + assert.ok(Array.isArray(client.ReadSZLListSync())); +}); +test('ReadSZLList (Promise)', async () => { + assert.ok(Array.isArray(await client.ReadSZLList())); +}); +test('ReadSZLList (Callback)', (t, done) => { + client.ReadSZLList((err, arr) => { + assert.ifError(err); + assert.ok(Array.isArray(arr)); + done(); + }); +}); + +// --- Information --- +test('GetCpuInfoSync', () => { + assert.ok(typeof client.GetCpuInfoSync() === 'object'); +}); +test('GetCpuInfo (Promise)', async () => { + assert.ok(typeof (await client.GetCpuInfo()) === 'object'); +}); +test('GetCpuInfo (Callback)', (t, done) => { + client.GetCpuInfo((err, info) => { + assert.ifError(err); + assert.ok(typeof info === 'object'); + done(); + }); +}); +test('GetCpInfoSync', () => { + assert.ok(typeof client.GetCpInfoSync() === 'object'); +}); +test('GetCpInfo (Promise)', async () => { + assert.ok(typeof (await client.GetCpInfo()) === 'object'); +}); +test('GetCpInfo (Callback)', (t, done) => { + client.GetCpInfo((err, info) => { + assert.ifError(err); + assert.ok(typeof info === 'object'); + done(); + }); +}); +test('GetOrderCodeSync', () => { + assert.ok(typeof client.GetOrderCodeSync() === 'object'); +}); +test('GetOrderCode (Promise)', async () => { + assert.ok(typeof (await client.GetOrderCode()) === 'object'); +}); +test('GetOrderCode (Callback)', (t, done) => { + client.GetOrderCode((err, info) => { + assert.ifError(err); + assert.ok(typeof info === 'object'); + done(); + }); +}); + +// --- Block Info --- +test('GetAgBlockInfoSync', () => { + assert.ok(typeof client.GetAgBlockInfoSync(client.Block_DB, DB_NUMBER) === 'object'); +}); +test('GetAgBlockInfo (Promise)', async () => { + assert.ok(typeof (await client.GetAgBlockInfo(client.Block_DB, DB_NUMBER)) === 'object'); +}); +test('GetAgBlockInfo (Callback)', (t, done) => { + client.GetAgBlockInfo(client.Block_DB, DB_NUMBER, (err, info) => { + assert.ifError(err); + assert.ok(typeof info === 'object'); + done(); + }); +}); +test('GetPgBlockInfo', () => { + assert.ok(typeof client.GetPgBlockInfo(blockBuf) === 'object'); +}); + +// --- Properties and Info --- +test('ExecTime', () => { + assert.ok(typeof client.ExecTime() === 'number' || typeof client.ExecTime() === 'boolean'); +}); +test('PDURequested', () => { + assert.ok(typeof client.PDURequested() === 'number' || typeof client.PDURequested() === 'boolean'); +}); +test('PDULength', () => { + assert.ok(typeof client.PDULength() === 'number' || typeof client.PDULength() === 'boolean'); +}); +test('ErrorText', () => { + assert.ok(typeof client.ErrorText(0) === 'string'); +}); +test('SetParam invalid throws Snap7 error with errno/code', () => { + assert.throws( + () => client.SetParam(9999, 1), + (err) => + err && + err.errno === client.errCliInvalidParamNumber && + err.code === `SNAP7_CLIENT_CODE_${client.errCliInvalidParamNumber}` + ); +}); +test('Upload (Promise) exposes Snap7 error fields', async () => { + await assert.rejects( + client.Upload(client.Block_DB, DB_NUMBER, SIZE), + (err) => err && err.errno === client.errCliNeedPassword && err.code === `SNAP7_CLIENT_CODE_${client.errCliNeedPassword}` + ); +}); +test('Upload (Callback) exposes Snap7 error fields', (t, done) => { + client.Upload(client.Block_DB, DB_NUMBER, SIZE, (err) => { + if (!err) { + // Unexpected success in this environment + t.fail('Expected error'); + done(); + return; + } + + assert.strictEqual(err.errno, client.errCliNeedPassword); + assert.strictEqual(err.code, `SNAP7_CLIENT_CODE_${client.errCliNeedPassword}`); + done(); + }); +}); diff --git a/test/test-server-ressourceless.js b/test/test-server-ressourceless.js new file mode 100644 index 0000000..2be8a1f --- /dev/null +++ b/test/test-server-ressourceless.js @@ -0,0 +1,65 @@ +const snap7 = require('../lib/node-snap7'); +const { test, before, after } = require('node:test'); +const assert = require('assert'); + +const DB_NUMBER = 1; +const SIZE = 16; + +let server, client, dynamicPort; +let dbBuffer; + +before(async () => { + // Pick a free high port to avoid collisions when tests run concurrently in CI. + dynamicPort = await new Promise((resolve, reject) => { + const net = require('net'); + const s = net.createServer(); + s.on('error', reject); + s.listen(0, '127.0.0.1', () => { + const address = s.address(); + s.close(() => resolve(address.port)); + }); + }); + + server = new snap7.S7Server(); + server.SetResourceless(true); + + dbBuffer = Buffer.alloc(SIZE, 0xAA); + + // Register DB area (required for tag info, but data is handled by event) + server.RegisterArea(server.srvAreaDB, DB_NUMBER, dbBuffer); + + // Handle read/write events + server.on('readWrite', (sender, operation, tag, buffer, callback) => { + if (operation === server.operationRead) { + // Fill buffer with current DB content + dbBuffer.copy(buffer, 0, tag.Start, tag.Start + tag.Size); + callback(buffer); + } else if (operation === server.operationWrite) { + // Write incoming data to DB buffer + buffer.copy(dbBuffer, tag.Start, 0, tag.Size); + callback(); + } + }); + + server.SetParam(server.LocalPort, dynamicPort); + await server.StartTo('127.0.0.1'); +}); + +after(async () => { + await server.Stop(); + server.UnregisterArea(server.srvAreaDB, DB_NUMBER); +}); + +test('ressourceless server: write and read DB', async () => { + client = new snap7.S7Client(); + client.SetParam(client.RemotePort, dynamicPort); + await client.ConnectTo('127.0.0.1', 0, 0); + + const writeBuf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + await client.DBWrite(DB_NUMBER, 0, SIZE, writeBuf); + + const readBuf = await client.DBRead(DB_NUMBER, 0, SIZE); + assert.ok(readBuf.equals(writeBuf), 'Read buffer should match written buffer'); + + await client.Disconnect(); +}); diff --git a/test/test-server.js b/test/test-server.js new file mode 100644 index 0000000..b55ae33 --- /dev/null +++ b/test/test-server.js @@ -0,0 +1,59 @@ +const snap7 = require('../lib/node-snap7'); +const { test, before, after } = require('node:test'); +const assert = require('assert'); + +const DB_NUMBER = 1; +const SIZE = 16; + +let server, client, dynamicPort; +let dbBuffer; + +before(async () => { + // Pick a free high port to avoid collisions when tests run concurrently in CI. + dynamicPort = await new Promise((resolve, reject) => { + const net = require('net'); + const s = net.createServer(); + s.on('error', reject); + s.listen(0, '127.0.0.1', () => { + const address = s.address(); + s.close(() => resolve(address.port)); + }); + }); + + server = new snap7.S7Server(); + server.SetResourceless(false); + + dbBuffer = Buffer.alloc(SIZE, 0xAA); + + // Register DB area (server manages the buffer directly) + server.RegisterArea(server.srvAreaDB, DB_NUMBER, dbBuffer); + + server.SetParam(server.LocalPort, dynamicPort); + await server.StartTo('127.0.0.1'); +}); + +after(async () => { + await server.Stop(); + server.UnregisterArea(server.srvAreaDB, DB_NUMBER); +}); + +test('resource server: write and read DB', async () => { + client = new snap7.S7Client(); + client.SetParam(client.RemotePort, dynamicPort); + await client.ConnectTo('127.0.0.1', 0, 0); + + const writeBuf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + await client.DBWrite(DB_NUMBER, 0, SIZE, writeBuf); + + const readBuf = await client.DBRead(DB_NUMBER, 0, SIZE); + assert.ok(readBuf.equals(writeBuf), 'Read buffer should match written buffer'); + + await client.Disconnect(); +}); + +test('server SetParam invalid throws Snap7 error', () => { + assert.throws( + () => server.SetParam(9999, 1), + (err) => err && err.errno === server.errSrvInvalidParamNumber && err.code === `SNAP7_SERVER_CODE_${server.errSrvInvalidParamNumber}` + ); +});