├── .codeclimate.yml ├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── dictionaries │ └── klaus.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jsLibraryMappings.xml ├── jsLinters │ ├── eslint.xml │ └── jshint.xml ├── misc.xml ├── modules.xml ├── node-bacstack.iml └── vcs.xml ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── FAQ.md ├── LICENSE.md ├── README.md ├── clean.sh ├── docker-compose.yml ├── examples ├── discover-devices.js ├── read-device.js └── subscribe-cov.js ├── images ├── bacnet-icon-quad.png ├── bacnet-icon-quad128.png ├── bacnet-icon-quad64.png ├── bacnet-icon-small.xcf └── bacnet-icon.xcf ├── index.js ├── lib ├── apdu.js ├── asn1.js ├── bvlc.js ├── client.js ├── enum.js ├── npdu.js ├── services │ ├── add-list-element.js │ ├── alarm-acknowledge.js │ ├── alarm-summary.js │ ├── atomic-read-file.js │ ├── atomic-write-file.js │ ├── cov-notify.js │ ├── create-object.js │ ├── delete-object.js │ ├── device-communication-control.js │ ├── error.js │ ├── event-information.js │ ├── event-notify-data.js │ ├── get-enrollment-summary.js │ ├── get-event-information.js │ ├── i-am.js │ ├── i-have.js │ ├── index.js │ ├── life-safety-operation.js │ ├── private-transfer.js │ ├── read-property-multiple.js │ ├── read-property.js │ ├── read-range.js │ ├── register-foreign-device.js │ ├── reinitialize-device.js │ ├── subscribe-cov.js │ ├── subscribe-property.js │ ├── time-sync.js │ ├── who-has.js │ ├── who-is.js │ ├── write-property-multiple.js │ └── write-property.js └── transport.js ├── npm-update.sh ├── npm-upgrade.sh ├── package-lock.json ├── package.json ├── reports └── coverage │ └── git.keep └── test ├── compliance ├── read-property-multiple.spec.js ├── read-property.spec.js ├── subscribe-cov.spec.js ├── subscribe-property.spec.js ├── utils.js ├── who-is.spec.js ├── write-property-multiple.spec.js └── write-property.spec.js ├── integration ├── acknowledge-alarm.spec.js ├── add-list-element.spec.js ├── confirmed-cov-notification.spec.js ├── confirmed-event-notification.spec.js ├── confirmed-private-transfer.spec.js ├── create-object.spec.js ├── delete-object.spec.js ├── device-communication-control.spec.js ├── get-alarm-summary.spec.js ├── get-enrollment-summary.spec.js ├── get-event-information.spec.js ├── read-file.spec.js ├── read-property-multiple.spec.js ├── read-property.spec.js ├── read-range.spec.js ├── register-foreign-device.spec.js ├── reinitialize-sevice.spec.js ├── remove-list-element.spec.js ├── subscribe-cov.spec.js ├── subscribe-property.spec.js ├── time-sync-utc.spec.js ├── time-sync.spec.js ├── unconfirmed-cov-notification.spec.js ├── unconfirmed-event-notification.spec.js ├── unconfirmed-private-transfer.spec.js ├── utils.js ├── who-is.spec.js ├── write-file.spec.js ├── write-property-multiple.spec.js └── write-property.spec.js └── unit ├── apdu.spec.js ├── asn1.spec.js ├── bvlc.spec.js ├── client.spec.js ├── enum.spec.js ├── npdu.spec.js ├── service-add-list-element.spec.js ├── service-alarm-acknowledge.spec.js ├── service-alarm-summary.spec.js ├── service-atomic-read-file.spec.js ├── service-atomic-write-file.spec.js ├── service-cov-notify.spec.js ├── service-create-object.spec.js ├── service-delete-object.spec.js ├── service-device-communication-control.spec.js ├── service-error.spec.js ├── service-event-information.spec.js ├── service-event-notify-data.spec.js ├── service-get-enrollment-summary.spec.js ├── service-get-event-information.spec.js ├── service-i-am.spec.js ├── service-i-have.spec.js ├── service-life-safety-operation.spec.js ├── service-private-transfer.spec.js ├── service-read-property-multiple.spec.js ├── service-read-property.spec.js ├── service-read-range.spec.js ├── service-reinitialize-device.spec.js ├── service-subscribe-cov.spec.js ├── service-subscribe-property.spec.js ├── service-time-sync.spec.js ├── service-who-has.spec.js ├── service-who-is.spec.js ├── service-write-property-multiple.spec.js ├── service-write-property.spec.js └── utils.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | fixme: 5 | enabled: true 6 | markdownlint: 7 | enabled: true 8 | duplication: 9 | enabled: false 10 | exclude_paths: 11 | - "CHANGELOG.md" 12 | ratings: 13 | paths: 14 | - "lib/**/*" 15 | - "index.js" 16 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | reports/ 4 | docs/ 5 | .git/ 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8, 4 | "sourceType": "module" 5 | }, 6 | "rules": { 7 | "quotes": ["error", "single", { "allowTemplateLiterals": true }], 8 | "max-len": [ 9 | 2, 10 | 320 11 | ], 12 | "semi": [ 13 | 2, 14 | "always" 15 | ], 16 | "curly": 0, 17 | "valid-jsdoc": 0 18 | }, 19 | "env": { 20 | "mocha": true, 21 | "node": true, 22 | "es2017": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Node Version: `X.Y.Z` 2 | 3 | Node BACstack Version: `X.Y.Z` 4 | 5 | - [ ] Bug Report 6 | - [ ] Feature Request 7 | - [ ] Question 8 | 9 | **Note:** Make sure you have read the [FAQs](https://github.com/fh1ch/node-bacstack/blob/master/FAQ.md) 10 | before logging this issue. 11 | 12 | ### Feature Request / Question 13 | 14 | - 15 | 16 | ### Current Behaviour (Bug Report) 17 | 18 | - 19 | 20 | ### Expected Behaviour (Bug Report) 21 | 22 | - 23 | 24 | ### Steps to reproduce Issue (Bug Report) 25 | 26 | - 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Checklist 2 | 3 | - [ ] I've read and followed the [Contribution Guide](https://github.com/fh1ch/node-bacstack/blob/master/CONTRIBUTING.md). 4 | - [ ] My commit messages reference affected issues and mention breaking changes if applicable. 5 | - [ ] I've updated / wrote inline documentation for the source code affected by my changes. 6 | - [ ] All mandatory CI checks have passed (see when Pull Request has been submitted). 7 | 8 | ### Open Question 9 | 10 | - 11 | 12 | ### What does this Pull Request do 13 | 14 | - 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | reports/ 4 | docs/ 5 | .nyc_output/ 6 | 7 | coverage/ 8 | 9 | *.log 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Zeppelin ignored files 8 | /ZeppelinRemoteNotebooks/ 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 15 | 16 | 29 | 30 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/dictionaries/klaus.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bacnet 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/jshint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 85 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/node-bacstack.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "maximumLineLength": 320, 4 | "requireSemicolons": true, 5 | "requireCurlyBraces": false, 6 | "requireCapitalizedConstructors": false, 7 | "jsDoc": false, 8 | "excludeFiles": [ 9 | "test/**", 10 | "node_modules/**", 11 | "docs/**" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | test/compliance/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "es3": false, 6 | "forin": true, 7 | "freeze": true, 8 | "latedef": false, 9 | "noarg": true, 10 | "nonbsp": true, 11 | "nonew": true, 12 | "plusplus": false, 13 | "undef": true, 14 | "unused": false, 15 | "strict": false, 16 | "maxparams": 15, 17 | "maxdepth": 5, 18 | "maxstatements": 100, 19 | "maxcomplexity": 50, 20 | 21 | "asi": false, 22 | "boss": false, 23 | "debug": false, 24 | "eqnull": true, 25 | "esnext": false, 26 | "evil": false, 27 | "expr": false, 28 | "funcscope": false, 29 | "globalstrict": false, 30 | "iterator": false, 31 | "lastsemic": false, 32 | "loopfunc": true, 33 | "maxerr": false, 34 | "moz": false, 35 | "notypeof": false, 36 | "proto": false, 37 | "scripturl": false, 38 | "shadow": false, 39 | "supernew": false, 40 | "validthis": false, 41 | "noyield": false, 42 | 43 | "browser": true, 44 | "node": true, 45 | 46 | "esversion": 8, 47 | 48 | "globals": { 49 | "describe": false, 50 | "it": false, 51 | "beforeEach": false, 52 | "afterEach": false 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | code/ 2 | .git 3 | .idea 4 | test 5 | extras 6 | node_modules 7 | .gitignore 8 | *.iml 9 | Example.png 10 | *EXAMPLE.png 11 | *.conf.js 12 | *.log 13 | .vscode/ 14 | .vyc_output/ 15 | coverage/ 16 | reports/ 17 | .github/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: "node_js" 2 | jobs: 3 | include: 4 | - stage: test 5 | node_js: "lts/*" 6 | sudo: false 7 | script: 8 | - "npm test" 9 | - stage: compliance 10 | node_js: "lts/*" 11 | services: docker 12 | sudo: false 13 | before_install: 14 | - sudo rm /usr/local/bin/docker-compose 15 | - curl -L https://github.com/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` > docker-compose 16 | - chmod +x docker-compose 17 | - sudo mv docker-compose /usr/local/bin 18 | script: 19 | - "npm install coveralls" 20 | - "npm run docker" 21 | - "cat ./reports/coverage/lcov.info | ./node_modules/.bin/coveralls" 22 | - stage: pages 23 | branches: 24 | only: 25 | - master 26 | - /^v\d+\.\d.\d*$/ 27 | node_js: "lts/*" 28 | sudo: false 29 | script: 30 | - "npm run docs" 31 | deploy: 32 | provider: pages 33 | on: 34 | branch: master 35 | local_dir: ./docs 36 | github_token: $GITHUB_API_KEY 37 | skip_cleanup: true 38 | 39 | cache: 40 | directories: 41 | - "node_modules" 42 | branches: 43 | only: 44 | - master 45 | - develop 46 | - alpha 47 | - beta 48 | - next 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Implementing and maintaining a protocol stack is a lot of work, therefore any 4 | help is appreciated, from creating issues, to contributing documentation, fixing 5 | issues and adding new features. 6 | 7 | Please follow the best-practice contribution guidelines as mentioned below when 8 | submitting any changes. 9 | 10 | ### Git 11 | 12 | Please, do your changes with the develop branch in your forked repo 13 | and send us your pull request from your own forked repo feature-branches 14 | or fix-branches to the develop branch of our repo! 15 | 16 | see [Git-Flow](https://www.atlassian.com/de/git/tutorials/comparing-workflows/gitflow-workflow) 17 | 18 | Git-Flow feature brachnes would be great here to seperate and to review changes to get the best integration results for your good work. 19 | 20 | ### Start 21 | 22 | ``` sh 23 | npm i -g npm-check-updates 24 | npm install 25 | ``` 26 | 27 | ### Conventional Changelog 28 | 29 | This module has a changelog which is automatically generated based on Git commit 30 | messages. This mechanism requires that all commit messages comply with the 31 | [Conventional Changelog](https://github.com/bcoe/conventional-changelog-standard/blob/master/convention.md). 32 | You can check if your commit messages complies with those guidelines by using: 33 | 34 | ``` sh 35 | npm run changelog 36 | ``` 37 | 38 | ### Code Style 39 | 40 | This module uses the [Google JavaScript Code-Style](https://google.github.io/styleguide/javascriptguide.xml) 41 | and enforces it using [JSCS](http://jscs.info/) as additional linter beneath 42 | [JSHint](http://jshint.com/). You can test if your changes comply with the code 43 | style by executing: 44 | 45 | ``` sh 46 | npm run lint 47 | ``` 48 | 49 | ### Testing and Coverage 50 | 51 | Testing is done using [Mocha](https://mochajs.org/) and is separated into two 52 | sets, `unit` and `integration`. While unit tries to test on function level, 53 | including synthetic decoding and encoding, the integration tests are using real 54 | recorded data and are only mocking the transport layer. 55 | 56 | For both sets, the test-coverage is calculated using [Istanbul](https://istanbul.js.org/). 57 | Running the tests and calculating the coverage can be done locally by executing: 58 | 59 | ``` sh 60 | npm run test 61 | npm run coverage 62 | npm run test:unit 63 | npm run test:integration 64 | ``` 65 | 66 | It is expected that new features or fixes do not negatively impact the test 67 | results or the coverage. 68 | 69 | ### Compliance Testing 70 | 71 | Besides the `unit` and `integration` test-sets, which are ensuring functionality 72 | using synthetical data, the `compliance` test-set is using a well established 73 | 3rd BACNET device emulator to test against. It uses the same test setup with 74 | [Mocha](https://mochajs.org/) and [Istanbul](https://istanbul.js.org/), but runs 75 | inside a Docker container, while using the [BACStack Compliance Docker](https://github.com/fh1ch/bacstack-compliance-docker) 76 | image to test against. Compliance test could also be tested with your device on Id: 1234. 77 | A compliance test will not work without the docker call or a device with Id 1234. 78 | 79 | The compliance tests can be executed locally and require Docker and 80 | Docker-Compose. To do so, simply run: 81 | 82 | ``` sh 83 | npm run docker 84 | ``` 85 | 86 | **uses coverage:all and coverage:all is just to use with docker, please!** 87 | 88 | ### Documentation 89 | 90 | The API documentation is generated using [JSDoc](http://usejsdoc.org/) and 91 | relies on in-line JSDoc3 syntax. The documentation can also be built locally by 92 | executing: 93 | 94 | ``` sh 95 | npm run docs 96 | ``` 97 | 98 | It is expected that new features or changes are reflected in the documentation 99 | as well. 100 | 101 | ### NVM 102 | 103 | Use [NVM](https://github.com/nvm-sh/nvm) to manage Node.js versions and to test with different versions in development. 104 | 105 | 106 | ``` sh 107 | nvm install lts 108 | nvm use lts 109 | nvm current 110 | ``` 111 | 112 | ### Clean Test 113 | 114 | ``` sh 115 | ./clean.sh 116 | ``` 117 | 118 | ### Show Updates for dependencies 119 | 120 | ``` sh 121 | ./npm-update.sh 122 | ``` 123 | ### Upgrade all dependencies 124 | 125 | Once 126 | ``` sh 127 | npm i -g npm-check-updates 128 | ``` 129 | 130 | Upgrade 131 | ``` sh 132 | ./npm-upgrade.sh 133 | ``` 134 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:dubnium-alpine 2 | 3 | # Set working directory 4 | WORKDIR /bacstack 5 | 6 | # Install dependencies 7 | COPY package.json . 8 | RUN npm install && npm i --only=dev 9 | 10 | # Add node-bacstack 11 | COPY . . 12 | 13 | # Run compliance tests 14 | CMD DEBUG=bacnet* npm run coverage:all 15 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | This file contains a collection of frequently asked questions and additional 4 | background information. 5 | 6 | ## Why can't I connect to my local device simulator? 7 | 8 | BACnet uses unicast and broadcast UDP telegrams on a single port to communicate 9 | with other devices. This makes it impossible to run two BACnet stacks on the 10 | same machine without taking a detour. The most promising way is either to rely 11 | on something like Docker an use it's internal network (e.g. see 12 | `docker-compose.yml`) or use a secondary IP for your local machine. 13 | 14 | ## I receive the error `ERR_TIMEOUT`. What does this mean? 15 | 16 | The error type `ERR_TIMEOUT` is created by Node BACstack itself, if no answer 17 | has been received from a target device in the defined timeout window. This can 18 | have a variety of root-causes like issues on the remote device itself (e.g. 19 | communication disabled), network issues (e.g. wrong network topology), wrong 20 | communication parameters (e.g. wrong port) or ignored limitations (stack doesn't 21 | support routing). Some tips regarding debugging such issues are described in 22 | this document. 23 | 24 | ## I receive the error `BacnetError - Class: X - Code: Y`. What does this mean? 25 | 26 | The two error types `BacnetError` and `BacnetAbort` are application level errors 27 | and are returned by a BACnet remote device in case of a failure. Both error 28 | types also contain the reason for the failure, which can be interpreted like: 29 | 30 | ```txt 31 | Error: BacnetError - Class: [X] - Code: [Y] 32 | [X] => In `lib/enum.js` see `ErrorClass`. 33 | [Y] => In `lib/enum.js` see `ErrorCode`. 34 | 35 | Error: BacnetAbort - Reason: [Z] 36 | [Z] => In `lib/enum.js` see `RejectReason`. 37 | ``` 38 | 39 | We therefore can encode the example `Error: BacnetError - Class: 2 - Code: 32` 40 | to `Error: BacnetError - Class: PROPERTY - Code: UNKNOWN_PROPERTY`, meaning the 41 | property we tried to read or modify doesn't exist. 42 | 43 | ## What is the easiest way to debug failing operations? 44 | 45 | A good way to start debugging all sort of failures, is to cross-check the 46 | attempted function with another BACnet tool, such as one of the many freely 47 | available BACnet browsers. This verifies, if the attempted function itself works 48 | with the target device and rules-out other issues like connectivity. 49 | 50 | This stack also has internal debug messages, which can be enabled with 51 | `export DEBUG=bacstack`. It will print all sorts of internals to the console, 52 | which can be used to get an idea, if or why messages are getting dropped. 53 | 54 | Another powerful tool to not only identify network issues, but also verify sent 55 | and received messages is [Wireshark](https://wireshark.org/). It comes with 56 | inbuilt BACnet analysis tooling, which can help to make all BACnet telegrams 57 | visible and also checks if they comply to the BACnet standard. 58 | 59 | ## Where can I get more information / examples on undocumented functions? 60 | 61 | Beneath the rendered documentation itself, a good starting point for even more 62 | examples are the compliance tests. They use the stack via it's public interface 63 | and test a wide variety of scenarios. The tests are splitted into individual 64 | files per function and can be found in `test/compliance/`. 65 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-2020 Community Driven 4 | 5 | Copyright (c) 2017-2019 Fabio Huser (fh1ch) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | rm -rf node_modules/ 4 | 5 | rm -rf docs/ 6 | 7 | rm -rf reports/ 8 | 9 | mkdir -p reports/coverage 10 | touch reports/coverage/git.keep 11 | 12 | rm -rf .nyc_output/ 13 | 14 | rm package-lock.json 15 | 16 | npm cache verify 17 | 18 | npm install 19 | 20 | npm i --only=dev 21 | 22 | npm test 23 | 24 | npm run changelog 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bacnet-device: 4 | image: fh1ch/bacstack-compliance-docker 5 | bacnet-client: 6 | build: 7 | context: . 8 | links: 9 | - bacnet-device 10 | volumes: 11 | - ./reports/coverage:/reports/coverage 12 | -------------------------------------------------------------------------------- /examples/discover-devices.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This script will discover all devices in the network and print out their names 5 | * 6 | * After 30s the discovery is stopped automatically 7 | */ 8 | 9 | const Bacnet = require('../index'); 10 | 11 | // create instance of Bacnet 12 | const bacnetClient = new Bacnet({apduTimeout: 10000, interface: '0.0.0.0'}); 13 | 14 | // emitted on errors 15 | bacnetClient.on('error', (err) => { 16 | console.error(err); 17 | bacnetClient.close(); 18 | }); 19 | 20 | // emmitted when Bacnet server listens for incoming UDP packages 21 | bacnetClient.on('listening', () => { 22 | console.log('discovering devices for 30 seconds ...'); 23 | // discover devices once we are listening 24 | bacnetClient.whoIs(); 25 | 26 | setTimeout(() => { 27 | bacnetClient.close(); 28 | console.log('closed transport ' + Date.now()); 29 | }, 30000); 30 | 31 | }); 32 | 33 | const knownDevices = []; 34 | 35 | // emitted when a new device is discovered in the network 36 | bacnetClient.on('iAm', (device) => { 37 | // address object of discovered device, 38 | // just use in subsequent calls that are directed to this device 39 | const address = device.header.sender; 40 | 41 | //discovered device ID 42 | const deviceId = device.payload.deviceId; 43 | if (knownDevices.includes(deviceId)) return; 44 | 45 | bacnetClient.readProperty(address, {type: 8, instance: deviceId}, Bacnet.enum.PropertyIdentifier.OBJECT_NAME, (err, value) => { 46 | if (err) { 47 | console.log('Found Device ' + deviceId + ' on ' + JSON.stringify(address)); 48 | console.log(err); 49 | } else { 50 | bacnetClient.readProperty(address, {type: 8, instance: deviceId}, Bacnet.enum.PropertyIdentifier.OBJECT_NAME, (err2, valueVendor) => { 51 | 52 | if (value && value.values && value.values[0].value) { 53 | console.log('Found Device ' + deviceId + ' on ' + JSON.stringify(address) + ': ' + value.values[0].value); 54 | } else { 55 | console.log('Found Device ' + deviceId + ' on ' + JSON.stringify(address)); 56 | console.log('value: ', JSON.stringify(value)); 57 | } 58 | if (!err2 && valueVendor && valueVendor.values && valueVendor.values[0].value) { 59 | console.log('Vendor: ' + valueVendor.values[0].value); 60 | } 61 | console.log(); 62 | }); 63 | } 64 | }); 65 | knownDevices.push(deviceId); 66 | }); 67 | -------------------------------------------------------------------------------- /examples/subscribe-cov.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This script will discover tze devices and tries to register a COV for ANALOG_INPUT 0 5 | * The script works very well when the Yabe Room Simulator exe is running 6 | * 7 | * After 20s the subscription is cancelled by updating it with a lifetime of 1s 8 | */ 9 | 10 | const Bacnet = require('../index'); 11 | 12 | // you need to run the Weather2 Station of the YABE BACnet package 13 | // https://sourceforge.net/projects/yetanotherbacnetexplorer/ 14 | 15 | // create instance of Bacnet 16 | const bacnetClient = new Bacnet({apduTimeout: 10000, interface: '0.0.0.0'}); 17 | 18 | // emitted for each new message 19 | bacnetClient.on('message', (msg, rinfo) => { 20 | console.log(msg); 21 | if (rinfo) console.log(rinfo); 22 | }); 23 | 24 | // emitted on errors 25 | bacnetClient.on('error', (err) => { 26 | console.error(err); 27 | bacnetClient.close(); 28 | }); 29 | 30 | // emmitted when Bacnet server listens for incoming UDP packages 31 | bacnetClient.on('listening', () => { 32 | console.log('sent whoIs ' + Date.now()); 33 | // discover devices once we are listening 34 | bacnetClient.whoIs(); 35 | }); 36 | 37 | // emitted when "Change of object" Messages are coming in 38 | bacnetClient.on('covNotifyUnconfirmed', (data) => { 39 | console.log('Received COV: ' + JSON.stringify(data)); 40 | }); 41 | 42 | // emitted when a new device is discovered in the network 43 | bacnetClient.on('iAm', (device) => { 44 | console.log('Received iAm: ' + JSON.stringify(device, null, 4)); 45 | // address object of discovered device, 46 | // just use in subsequent calls that are directed to this device 47 | const address = device.header.sender; 48 | 49 | //discovered device ID 50 | const deviceId = device.payload.deviceId; 51 | console.log('Found Device ' + deviceId + ' on ' + JSON.stringify(address)); 52 | 53 | // Subscribe changes for RESENT_VALUE of ANALOG_INPUT,0 object 54 | // lifetime 0 means "for ever" 55 | bacnetClient.subscribeCov(address, {type: 0, instance: 0}, 85, false, false, 0, (err) => { 56 | console.log('subscribeCOV' + err); 57 | }); 58 | 59 | // after 20s re-subscribe but with 1s lifetime to stop it 60 | // I had issues with "cancel" call with the simulated device 61 | setTimeout(() => { 62 | bacnetClient.subscribeCov(address, {type: 0, instance: 0}, 85, false, false, 1, (err) => { 63 | console.log('UnsubscribeCOV' + err); 64 | }); 65 | }, 20000); 66 | 67 | }); 68 | 69 | // after 30s end the connection 70 | setTimeout(() => { 71 | 72 | bacnetClient.close(); 73 | console.log('closed transport ' + Date.now()); 74 | 75 | }, 30000); // do not close to fast 76 | -------------------------------------------------------------------------------- /images/bacnet-icon-quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/images/bacnet-icon-quad.png -------------------------------------------------------------------------------- /images/bacnet-icon-quad128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/images/bacnet-icon-quad128.png -------------------------------------------------------------------------------- /images/bacnet-icon-quad64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/images/bacnet-icon-quad64.png -------------------------------------------------------------------------------- /images/bacnet-icon-small.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/images/bacnet-icon-small.xcf -------------------------------------------------------------------------------- /images/bacnet-icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/images/bacnet-icon.xcf -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/client'); 4 | module.exports._ = require('underscore'); 5 | module.exports.enum = require('./lib/enum'); 6 | -------------------------------------------------------------------------------- /lib/bvlc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baEnum = require('./enum'); 4 | const debug = require('debug')('bacnet:bvlc:debug'); 5 | const trace = require('debug')('bacnet:bvlc:trace'); 6 | 7 | const DEFAULT_BACNET_PORT = 47808; 8 | 9 | module.exports.encode = (buffer, func, msgLength, originatingIP) => { 10 | buffer[0] = baEnum.BVLL_TYPE_BACNET_IP; 11 | buffer[1] = func; 12 | buffer[2] = (msgLength & 0xFF00) >> 8; 13 | buffer[3] = (msgLength & 0x00FF) >> 0; 14 | if (originatingIP) { 15 | // This is always a FORWARDED_NPDU regardless of the 'func' parameter. 16 | if (func !== baEnum.BvlcResultPurpose.FORWARDED_NPDU) { 17 | throw new Error('Cannot specify originatingIP unless ' + 18 | 'BvlcResultPurpose.FORWARDED_NPDU is used.'); 19 | } 20 | // Encode the IP address and optional port into bytes. 21 | const [ipstr, portstr] = originatingIP.split(':'); 22 | const port = parseInt(portstr, 10) || DEFAULT_BACNET_PORT; 23 | const ip = ipstr.split('.'); 24 | buffer[4] = parseInt(ip[0], 10); 25 | buffer[5] = parseInt(ip[1], 10); 26 | buffer[6] = parseInt(ip[2], 10); 27 | buffer[7] = parseInt(ip[3], 10); 28 | buffer[8] = (port & 0xFF00) >> 8; 29 | buffer[9] = (port & 0x00FF) >> 0; 30 | return 6 + baEnum.BVLC_HEADER_LENGTH; 31 | } else { 32 | if (func === baEnum.BvlcResultPurpose.FORWARDED_NPDU) { 33 | throw new Error('Must specify originatingIP if ' + 34 | 'BvlcResultPurpose.FORWARDED_NPDU is used.'); 35 | } 36 | } 37 | return baEnum.BVLC_HEADER_LENGTH; 38 | }; 39 | 40 | module.exports.decode = (buffer, _offset) => { 41 | let len; 42 | const func = buffer[1]; 43 | const msgLength = (buffer[2] << 8) | (buffer[3] << 0); 44 | if (buffer[0] !== baEnum.BVLL_TYPE_BACNET_IP || buffer.length !== msgLength) { 45 | return undefined; 46 | } 47 | let originatingIP = null; 48 | switch (func) { 49 | case baEnum.BvlcResultPurpose.BVLC_RESULT: 50 | case baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU: 51 | case baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU: 52 | case baEnum.BvlcResultPurpose.DISTRIBUTE_BROADCAST_TO_NETWORK: 53 | case baEnum.BvlcResultPurpose.REGISTER_FOREIGN_DEVICE: 54 | case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE: 55 | case baEnum.BvlcResultPurpose.DELETE_FOREIGN_DEVICE_TABLE_ENTRY: 56 | case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE: 57 | case baEnum.BvlcResultPurpose.WRITE_BROADCAST_DISTRIBUTION_TABLE: 58 | case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE_ACK: 59 | case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE_ACK: 60 | len = 4; 61 | break; 62 | case baEnum.BvlcResultPurpose.FORWARDED_NPDU: 63 | // Work out where the packet originally came from before the BBMD 64 | // forwarded it to us, so we can tell the BBMD where to send any reply to. 65 | const port = (buffer[8] << 8) | buffer[9]; 66 | originatingIP = buffer.slice(4, 8).join('.'); 67 | 68 | // Only add the port if it's not the usual one. 69 | if (port !== DEFAULT_BACNET_PORT) { 70 | originatingIP += ':' + port; 71 | } 72 | 73 | len = 10; 74 | break; 75 | case baEnum.BvlcResultPurpose.SECURE_BVLL: 76 | // unimplemented 77 | return undefined; 78 | default: 79 | return undefined; 80 | } 81 | return { 82 | len, 83 | func, 84 | msgLength, 85 | // Originating IP is set to the IP address of the node that originally 86 | // sent the packet, when it has been forwarded to us by a BBMD (since the 87 | // BBMD's IP address will be in the sender field. 88 | originatingIP, 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /lib/npdu.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baEnum = require('./enum'); 4 | const debug = require('debug')('bacnet:npdu:debug'); 5 | const trace = require('debug')('bacnet:npdu:trace'); 6 | 7 | const DEFAULT_HOP_COUNT = 0xFF; 8 | const BACNET_PROTOCOL_VERSION = 1; 9 | const BACNET_ADDRESS_TYPES = { 10 | NONE: 0, 11 | IP: 1 12 | }; 13 | 14 | const decodeTarget = (buffer, offset) => { 15 | let len = 0; 16 | const target = {type: BACNET_ADDRESS_TYPES.NONE, net: (buffer[offset + len++] << 8) | (buffer[offset + len++] << 0)}; 17 | const adrLen = buffer[offset + len++]; 18 | if (adrLen > 0) { 19 | target.adr = []; 20 | for (let i = 0; i < adrLen; i++) { 21 | target.adr.push(buffer[offset + len++]); 22 | } 23 | } 24 | return { 25 | target, 26 | len 27 | }; 28 | }; 29 | 30 | const encodeTarget = (buffer, target) => { 31 | buffer.buffer[buffer.offset++] = (target.net & 0xFF00) >> 8; 32 | buffer.buffer[buffer.offset++] = (target.net & 0x00FF) >> 0; 33 | if (target.net === 0xFFFF || !target.adr) { 34 | buffer.buffer[buffer.offset++] = 0; 35 | } else { 36 | buffer.buffer[buffer.offset++] = target.adr.length; 37 | if (target.adr.length > 0) { 38 | for (let i = 0; i < target.adr.length; i++) { 39 | buffer.buffer[buffer.offset++] = target.adr[i]; 40 | } 41 | } 42 | } 43 | }; 44 | 45 | module.exports.decodeFunction = (buffer, offset) => { 46 | if (buffer[offset + 0] !== BACNET_PROTOCOL_VERSION) { 47 | return undefined; 48 | } 49 | return buffer[offset + 1]; 50 | }; 51 | 52 | module.exports.decode = (buffer, offset) => { 53 | let adrLen; 54 | const orgOffset = offset; 55 | offset++; 56 | const funct = buffer[offset++]; 57 | let destination; 58 | if (funct & baEnum.NpduControlBit.DESTINATION_SPECIFIED) { 59 | const tmpDestination = decodeTarget(buffer, offset); 60 | offset += tmpDestination.len; 61 | destination = tmpDestination.target; 62 | } 63 | let source; 64 | if (funct & baEnum.NpduControlBit.SOURCE_SPECIFIED) { 65 | const tmpSource = decodeTarget(buffer, offset); 66 | offset += tmpSource.len; 67 | source = tmpSource.target; 68 | } 69 | let hopCount = 0; 70 | if (funct & baEnum.NpduControlBit.DESTINATION_SPECIFIED) { 71 | hopCount = buffer[offset++]; 72 | } 73 | let networkMsgType = baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK; 74 | let vendorId = 0; 75 | if (funct & baEnum.NpduControlBit.NETWORK_LAYER_MESSAGE) { 76 | networkMsgType = buffer[offset++]; 77 | if (networkMsgType >= 0x80) { 78 | vendorId = (buffer[offset++] << 8) | (buffer[offset++] << 0); 79 | } else if (networkMsgType === baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK) { 80 | offset += 2; 81 | } 82 | } 83 | if (buffer[orgOffset + 0] !== BACNET_PROTOCOL_VERSION) { 84 | return undefined; 85 | } 86 | return { 87 | len: offset - orgOffset, 88 | funct, 89 | destination, 90 | source, 91 | hopCount, 92 | networkMsgType, 93 | vendorId 94 | }; 95 | }; 96 | 97 | module.exports.encode = (buffer, funct, destination, source, hopCount, networkMsgType, vendorId) => { 98 | const hasDestination = destination && destination.net > 0; 99 | const hasSource = source && source.net > 0 && source.net !== 0xFFFF; 100 | 101 | buffer.buffer[buffer.offset++] = BACNET_PROTOCOL_VERSION; 102 | buffer.buffer[buffer.offset++] = funct | (hasDestination ? baEnum.NpduControlBit.DESTINATION_SPECIFIED : 0) | (hasSource ? baEnum.NpduControlBit.SOURCE_SPECIFIED : 0); 103 | 104 | if (hasDestination) { 105 | encodeTarget(buffer, destination); 106 | } 107 | 108 | if (hasSource) { 109 | encodeTarget(buffer, source); 110 | } 111 | 112 | if (hasDestination) { 113 | buffer.buffer[buffer.offset++] = hopCount || DEFAULT_HOP_COUNT; 114 | } 115 | 116 | if ((funct & baEnum.NpduControlBit.NETWORK_LAYER_MESSAGE) > 0) { 117 | buffer.buffer[buffer.offset++] = networkMsgType; 118 | if (networkMsgType >= 0x80) { 119 | buffer.buffer[buffer.offset++] = (vendorId & 0xFF00) >> 8; 120 | buffer.buffer[buffer.offset++] = (vendorId & 0x00FF) >> 0; 121 | } 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /lib/services/add-list-element.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, objectId, propertyId, arrayIndex, values) => { 7 | baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance); 8 | baAsn1.encodeContextEnumerated(buffer, 1, propertyId); 9 | if (arrayIndex !== baEnum.ASN1_ARRAY_ALL) { 10 | baAsn1.encodeContextUnsigned(buffer, 2, arrayIndex); 11 | } 12 | baAsn1.encodeOpeningTag(buffer, 3); 13 | values.forEach((value) => { 14 | baAsn1.bacappEncodeApplicationData(buffer, value); 15 | }); 16 | baAsn1.encodeClosingTag(buffer, 3); 17 | }; 18 | 19 | module.exports.decode = (buffer, offset, apduLen) => { 20 | let len = 0; 21 | let result; 22 | let decodedValue; 23 | let value = {}; 24 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 25 | len += result.len; 26 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 27 | len += decodedValue.len; 28 | value.objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 29 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 30 | len += result.len; 31 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 32 | len += decodedValue.len; 33 | value.property = {id: decodedValue.value}; 34 | if (len < apduLen && baAsn1.decodeIsContextTag(buffer, offset + len, 2)) { 35 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 36 | len += result.len; 37 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 38 | len += decodedValue.len; 39 | value.property.index = decodedValue.value; 40 | } else { 41 | value.property.index = baEnum.ASN1_ARRAY_ALL; 42 | } 43 | const values = []; 44 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 3)) { 45 | return undefined; 46 | } 47 | len++; 48 | while ((apduLen - len) > 1) { 49 | result = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, value.objectId.type, value.property.id); 50 | if (!result) { 51 | return undefined; 52 | } 53 | len += result.len; 54 | delete result.len; 55 | values.push(result); 56 | } 57 | value.values = values; 58 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 3)) { 59 | return undefined; 60 | } 61 | len++; 62 | value.len = len; 63 | return value; 64 | }; 65 | -------------------------------------------------------------------------------- /lib/services/alarm-acknowledge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, ackProcessId, eventObjectId, eventStateAcknowledged, ackSource, eventTimeStamp, ackTimeStamp) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, ackProcessId); 8 | baAsn1.encodeContextObjectId(buffer, 1, eventObjectId.type, eventObjectId.instance); 9 | baAsn1.encodeContextEnumerated(buffer, 2, eventStateAcknowledged); 10 | baAsn1.bacappEncodeContextTimestamp(buffer, 3, eventTimeStamp); 11 | baAsn1.encodeContextCharacterString(buffer, 4, ackSource); 12 | baAsn1.bacappEncodeContextTimestamp(buffer, 5, ackTimeStamp); 13 | }; 14 | 15 | module.exports.decode = (buffer, offset, apduLen) => { 16 | let len = 0; 17 | let value = {}; 18 | let result; 19 | let decodedValue; 20 | let date; 21 | let time; 22 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 23 | len += result.len; 24 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 25 | len += decodedValue.len; 26 | value.acknowledgedProcessId = decodedValue.value; 27 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 28 | len += result.len; 29 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 30 | len += decodedValue.len; 31 | value.eventObjectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 32 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 33 | len += result.len; 34 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 35 | len += decodedValue.len; 36 | value.eventStateAcknowledged = decodedValue.value; 37 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 38 | len += result.len; 39 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 40 | len += result.len; 41 | if (result.tagNumber === baEnum.TimeStamp.TIME) { 42 | decodedValue = baAsn1.decodeBacnetTime(buffer, offset + len, result.value); 43 | len += decodedValue.len; 44 | value.eventTimeStamp = decodedValue.value; 45 | } else if (result.tagNumber === baEnum.TimeStamp.SEQUENCE_NUMBER) { 46 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 47 | len += decodedValue.len; 48 | value.eventTimeStamp = decodedValue.value; 49 | } else if (result.tagNumber === baEnum.TimeStamp.DATETIME) { 50 | date = baAsn1.decodeApplicationDate(buffer, offset + len); 51 | len += date.len; 52 | date = date.value.value; 53 | time = baAsn1.decodeApplicationTime(buffer, offset + len); 54 | len += time.len; 55 | time = time.value.value; 56 | value.eventTimeStamp = new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()); 57 | len++; 58 | } 59 | len++; 60 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 61 | len += result.len; 62 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 63 | value.acknowledgeSource = decodedValue.value; 64 | len += decodedValue.len; 65 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 66 | len += result.len; 67 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 68 | len += result.len; 69 | if (result.tagNumber === baEnum.TimeStamp.TIME) { 70 | decodedValue = baAsn1.decodeBacnetTime(buffer, offset + len, result.value); 71 | len += decodedValue.len; 72 | value.acknowledgeTimeStamp = decodedValue.value; 73 | } else if (result.tagNumber === baEnum.TimeStamp.SEQUENCE_NUMBER) { 74 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 75 | len += decodedValue.len; 76 | value.acknowledgeTimeStamp = decodedValue.value; 77 | } else if (result.tagNumber === baEnum.TimeStamp.DATETIME) { 78 | date = baAsn1.decodeApplicationDate(buffer, offset + len); 79 | len += date.len; 80 | date = date.value.value; 81 | time = baAsn1.decodeApplicationTime(buffer, offset + len); 82 | len += time.len; 83 | time = time.value.value; 84 | value.acknowledgeTimeStamp = new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()); 85 | len++; 86 | } 87 | len++; 88 | value.len = len; 89 | return value; 90 | }; 91 | -------------------------------------------------------------------------------- /lib/services/alarm-summary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, alarms) => { 7 | alarms.forEach((alarm) => { 8 | baAsn1.encodeContextObjectId(buffer, 12, alarm.objectId.type, alarm.objectId.instance); 9 | baAsn1.encodeContextEnumerated(buffer, 9, alarm.alarmState); 10 | baAsn1.encodeContextBitstring(buffer, 8, alarm.acknowledgedTransitions); 11 | }); 12 | }; 13 | 14 | module.exports.decode = (buffer, offset, apduLen) => { 15 | let len = 0; 16 | let result; 17 | let decodedValue; 18 | const alarms = []; 19 | while ((apduLen - 3 - len) > 0) { 20 | let value = {}; 21 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 22 | len += result.len; 23 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 24 | len += decodedValue.len; 25 | value.objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 26 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 27 | len += result.len; 28 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 29 | len += decodedValue.len; 30 | value.alarmState = decodedValue.value; 31 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 32 | len += result.len; 33 | decodedValue = baAsn1.decodeBitstring(buffer, offset + len, result.value); 34 | len += decodedValue.len; 35 | value.acknowledgedTransitions = decodedValue.value; 36 | alarms.push(value); 37 | } 38 | return { 39 | len, 40 | alarms 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /lib/services/atomic-write-file.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, isStream, objectId, position, blocks) => { 7 | baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); 8 | if (isStream) { 9 | baAsn1.encodeOpeningTag(buffer, 0); 10 | baAsn1.encodeApplicationSigned(buffer, position); 11 | baAsn1.encodeApplicationOctetString(buffer, blocks[0], 0, blocks[0].length); 12 | baAsn1.encodeClosingTag(buffer, 0); 13 | } else { 14 | baAsn1.encodeOpeningTag(buffer, 1); 15 | baAsn1.encodeApplicationSigned(buffer, position); 16 | baAsn1.encodeApplicationUnsigned(buffer, blocks.length); 17 | for (let i = 0; i < blocks.length; i++) { 18 | baAsn1.encodeApplicationOctetString(buffer, blocks[i], 0, blocks[i].length); 19 | } 20 | baAsn1.encodeClosingTag(buffer, 1); 21 | } 22 | }; 23 | 24 | module.exports.decode = (buffer, offset, apduLen) => { 25 | let len = 0; 26 | let result; 27 | let decodedValue; 28 | let isStream; 29 | let position; 30 | let blocks = []; 31 | let blockCount; 32 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 33 | len += result.len; 34 | if (result.tagNumber !== baEnum.ApplicationTag.OBJECTIDENTIFIER) { 35 | return undefined; 36 | } 37 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 38 | len += decodedValue.len; 39 | let objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 40 | if (baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 0)) { 41 | isStream = true; 42 | len++; 43 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 44 | len += result.len; 45 | if (result.tagNumber !== baEnum.ApplicationTag.SIGNED_INTEGER) { 46 | return undefined; 47 | } 48 | decodedValue = baAsn1.decodeSigned(buffer, offset + len, result.value); 49 | len += decodedValue.len; 50 | position = decodedValue.value; 51 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 52 | len += result.len; 53 | if (result.tagNumber !== baEnum.ApplicationTag.OCTET_STRING) { 54 | return undefined; 55 | } 56 | decodedValue = baAsn1.decodeOctetString(buffer, offset + len, apduLen, 0, result.value); 57 | len += decodedValue.len; 58 | blocks.push(decodedValue.value); 59 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 0)) { 60 | return undefined; 61 | } 62 | len++; 63 | } else if (baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 1)) { 64 | isStream = false; 65 | len++; 66 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 67 | len += result.len; 68 | if (result.tagNumber !== baEnum.ApplicationTag.SIGNED_INTEGER) { 69 | return undefined; 70 | } 71 | decodedValue = baAsn1.decodeSigned(buffer, offset + len, result.value); 72 | len += decodedValue.len; 73 | position = decodedValue.value; 74 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 75 | len += result.len; 76 | if (result.tagNumber !== baEnum.ApplicationTag.UNSIGNED_INTEGER) { 77 | return undefined; 78 | } 79 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 80 | len += decodedValue.len; 81 | blockCount = decodedValue.value; 82 | for (let i = 0; i < blockCount; i++) { 83 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 84 | len += result.len; 85 | if (result.tagNumber !== baEnum.ApplicationTag.OCTET_STRING) { 86 | return undefined; 87 | } 88 | decodedValue = baAsn1.decodeOctetString(buffer, offset + len, apduLen, 0, result.value); 89 | len += decodedValue.len; 90 | blocks.push(decodedValue.value); 91 | } 92 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 1)) { 93 | return undefined; 94 | } 95 | len++; 96 | } else { 97 | return undefined; 98 | } 99 | return { 100 | len, 101 | isStream, 102 | objectId, 103 | position, 104 | blocks 105 | }; 106 | }; 107 | 108 | module.exports.encodeAcknowledge = (buffer, isStream, position) => { 109 | if (isStream) { 110 | baAsn1.encodeContextSigned(buffer, 0, position); 111 | } else { 112 | baAsn1.encodeContextSigned(buffer, 1, position); 113 | } 114 | }; 115 | 116 | module.exports.decodeAcknowledge = (buffer, offset) => { 117 | let len = 0; 118 | let isStream = false; 119 | let position = 0; 120 | let decodedValue; 121 | const result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 122 | len += result.len; 123 | if (result.tagNumber === 0) { 124 | isStream = true; 125 | } else if (result.tagNumber === 1) { 126 | isStream = false; 127 | } else { 128 | return undefined; 129 | } 130 | decodedValue = baAsn1.decodeSigned(buffer, offset + len, result.value); 131 | len += decodedValue.len; 132 | position = decodedValue.value; 133 | return { 134 | len, 135 | isStream, 136 | position 137 | }; 138 | }; 139 | -------------------------------------------------------------------------------- /lib/services/cov-notify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, subscriberProcessId, initiatingDeviceId, monitoredObjectId, timeRemaining, values) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, subscriberProcessId); 8 | baAsn1.encodeContextObjectId(buffer, 1, baEnum.ObjectType.DEVICE, initiatingDeviceId); 9 | baAsn1.encodeContextObjectId(buffer, 2, monitoredObjectId.type, monitoredObjectId.instance); 10 | baAsn1.encodeContextUnsigned(buffer, 3, timeRemaining); 11 | baAsn1.encodeOpeningTag(buffer, 4); 12 | values.forEach((value) => { 13 | baAsn1.encodeContextEnumerated(buffer, 0, value.property.id); 14 | if (value.property.index === baEnum.ASN1_ARRAY_ALL) { 15 | baAsn1.encodeContextUnsigned(buffer, 1, value.property.index); 16 | } 17 | baAsn1.encodeOpeningTag(buffer, 2); 18 | value.value.forEach((v) => { 19 | baAsn1.bacappEncodeApplicationData(buffer, v); 20 | }); 21 | baAsn1.encodeClosingTag(buffer, 2); 22 | if (value.priority === baEnum.ASN1_NO_PRIORITY) { 23 | baAsn1.encodeContextUnsigned(buffer, 3, value.priority); 24 | } 25 | // TODO: Handle to too large telegrams -> APDU limit 26 | }); 27 | baAsn1.encodeClosingTag(buffer, 4); 28 | }; 29 | 30 | module.exports.decode = (buffer, offset, apduLen) => { 31 | let len = 0; 32 | let result; 33 | let decodedValue; 34 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 35 | return undefined; 36 | } 37 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 38 | len += result.len; 39 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 40 | len += decodedValue.len; 41 | const subscriberProcessId = decodedValue.value; 42 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 43 | return undefined; 44 | } 45 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 46 | len += result.len; 47 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 48 | len += decodedValue.len; 49 | const initiatingDeviceId = {type: decodedValue.objectType, instance: decodedValue.instance}; 50 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 2)) { 51 | return undefined; 52 | } 53 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 54 | len += result.len; 55 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 56 | len += decodedValue.len; 57 | const monitoredObjectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 58 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 3)) { 59 | return undefined; 60 | } 61 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 62 | len += result.len; 63 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 64 | len += decodedValue.len; 65 | const timeRemaining = decodedValue.value; 66 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 4)) { 67 | return undefined; 68 | } 69 | len++; 70 | const values = []; 71 | while ((apduLen - len) > 1 && !baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 4)) { 72 | let newEntry = {}; 73 | newEntry.property = {}; 74 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 75 | return undefined; 76 | } 77 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 78 | len += result.len; 79 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 80 | len += decodedValue.len; 81 | newEntry.property.id = decodedValue.value; 82 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 83 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 84 | len += result.len; 85 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 86 | len += decodedValue.len; 87 | newEntry.property.index = decodedValue.value; 88 | } else { 89 | newEntry.property.index = baEnum.ASN1_ARRAY_ALL; 90 | } 91 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 2)) { 92 | return undefined; 93 | } 94 | len++; 95 | const properties = []; 96 | while ((apduLen - len) > 1 && !baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 2)) { 97 | decodedValue = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, monitoredObjectId.type, newEntry.property.id); 98 | if (!decodedValue) { 99 | return undefined; 100 | } 101 | len += decodedValue.len; 102 | delete decodedValue.len; 103 | properties.push(decodedValue); 104 | } 105 | newEntry.value = properties; 106 | len++; 107 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 3)) { 108 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 109 | len += result.len; 110 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 111 | len += decodedValue.len; 112 | newEntry.priority = decodedValue.value; 113 | } else { 114 | newEntry.priority = baEnum.ASN1_NO_PRIORITY; 115 | } 116 | values.push(newEntry); 117 | } 118 | return { 119 | len, 120 | subscriberProcessId, 121 | initiatingDeviceId, 122 | monitoredObjectId, 123 | timeRemaining, 124 | values 125 | }; 126 | }; 127 | -------------------------------------------------------------------------------- /lib/services/create-object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, objectId, values) => { 7 | baAsn1.encodeOpeningTag(buffer, 0); 8 | baAsn1.encodeContextObjectId(buffer, 1, objectId.type, objectId.instance); 9 | baAsn1.encodeClosingTag(buffer, 0); 10 | baAsn1.encodeOpeningTag(buffer, 1); 11 | values.forEach((propertyValue) => { 12 | baAsn1.encodeContextEnumerated(buffer, 0, propertyValue.property.id); 13 | if (propertyValue.property.index !== baEnum.ASN1_ARRAY_ALL) { 14 | baAsn1.encodeContextUnsigned(buffer, 1, propertyValue.property.index); 15 | } 16 | baAsn1.encodeOpeningTag(buffer, 2); 17 | propertyValue.value.forEach((value) => { 18 | baAsn1.bacappEncodeApplicationData(buffer, value); 19 | }); 20 | baAsn1.encodeClosingTag(buffer, 2); 21 | if (propertyValue.priority !== baEnum.ASN1_NO_PRIORITY) { 22 | baAsn1.encodeContextUnsigned(buffer, 3, propertyValue.priority); 23 | } 24 | }); 25 | baAsn1.encodeClosingTag(buffer, 1); 26 | }; 27 | 28 | module.exports.decode = (buffer, offset, apduLen) => { 29 | let len = 0; 30 | let result; 31 | let decodedValue; 32 | let objectId; 33 | const valueList = []; 34 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 35 | len += result.len; 36 | if ((result.tagNumber === 0) && (apduLen > len)) { 37 | apduLen -= len; 38 | if (apduLen < 4) { 39 | return undefined; 40 | } 41 | decodedValue = baAsn1.decodeContextObjectId(buffer, offset + len, 1); 42 | len += decodedValue.len; 43 | objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 44 | } else { 45 | return undefined; 46 | } 47 | if (baAsn1.decodeIsClosingTag(buffer, offset + len)) { 48 | len++; 49 | } 50 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 1)) { 51 | return undefined; 52 | } 53 | len++; 54 | while ((apduLen - len) > 1) { 55 | let newEntry = {}; 56 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 57 | len += result.len; 58 | if (result.tagNumber !== 0) { 59 | return undefined; 60 | } 61 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 62 | len += decodedValue.len; 63 | let propertyId = decodedValue.value; 64 | let arraIndex = baEnum.ASN1_ARRAY_ALL; 65 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 66 | len += result.len; 67 | if (result.tagNumber === 1) { 68 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 69 | len += decodedValue.len; 70 | arraIndex += decodedValue.value; 71 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 72 | len += result.len; 73 | } 74 | newEntry.property = {id: propertyId, index: arraIndex}; 75 | if ((result.tagNumber === 2) && (baAsn1.decodeIsOpeningTag(buffer, offset + len - 1))) { 76 | const values = []; 77 | while (!baAsn1.decodeIsClosingTag(buffer, offset + len)) { 78 | decodedValue = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, objectId.type, propertyId); 79 | if (!decodedValue) { 80 | return undefined; 81 | } 82 | len += decodedValue.len; 83 | delete decodedValue.len; 84 | values.push(decodedValue); 85 | } 86 | len++; 87 | newEntry.value = values; 88 | } else { 89 | return undefined; 90 | } 91 | valueList.push(newEntry); 92 | } 93 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 1)) { 94 | return undefined; 95 | } 96 | len++; 97 | return { 98 | len, 99 | objectId, 100 | values: valueList 101 | }; 102 | }; 103 | 104 | module.exports.encodeAcknowledge = (buffer, objectId) => { 105 | baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); 106 | }; 107 | -------------------------------------------------------------------------------- /lib/services/delete-object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.decode = (buffer, offset, apduLen) => { 7 | const result = baAsn1.decodeTagNumberAndValue(buffer, offset); 8 | if (result.tagNumber !== 12) { 9 | return undefined; 10 | } 11 | let len = 1; 12 | const value = baAsn1.decodeObjectId(buffer, offset + len); 13 | len += value.len; 14 | if (len !== apduLen) { 15 | return undefined; 16 | } 17 | value.len = len; 18 | return value; 19 | }; 20 | 21 | module.exports.encode = (buffer, objectId) => { 22 | baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/services/device-communication-control.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, timeDuration, enableDisable, password) => { 7 | if (timeDuration > 0) { 8 | baAsn1.encodeContextUnsigned(buffer, 0, timeDuration); 9 | } 10 | baAsn1.encodeContextEnumerated(buffer, 1, enableDisable); 11 | if (password && password !== '') { 12 | baAsn1.encodeContextCharacterString(buffer, 2, password); 13 | } 14 | }; 15 | 16 | module.exports.decode = (buffer, offset, apduLen) => { 17 | let len = 0; 18 | let value = {}; 19 | let decodedValue; 20 | let result; 21 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 22 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 23 | len += result.len; 24 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 25 | value.timeDuration = decodedValue.value; 26 | len += decodedValue.len; 27 | } 28 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 29 | return undefined; 30 | } 31 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 32 | len += result.len; 33 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 34 | value.enableDisable = decodedValue.value; 35 | len += decodedValue.len; 36 | if (len < apduLen) { 37 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 2)) { 38 | return undefined; 39 | } 40 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 41 | len += result.len; 42 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 43 | value.password = decodedValue.value; 44 | len += decodedValue.len; 45 | } 46 | value.len = len; 47 | return value; 48 | }; 49 | -------------------------------------------------------------------------------- /lib/services/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, errorClass, errorCode) => { 7 | baAsn1.encodeApplicationEnumerated(buffer, errorClass); 8 | baAsn1.encodeApplicationEnumerated(buffer, errorCode); 9 | }; 10 | 11 | module.exports.decode = (buffer, offset) => { 12 | const orgOffset = offset; 13 | let result; 14 | result = baAsn1.decodeTagNumberAndValue(buffer, offset); 15 | offset += result.len; 16 | const errorClass = baAsn1.decodeEnumerated(buffer, offset, result.value); 17 | offset += errorClass.len; 18 | result = baAsn1.decodeTagNumberAndValue(buffer, offset); 19 | offset += result.len; 20 | const errorCode = baAsn1.decodeEnumerated(buffer, offset, result.value); 21 | offset += errorClass.len; 22 | return { 23 | len: offset - orgOffset, 24 | class: errorClass.value, 25 | code: errorCode.value 26 | }; 27 | }; 28 | 29 | module.exports.buildMessage = function (result) { 30 | return 'BacnetError Class: ' + baEnum.ErrorClassName[result.class] + ' ' + 31 | '(' + result.class + ') ' + 32 | 'Code: ' + baEnum.ErrorCodeName[result.code] + ' (' + result.code + ')'; 33 | }; 34 | -------------------------------------------------------------------------------- /lib/services/event-information.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, events, moreEvents) => { 7 | baAsn1.encodeOpeningTag(buffer, 0); 8 | events.forEach((event) => { 9 | baAsn1.encodeContextObjectId(buffer, 0, event.objectId.type, event.objectId.instance); 10 | baAsn1.encodeContextEnumerated(buffer, 1, event.eventState); 11 | baAsn1.encodeContextBitstring(buffer, 2, event.acknowledgedTransitions); 12 | baAsn1.encodeOpeningTag(buffer, 3); 13 | for (let i = 0; i < 3; i++) { 14 | baAsn1.encodeApplicationDate(buffer, event.eventTimeStamps[i]); 15 | baAsn1.encodeApplicationTime(buffer, event.eventTimeStamps[i]); 16 | } 17 | baAsn1.encodeClosingTag(buffer, 3); 18 | baAsn1.encodeContextEnumerated(buffer, 4, event.notifyType); 19 | baAsn1.encodeContextBitstring(buffer, 5, event.eventEnable); 20 | baAsn1.encodeOpeningTag(buffer, 6); 21 | for (let i = 0; i < 3; i++) { 22 | baAsn1.encodeApplicationUnsigned(buffer, event.eventPriorities[i]); 23 | } 24 | baAsn1.encodeClosingTag(buffer, 6); 25 | }); 26 | baAsn1.encodeClosingTag(buffer, 0); 27 | baAsn1.encodeContextBoolean(buffer, 1, moreEvents); 28 | }; 29 | 30 | module.exports.decode = (buffer, offset, apduLen) => { 31 | let len = 0; 32 | let result; 33 | let decodedValue; 34 | len++; 35 | const alarms = []; 36 | let moreEvents; 37 | while ((apduLen - 3 - len) > 0) { 38 | let value = {}; 39 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 40 | len += result.len; 41 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 42 | len += decodedValue.len; 43 | value.objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 44 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 45 | len += result.len; 46 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 47 | len += decodedValue.len; 48 | value.eventState = decodedValue.value; 49 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 50 | len += result.len; 51 | decodedValue = baAsn1.decodeBitstring(buffer, offset + len, result.value); 52 | len += decodedValue.len; 53 | value.acknowledgedTransitions = decodedValue.value; 54 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 55 | len += result.len; 56 | value.eventTimeStamps = []; 57 | for (let i = 0; i < 3; i++) { 58 | if (result.tagNumber !== baEnum.ApplicationTag.NULL) { 59 | decodedValue = baAsn1.decodeApplicationDate(buffer, offset + len); 60 | len += decodedValue.len; 61 | const date = decodedValue.value.value; 62 | decodedValue = baAsn1.decodeApplicationTime(buffer, offset + len); 63 | len += decodedValue.len; 64 | const time = decodedValue.value.value; 65 | value.eventTimeStamps[i] = new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds()); 66 | } else { 67 | len += result.value; 68 | } 69 | } 70 | len++; 71 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 72 | len += result.len; 73 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 74 | len += decodedValue.len; 75 | value.notifyType = decodedValue.value; 76 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 77 | len += result.len; 78 | decodedValue = baAsn1.decodeBitstring(buffer, offset + len, result.value); 79 | len += decodedValue.len; 80 | value.eventEnable = decodedValue.value; 81 | len++; 82 | value.eventPriorities = []; 83 | for (let i = 0; i < 3; i++) { 84 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 85 | len += result.len; 86 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 87 | len += decodedValue.len; 88 | value.eventPriorities[i] = decodedValue.value; 89 | } 90 | len++; 91 | alarms.push(value); 92 | } 93 | moreEvents = (buffer[apduLen - 1] === 1); 94 | return { 95 | len, 96 | alarms, 97 | moreEvents 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /lib/services/i-am.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The iAm event represents the response to a whoIs request to detect all 3 | * devices in a BACNET network. 4 | * 5 | * @event bacnet.iAm 6 | * @param {number} deviceId - The BACNET device-id of the detected device. 7 | * @param {number} maxApdu - The max APDU size the detected device supports. 8 | * @param {number} segmentation - The type of segmentation the detected device supports. 9 | * @param {number} vendorId - The BACNET vendor-id of the detected device. 10 | * 11 | * @example 12 | * const bacnet = require('node-bacnet'); 13 | * const client = new bacnet(); 14 | * 15 | * client.on('iAm', (msg) => { 16 | * console.log( 17 | * 'address: ', msg.header.address, 18 | * ' - deviceId: ', msg.payload.deviceId, 19 | * ' - maxApdu: ', msg.payload.maxApdu, 20 | * ' - segmentation: ', msg.payload.segmentation, 21 | * ' - vendorId: ', msg.payload.vendorId 22 | * ); 23 | * }); 24 | */ 25 | 26 | 'use strict'; 27 | 28 | const baAsn1 = require('../asn1'); 29 | const baEnum = require('../enum'); 30 | 31 | module.exports.encode = (buffer, deviceId, maxApdu, segmentation, vendorId) => { 32 | baAsn1.encodeApplicationObjectId(buffer, baEnum.ObjectType.DEVICE, deviceId); 33 | baAsn1.encodeApplicationUnsigned(buffer, maxApdu); 34 | baAsn1.encodeApplicationEnumerated(buffer, segmentation); 35 | baAsn1.encodeApplicationUnsigned(buffer, vendorId); 36 | }; 37 | 38 | module.exports.decode = (buffer, offset) => { 39 | let result; 40 | let apduLen = 0; 41 | const orgOffset = offset; 42 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + apduLen); 43 | apduLen += result.len; 44 | if (result.tagNumber !== baEnum.ApplicationTag.OBJECTIDENTIFIER) { 45 | return undefined; 46 | } 47 | result = baAsn1.decodeObjectId(buffer, offset + apduLen); 48 | apduLen += result.len; 49 | if (result.objectType !== baEnum.ObjectType.DEVICE) { 50 | return undefined; 51 | } 52 | const deviceId = result.instance; 53 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + apduLen); 54 | apduLen += result.len; 55 | if (result.tagNumber !== baEnum.ApplicationTag.UNSIGNED_INTEGER) { 56 | return undefined; 57 | } 58 | result = baAsn1.decodeUnsigned(buffer, offset + apduLen, result.value); 59 | apduLen += result.len; 60 | const maxApdu = result.value; 61 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + apduLen); 62 | apduLen += result.len; 63 | if (result.tagNumber !== baEnum.ApplicationTag.ENUMERATED) { 64 | return undefined; 65 | } 66 | result = baAsn1.decodeEnumerated(buffer, offset + apduLen, result.value); 67 | apduLen += result.len; 68 | if (result.value > baEnum.Segmentation.NO_SEGMENTATION) { 69 | return undefined; 70 | } 71 | const segmentation = result.value; 72 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + apduLen); 73 | apduLen += result.len; 74 | if (result.tagNumber !== baEnum.ApplicationTag.UNSIGNED_INTEGER) { 75 | return undefined; 76 | } 77 | result = baAsn1.decodeUnsigned(buffer, offset + apduLen, result.value); 78 | apduLen += result.len; 79 | if (result.value > 0xFFFF) { 80 | return undefined; 81 | } 82 | const vendorId = result.value; 83 | return { 84 | len: offset - orgOffset, 85 | deviceId, 86 | maxApdu, 87 | segmentation, 88 | vendorId 89 | }; 90 | }; 91 | -------------------------------------------------------------------------------- /lib/services/i-have.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, deviceId, objectId, objectName) => { 7 | baAsn1.encodeApplicationObjectId(buffer, deviceId.type, deviceId.instance); 8 | baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); 9 | baAsn1.encodeApplicationCharacterString(buffer, objectName); 10 | }; 11 | 12 | module.exports.decode = (buffer, offset, apduLen) => { 13 | let len = 0; 14 | let result; 15 | let decodedValue; 16 | let value = {}; 17 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 18 | len += result.len; 19 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 20 | len += decodedValue.len; 21 | value.deviceId = {type: decodedValue.objectType, instance: decodedValue.instance}; 22 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 23 | len += result.len; 24 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 25 | len += decodedValue.len; 26 | value.objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 27 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 28 | len += result.len; 29 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 30 | len += decodedValue.len; 31 | value.objectName = decodedValue.value; 32 | value.len = len; 33 | return value; 34 | }; 35 | -------------------------------------------------------------------------------- /lib/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.addListElement = require('./add-list-element'); 4 | module.exports.alarmAcknowledge = require('./alarm-acknowledge'); 5 | module.exports.alarmSummary = require('./alarm-summary'); 6 | module.exports.atomicReadFile = require('./atomic-read-file'); 7 | module.exports.atomicWriteFile = require('./atomic-write-file'); 8 | module.exports.covNotify = require('./cov-notify'); 9 | module.exports.covNotifyUnconfirmed = module.exports.covNotify; 10 | module.exports.createObject = require('./create-object'); 11 | module.exports.deleteObject = require('./delete-object'); 12 | module.exports.deviceCommunicationControl = require('./device-communication-control'); 13 | module.exports.error = require('./error'); 14 | module.exports.eventInformation = require('./event-information'); 15 | module.exports.eventNotifyData = require('./event-notify-data'); 16 | module.exports.getEnrollmentSummary = require('./get-enrollment-summary'); 17 | module.exports.getEventInformation = require('./get-event-information'); 18 | module.exports.iAm = require('./i-am'); 19 | module.exports.iHave = require('./i-have'); 20 | module.exports.lifeSafetyOperation = require('./life-safety-operation'); 21 | module.exports.privateTransfer = require('./private-transfer'); 22 | module.exports.readProperty = require('./read-property'); 23 | module.exports.readPropertyMultiple = require('./read-property-multiple'); 24 | module.exports.readRange = require('./read-range'); 25 | module.exports.registerForeignDevice = require('./register-foreign-device'); 26 | module.exports.reinitializeDevice = require('./reinitialize-device'); 27 | module.exports.subscribeCov = require('./subscribe-cov'); 28 | module.exports.subscribeProperty = require('./subscribe-property'); 29 | module.exports.timeSync = require('./time-sync'); 30 | module.exports.timeSyncUTC = module.exports.timeSync; 31 | module.exports.whoHas = require('./who-has'); 32 | module.exports.whoIs = require('./who-is'); 33 | module.exports.writeProperty = require('./write-property'); 34 | module.exports.writePropertyMultiple = require('./write-property-multiple'); 35 | -------------------------------------------------------------------------------- /lib/services/life-safety-operation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, processId, requestingSource, operation, targetObjectId) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, processId); 8 | baAsn1.encodeContextCharacterString(buffer, 1, requestingSource); 9 | baAsn1.encodeContextEnumerated(buffer, 2, operation); 10 | baAsn1.encodeContextObjectId(buffer, 3, targetObjectId.type, targetObjectId.instance); 11 | }; 12 | 13 | module.exports.decode = (buffer, offset, apduLen) => { 14 | let len = 0; 15 | let result; 16 | let decodedValue; 17 | let value = {}; 18 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 19 | len += result.len; 20 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 21 | len += decodedValue.len; 22 | value.processId = decodedValue.value; 23 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 24 | len += result.len; 25 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 26 | len += decodedValue.len; 27 | value.requestingSource = decodedValue.value; 28 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 29 | len += result.len; 30 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 31 | len += decodedValue.len; 32 | value.operation = decodedValue.value; 33 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 34 | len += result.len; 35 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 36 | len += decodedValue.len; 37 | value.targetObjectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 38 | value.len = len; 39 | return value; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/services/private-transfer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, vendorId, serviceNumber, data) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, vendorId); 8 | baAsn1.encodeContextUnsigned(buffer, 1, serviceNumber); 9 | baAsn1.encodeOpeningTag(buffer, 2); 10 | for (let i = 0; i < data.length; i++) { 11 | buffer.buffer[buffer.offset++] = data[i]; 12 | } 13 | baAsn1.encodeClosingTag(buffer, 2); 14 | }; 15 | 16 | module.exports.decode = (buffer, offset, apduLen) => { 17 | let len = 0; 18 | let result; 19 | let decodedValue; 20 | let value = {}; 21 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 22 | len += result.len; 23 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 24 | len += decodedValue.len; 25 | value.vendorId = decodedValue.value; 26 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 27 | len += result.len; 28 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 29 | len += decodedValue.len; 30 | value.serviceNumber = decodedValue.value; 31 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 32 | len += result.len; 33 | const size = apduLen - (offset + len + 1); 34 | const data = []; 35 | for (let i = 0; i < size; i++) { 36 | data.push(buffer[offset + len++]); 37 | } 38 | value.data = data; 39 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 40 | len += result.len; 41 | value.len = len; 42 | return value; 43 | }; 44 | -------------------------------------------------------------------------------- /lib/services/read-property-multiple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, properties) => { 7 | properties.forEach((value) => { 8 | baAsn1.encodeReadAccessSpecification(buffer, value); 9 | }); 10 | }; 11 | 12 | module.exports.decode = (buffer, offset, apduLen) => { 13 | let len = 0; 14 | const values = []; 15 | while ((apduLen - len) > 0) { 16 | const decodedValue = baAsn1.decodeReadAccessSpecification(buffer, offset + len, apduLen - len); 17 | if (!decodedValue) { 18 | return undefined; 19 | } 20 | len += decodedValue.len; 21 | values.push(decodedValue.value); 22 | } 23 | return { 24 | len, 25 | properties: values 26 | }; 27 | }; 28 | 29 | module.exports.encodeAcknowledge = (buffer, values) => { 30 | values.forEach((value) => { 31 | baAsn1.encodeReadAccessResult(buffer, value); 32 | }); 33 | }; 34 | 35 | module.exports.decodeAcknowledge = (buffer, offset, apduLen) => { 36 | let len = 0; 37 | const values = []; 38 | while ((apduLen - len) > 0) { 39 | const result = baAsn1.decodeReadAccessResult(buffer, offset + len, apduLen - len); 40 | if (!result) { 41 | return undefined; 42 | } 43 | len += result.len; 44 | values.push(result.value); 45 | } 46 | return { 47 | len, 48 | values 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /lib/services/read-property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, objectType, objectInstance, propertyId, arrayIndex) => { 7 | if (objectType <= baEnum.ASN1_MAX_OBJECT) { 8 | baAsn1.encodeContextObjectId(buffer, 0, objectType, objectInstance); 9 | } 10 | if (propertyId <= baEnum.ASN1_MAX_PROPERTY_ID) { 11 | baAsn1.encodeContextEnumerated(buffer, 1, propertyId); 12 | } 13 | if (arrayIndex !== baEnum.ASN1_ARRAY_ALL) { 14 | baAsn1.encodeContextUnsigned(buffer, 2, (arrayIndex || arrayIndex === 0) ? arrayIndex : baEnum.ASN1_ARRAY_ALL); 15 | } 16 | }; 17 | 18 | module.exports.decode = (buffer, offset, apduLen) => { 19 | let len = 0; 20 | let result; 21 | let decodedValue; 22 | if (apduLen < 7) { 23 | return undefined; 24 | } 25 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 26 | return undefined; 27 | } 28 | len++; 29 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 30 | len += decodedValue.len; 31 | let objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 32 | let property = {}; 33 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 34 | len += result.len; 35 | if (result.tagNumber !== 1) { 36 | return undefined; 37 | } 38 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 39 | len += decodedValue.len; 40 | property.id = decodedValue.value; 41 | property.index = baEnum.ASN1_ARRAY_ALL; 42 | if (len < apduLen) { 43 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 44 | len += result.len; 45 | if ((result.tagNumber === 2) && (len < apduLen)) { 46 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 47 | len += decodedValue.len; 48 | property.index = decodedValue.value; 49 | } else { 50 | return undefined; 51 | } 52 | } 53 | if (len < apduLen) { 54 | return undefined; 55 | } 56 | return { 57 | len, 58 | objectId, 59 | property 60 | }; 61 | }; 62 | 63 | module.exports.encodeAcknowledge = (buffer, objectId, propertyId, arrayIndex, values) => { 64 | baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance); 65 | baAsn1.encodeContextEnumerated(buffer, 1, propertyId); 66 | if (arrayIndex !== baEnum.ASN1_ARRAY_ALL) { 67 | baAsn1.encodeContextUnsigned(buffer, 2, arrayIndex); 68 | } 69 | baAsn1.encodeOpeningTag(buffer, 3); 70 | values.forEach((value) => { 71 | baAsn1.bacappEncodeApplicationData(buffer, value); 72 | }); 73 | baAsn1.encodeClosingTag(buffer, 3); 74 | }; 75 | 76 | module.exports.decodeAcknowledge = (buffer, offset, apduLen) => { 77 | let result; 78 | let decodedValue; 79 | let objectId = {}; 80 | let property = {}; 81 | if (!baAsn1.decodeIsContextTag(buffer, offset, 0)) { 82 | return undefined; 83 | } 84 | let len = 1; 85 | result = baAsn1.decodeObjectId(buffer, offset + len); 86 | len += result.len; 87 | objectId.type = result.objectType; 88 | objectId.instance = result.instance; 89 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 90 | len += result.len; 91 | if (result.tagNumber !== 1) { 92 | return undefined; 93 | } 94 | result = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 95 | len += result.len; 96 | property.id = result.value; 97 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 98 | if (result.tagNumber === 2) { 99 | len += result.len; 100 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 101 | len += decodedValue.len; 102 | property.index = decodedValue.value; 103 | } else { 104 | property.index = baEnum.ASN1_ARRAY_ALL; 105 | } 106 | const values = []; 107 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 3)) { 108 | return undefined; 109 | } 110 | len++; 111 | while ((apduLen - len) > 1) { 112 | result = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, objectId.type, property.id); 113 | if (!result) { 114 | return undefined; 115 | } 116 | len += result.len; 117 | delete result.len; 118 | values.push(result); 119 | } 120 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 3)) { 121 | return undefined; 122 | } 123 | len++; 124 | return { 125 | len, 126 | objectId, 127 | property, 128 | values 129 | }; 130 | }; 131 | -------------------------------------------------------------------------------- /lib/services/register-foreign-device.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, ttl, length = 2) => { 7 | baAsn1.encodeUnsigned(buffer, ttl, length); 8 | }; 9 | 10 | module.exports.decode = (buffer, offset, length = 2) => { 11 | let len = 0; 12 | let result = baAsn1.decodeUnsigned(buffer, offset + len, length); 13 | len += result.len; 14 | return { 15 | len, 16 | ttl: result.value, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /lib/services/reinitialize-device.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, state, password) => { 7 | baAsn1.encodeContextEnumerated(buffer, 0, state); 8 | if (password && password !== '') { 9 | baAsn1.encodeContextCharacterString(buffer, 1, password); 10 | } 11 | }; 12 | 13 | module.exports.decode = (buffer, offset, apduLen) => { 14 | let len = 0; 15 | let value = {}; 16 | let result; 17 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 18 | return undefined; 19 | } 20 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 21 | len += result.len; 22 | let decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 23 | value.state = decodedValue.value; 24 | len += decodedValue.len; 25 | if (len < apduLen) { 26 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 27 | return undefined; 28 | } 29 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 30 | len += result.len; 31 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 32 | value.password = decodedValue.value; 33 | len += decodedValue.len; 34 | } 35 | value.len = len; 36 | return value; 37 | }; 38 | -------------------------------------------------------------------------------- /lib/services/subscribe-cov.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, subscriberProcessId, monitoredObjectId, cancellationRequest, issueConfirmedNotifications, lifetime) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, subscriberProcessId); 8 | baAsn1.encodeContextObjectId(buffer, 1, monitoredObjectId.type, monitoredObjectId.instance); 9 | if (!cancellationRequest) { 10 | baAsn1.encodeContextBoolean(buffer, 2, issueConfirmedNotifications); 11 | baAsn1.encodeContextUnsigned(buffer, 3, lifetime); 12 | } 13 | }; 14 | 15 | module.exports.decode = (buffer, offset, apduLen) => { 16 | let len = 0; 17 | let value = {}; 18 | let result; 19 | let decodedValue; 20 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 21 | return undefined; 22 | } 23 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 24 | len += result.len; 25 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 26 | len += decodedValue.len; 27 | value.subscriberProcessId = decodedValue.value; 28 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 29 | return undefined; 30 | } 31 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 32 | len += result.len; 33 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 34 | len += decodedValue.len; 35 | value.monitoredObjectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 36 | value.cancellationRequest = true; 37 | if (len < apduLen) { 38 | value.issueConfirmedNotifications = false; 39 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 2)) { 40 | value.cancellationRequest = false; 41 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 42 | len += result.len; 43 | value.issueConfirmedNotifications = buffer[offset + len] > 0; 44 | len++; 45 | } 46 | value.lifetime = 0; 47 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 3)) { 48 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 49 | len += result.len; 50 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 51 | len += decodedValue.len; 52 | value.lifetime = decodedValue.value; 53 | } 54 | } 55 | value.len = len; 56 | return value; 57 | }; 58 | -------------------------------------------------------------------------------- /lib/services/subscribe-property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, subscriberProcessId, monitoredObjectId, cancellationRequest, issueConfirmedNotifications, lifetime, monitoredProperty, covIncrementPresent, covIncrement) => { 7 | baAsn1.encodeContextUnsigned(buffer, 0, subscriberProcessId); 8 | baAsn1.encodeContextObjectId(buffer, 1, monitoredObjectId.type, monitoredObjectId.instance); 9 | if (!cancellationRequest) { 10 | baAsn1.encodeContextBoolean(buffer, 2, issueConfirmedNotifications); 11 | baAsn1.encodeContextUnsigned(buffer, 3, lifetime); 12 | } 13 | baAsn1.encodeOpeningTag(buffer, 4); 14 | baAsn1.encodeContextEnumerated(buffer, 0, monitoredProperty.id); 15 | if (monitoredProperty.index !== baEnum.ASN1_ARRAY_ALL) { 16 | baAsn1.encodeContextUnsigned(buffer, 1, monitoredProperty.index); 17 | } 18 | baAsn1.encodeClosingTag(buffer, 4); 19 | if (covIncrementPresent) { 20 | baAsn1.encodeContextReal(buffer, 5, covIncrement); 21 | } 22 | }; 23 | 24 | module.exports.decode = (buffer, offset) => { 25 | let len = 0; 26 | let value = {}; 27 | let result; 28 | let decodedValue; 29 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 30 | return undefined; 31 | } 32 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 33 | len += result.len; 34 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 35 | len += decodedValue.len; 36 | value.subscriberProcessId = decodedValue.value; 37 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 38 | return undefined; 39 | } 40 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 41 | len += result.len; 42 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 43 | len += decodedValue.len; 44 | value.monitoredObjectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 45 | value.cancellationRequest = true; 46 | value.issueConfirmedNotifications = false; 47 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 2)) { 48 | value.cancellationRequest = false; 49 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 50 | len += result.len; 51 | value.issueConfirmedNotifications = buffer[offset + len] > 0; 52 | len++; 53 | } 54 | value.lifetime = 0; 55 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 3)) { 56 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 57 | len += result.len; 58 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 59 | len += decodedValue.len; 60 | value.lifetime = decodedValue.value; 61 | } 62 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 4)) { 63 | return undefined; 64 | } 65 | len++; 66 | value.monitoredProperty = {}; 67 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 68 | return undefined; 69 | } 70 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 71 | len += result.len; 72 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 73 | len += decodedValue.len; 74 | value.monitoredProperty.id = decodedValue.value; 75 | value.monitoredProperty.index = baEnum.ASN1_ARRAY_ALL; 76 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 1)) { 77 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 78 | len += result.len; 79 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 80 | len += decodedValue.len; 81 | value.monitoredProperty.index = decodedValue.value; 82 | } 83 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 4)) { 84 | return undefined; 85 | } 86 | len++; 87 | value.covIncrement = 0; 88 | if (baAsn1.decodeIsContextTag(buffer, offset + len, 5)) { 89 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 90 | len += result.len; 91 | decodedValue = baAsn1.decodeReal(buffer, offset + len); 92 | len += decodedValue.len; 93 | value.covIncrement = decodedValue.value; 94 | } 95 | value.len = len; 96 | return value; 97 | }; 98 | -------------------------------------------------------------------------------- /lib/services/time-sync.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The timeSync event represents the request to synchronize the local time to 3 | * the received time. 4 | * 5 | * @event bacnet.timeSync 6 | * @param {date} dateTime - The time to be synchronized to. 7 | * 8 | * @example 9 | * const bacnet = require('node-bacnet'); 10 | * const client = new bacnet(); 11 | * 12 | * client.on('timeSync', (msg) => { 13 | * console.log( 14 | * 'address: ', msg.header.address, 15 | * ' - dateTime: ', msg.payload.dateTime 16 | * ); 17 | * }); 18 | */ 19 | 20 | 'use strict'; 21 | 22 | const baAsn1 = require('../asn1'); 23 | const baEnum = require('../enum'); 24 | 25 | module.exports.encode = (buffer, time) => { 26 | baAsn1.encodeApplicationDate(buffer, time); 27 | baAsn1.encodeApplicationTime(buffer, time); 28 | }; 29 | 30 | module.exports.decode = (buffer, offset, length) => { 31 | let len = 0; 32 | let result; 33 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 34 | len += result.len; 35 | if (result.tagNumber !== baEnum.ApplicationTag.DATE) { 36 | return undefined; 37 | } 38 | const date = baAsn1.decodeDate(buffer, offset + len); 39 | len += date.len; 40 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 41 | len += result.len; 42 | if (result.tagNumber !== baEnum.ApplicationTag.TIME) { 43 | return undefined; 44 | } 45 | const time = baAsn1.decodeBacnetTime(buffer, offset + len); 46 | len += time.len; 47 | return { 48 | len, 49 | value: new Date(date.value.getFullYear(), date.value.getMonth(), date.value.getDate(), time.value.getHours(), time.value.getMinutes(), time.value.getSeconds(), time.value.getMilliseconds()) 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /lib/services/who-has.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, lowLimit, highLimit, objectId, objectName) => { 7 | if ((lowLimit >= 0) && (lowLimit <= baEnum.ASN1_MAX_INSTANCE) && (highLimit >= 0) && (highLimit <= baEnum.ASN1_MAX_INSTANCE)) { 8 | baAsn1.encodeContextUnsigned(buffer, 0, lowLimit); 9 | baAsn1.encodeContextUnsigned(buffer, 1, highLimit); 10 | } 11 | if (objectName && objectName !== '') { 12 | baAsn1.encodeContextCharacterString(buffer, 3, objectName); 13 | } else { 14 | baAsn1.encodeContextObjectId(buffer, 2, objectId.type, objectId.instance); 15 | } 16 | }; 17 | 18 | module.exports.decode = (buffer, offset, apduLen) => { 19 | let len = 0; 20 | let value = {}; 21 | let decodedValue; 22 | let result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 23 | len += result.len; 24 | if (result.tagNumber === 0) { 25 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 26 | len += decodedValue.len; 27 | if (decodedValue.value <= baEnum.ASN1_MAX_INSTANCE) { 28 | value.lowLimit = decodedValue.value; 29 | } 30 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 31 | len += result.len; 32 | } 33 | if (result.tagNumber === 1) { 34 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 35 | len += decodedValue.len; 36 | if (decodedValue.value <= baEnum.ASN1_MAX_INSTANCE) { 37 | value.highLimit = decodedValue.value; 38 | } 39 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 40 | len += result.len; 41 | } 42 | if (result.tagNumber === 2) { 43 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 44 | len += decodedValue.len; 45 | value.objectId = {type: decodedValue.objectType, instance: decodedValue.instance}; 46 | } 47 | if (result.tagNumber === 3) { 48 | decodedValue = baAsn1.decodeCharacterString(buffer, offset + len, apduLen - (offset + len), result.value); 49 | len += decodedValue.len; 50 | value.objectName = decodedValue.value; 51 | } 52 | value.len = len; 53 | return value; 54 | }; 55 | -------------------------------------------------------------------------------- /lib/services/who-is.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The whoIs event represents the request for an IAm reponse to detect all 3 | * devices in a BACNET network. 4 | * 5 | * @event bacnet.whoIs 6 | * @param {number=} lowLimit - The lowest BACnet ID being queried. 7 | * @param {number=} highLimit - The highest BACnet ID being queried. 8 | * 9 | * @example 10 | * const bacnet = require('node-bacnet'); 11 | * const client = new bacnet(); 12 | * 13 | * client.on('whoIs', (msg) => { 14 | * console.log( 15 | * 'address: ', msg.header.address, 16 | * ' - lowLimit: ', msg.payload.lowLimit, 17 | * ' - highLimit: ', msg.payload.highLimit 18 | * ); 19 | * }); 20 | */ 21 | 22 | 'use strict'; 23 | 24 | const baAsn1 = require('../asn1'); 25 | const baEnum = require('../enum'); 26 | 27 | module.exports.encode = (buffer, lowLimit, highLimit) => { 28 | if ((lowLimit >= 0) && (lowLimit <= baEnum.ASN1_MAX_INSTANCE) && (highLimit >= 0) && (highLimit <= baEnum.ASN1_MAX_INSTANCE)) { 29 | baAsn1.encodeContextUnsigned(buffer, 0, lowLimit); 30 | baAsn1.encodeContextUnsigned(buffer, 1, highLimit); 31 | } 32 | }; 33 | 34 | module.exports.decode = (buffer, offset, apduLen) => { 35 | let len = 0; 36 | let value = {}; 37 | if (apduLen <= 0) { 38 | return {}; // TODO: why?? 39 | } 40 | let result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 41 | len += result.len; 42 | if (result.tagNumber !== 0) { 43 | return undefined; 44 | } 45 | if (apduLen <= len) { 46 | return undefined; 47 | } 48 | let decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 49 | len += decodedValue.len; 50 | if (decodedValue.value <= baEnum.ASN1_MAX_INSTANCE) { 51 | value.lowLimit = decodedValue.value; 52 | } 53 | if (apduLen <= len) { 54 | return undefined; 55 | } 56 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 57 | len += result.len; 58 | if (result.tagNumber !== 1) { 59 | return undefined; 60 | } 61 | if (apduLen <= len) { 62 | return undefined; 63 | } 64 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 65 | len += decodedValue.len; 66 | if (decodedValue.value <= baEnum.ASN1_MAX_INSTANCE) { 67 | value.highLimit = decodedValue.value; 68 | } 69 | value.len = len; 70 | return value; 71 | }; 72 | -------------------------------------------------------------------------------- /lib/services/write-property-multiple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | const encode = module.exports.encode = (buffer, objectId, values) => { 7 | baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance); 8 | baAsn1.encodeOpeningTag(buffer, 1); 9 | values.forEach((pValue) => { 10 | baAsn1.encodeContextEnumerated(buffer, 0, pValue.property.id); 11 | if (pValue.property.index !== baEnum.ASN1_ARRAY_ALL) { 12 | baAsn1.encodeContextUnsigned(buffer, 1, pValue.property.index); 13 | } 14 | baAsn1.encodeOpeningTag(buffer, 2); 15 | pValue.value.forEach((value) => { 16 | baAsn1.bacappEncodeApplicationData(buffer, value); 17 | }); 18 | baAsn1.encodeClosingTag(buffer, 2); 19 | if (pValue.priority !== baEnum.ASN1_NO_PRIORITY) { 20 | baAsn1.encodeContextUnsigned(buffer, 3, pValue.priority); 21 | } 22 | }); 23 | baAsn1.encodeClosingTag(buffer, 1); 24 | }; 25 | 26 | module.exports.decode = (buffer, offset, apduLen) => { 27 | let len = 0; 28 | let result; 29 | let decodedValue; 30 | let objectId; 31 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 32 | len += result.len; 33 | if ((result.tagNumber !== 0) || (apduLen <= len)) { 34 | return undefined; 35 | } 36 | apduLen -= len; 37 | if (apduLen < 4) { 38 | return undefined; 39 | } 40 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 41 | len += decodedValue.len; 42 | objectId = { 43 | type: decodedValue.objectType, 44 | instance: decodedValue.instance 45 | }; 46 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 1)) { 47 | return undefined; 48 | } 49 | len++; 50 | const _values = []; 51 | while ((apduLen - len) > 1) { 52 | let newEntry = {}; 53 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 54 | len += result.len; 55 | if (result.tagNumber !== 0) { 56 | return undefined; 57 | } 58 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 59 | len += decodedValue.len; 60 | let propertyId = decodedValue.value; 61 | let arrayIndex = baEnum.ASN1_ARRAY_ALL; 62 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 63 | len += result.len; 64 | if (result.tagNumber === 1) { 65 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 66 | len += decodedValue.len; 67 | arrayIndex = decodedValue.value; 68 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 69 | len += result.len; 70 | } 71 | newEntry.property = {id: propertyId, index: arrayIndex}; 72 | if ((result.tagNumber !== 2) || (!baAsn1.decodeIsOpeningTag(buffer, offset + len - 1))) { 73 | return undefined; 74 | } 75 | const values = []; 76 | while ((len + offset) <= buffer.length && !baAsn1.decodeIsClosingTag(buffer, offset + len)) { 77 | let value = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, objectId.type, propertyId); 78 | if (!value) { 79 | return undefined; 80 | } 81 | len += value.len; 82 | delete value.len; 83 | values.push(value); 84 | } 85 | len++; 86 | newEntry.value = values; 87 | let priority = baEnum.ASN1_NO_PRIORITY; 88 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 89 | len += result.len; 90 | if (result.tagNumber === 3) { 91 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 92 | len += decodedValue.len; 93 | priority = decodedValue.value; 94 | } else { 95 | len--; 96 | } 97 | newEntry.priority = priority; 98 | _values.push(newEntry); 99 | if (baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 1)) { 100 | len++; 101 | } 102 | } 103 | if (baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 1)) { 104 | len++; 105 | } 106 | return { 107 | len, 108 | objectId, 109 | values: _values 110 | }; 111 | }; 112 | 113 | module.exports.encodeObject = (buffer, values) => { 114 | values.forEach((object) => { 115 | encode(buffer, object.objectId, object.values); 116 | }); 117 | }; 118 | -------------------------------------------------------------------------------- /lib/services/write-property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const baAsn1 = require('../asn1'); 4 | const baEnum = require('../enum'); 5 | 6 | module.exports.encode = (buffer, objectType, objectInstance, propertyId, arrayIndex, priority, values) => { 7 | baAsn1.encodeContextObjectId(buffer, 0, objectType, objectInstance); 8 | baAsn1.encodeContextEnumerated(buffer, 1, propertyId); 9 | if (arrayIndex !== baEnum.ASN1_ARRAY_ALL) { 10 | baAsn1.encodeContextUnsigned(buffer, 2, arrayIndex); 11 | } 12 | baAsn1.encodeOpeningTag(buffer, 3); 13 | values.forEach((value) => { 14 | baAsn1.bacappEncodeApplicationData(buffer, value); 15 | }); 16 | baAsn1.encodeClosingTag(buffer, 3); 17 | if (priority !== baEnum.ASN1_NO_PRIORITY) { 18 | baAsn1.encodeContextUnsigned(buffer, 4, priority); 19 | } 20 | }; 21 | 22 | module.exports.decode = (buffer, offset, apduLen) => { 23 | let len = 0; 24 | let value = { 25 | property: {} 26 | }; 27 | let decodedValue; 28 | let result; 29 | if (!baAsn1.decodeIsContextTag(buffer, offset + len, 0)) { 30 | return undefined; 31 | } 32 | len++; 33 | decodedValue = baAsn1.decodeObjectId(buffer, offset + len); 34 | let objectId = { 35 | type: decodedValue.objectType, 36 | instance: decodedValue.instance 37 | }; 38 | len += decodedValue.len; 39 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 40 | len += result.len; 41 | if (result.tagNumber !== 1) { 42 | return undefined; 43 | } 44 | decodedValue = baAsn1.decodeEnumerated(buffer, offset + len, result.value); 45 | len += decodedValue.len; 46 | value.property.id = decodedValue.value; 47 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 48 | if (result.tagNumber === 2) { 49 | len += result.len; 50 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 51 | len += decodedValue.len; 52 | value.property.index = decodedValue.value; 53 | } else { 54 | value.property.index = baEnum.ASN1_ARRAY_ALL; 55 | } 56 | if (!baAsn1.decodeIsOpeningTagNumber(buffer, offset + len, 3)) { 57 | return undefined; 58 | } 59 | len++; 60 | const values = []; 61 | while ((apduLen - len) > 1 && !baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 3)) { 62 | decodedValue = baAsn1.bacappDecodeApplicationData(buffer, offset + len, apduLen + offset, objectId.type, value.property.id); 63 | if (!decodedValue) { 64 | return undefined; 65 | } 66 | len += decodedValue.len; 67 | delete decodedValue.len; 68 | values.push(decodedValue); 69 | } 70 | value.value = values; 71 | if (!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 3)) { 72 | return undefined; 73 | } 74 | len++; 75 | value.priority = baEnum.ASN1_MAX_PRIORITY; 76 | if (len < apduLen) { 77 | result = baAsn1.decodeTagNumberAndValue(buffer, offset + len); 78 | if (result.tagNumber === 4) { 79 | len += result.len; 80 | decodedValue = baAsn1.decodeUnsigned(buffer, offset + len, result.value); 81 | len += decodedValue; 82 | if ((decodedValue.value >= baEnum.ASN1_MIN_PRIORITY) && (decodedValue.value <= baEnum.ASN1_MAX_PRIORITY)) { 83 | value.priority = decodedValue.value; 84 | } else { 85 | return undefined; 86 | } 87 | } 88 | } 89 | return { 90 | len, 91 | objectId, 92 | value 93 | }; 94 | }; 95 | -------------------------------------------------------------------------------- /lib/transport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const createSocket = require('dgram').createSocket; 4 | const EventEmitter = require('events').EventEmitter; 5 | const debug = require('debug')('bacnet:transport:debug'); 6 | const trace = require('debug')('bacnet:transport:trace'); 7 | 8 | const DEFAULT_BACNET_PORT = 47808; 9 | 10 | class Transport extends EventEmitter { 11 | constructor(settings) { 12 | super(); 13 | this._lastSendMessages = {}; 14 | this._settings = settings; 15 | this._server = createSocket({type: 'udp4', reuseAddr: settings.reuseAddr}); 16 | this._server.on('message', (msg, rinfo) => { 17 | // Check for pot. duplicate messages 18 | if (this.ownAddress.port === rinfo.port) { 19 | for (let [messageKey, earlierSentBuffer] of Object.entries(this._lastSendMessages)) { 20 | if (msg.equals(earlierSentBuffer)) { 21 | debug(`server IGNORE message from ${rinfo.address}:${rinfo.port} (${messageKey}): ${msg.toString('hex')}`); 22 | return; 23 | } 24 | } 25 | } 26 | debug(`server got message from ${rinfo.address}:${rinfo.port}: ${msg.toString('hex')}`); 27 | this.emit('message', msg, rinfo.address + (rinfo.port === DEFAULT_BACNET_PORT ? '' : ':' + rinfo.port)); 28 | }); 29 | this._server.on('listening', () => { 30 | this.ownAddress = this._server.address(); 31 | debug(`server listening on ${this.ownAddress.address}:${this.ownAddress.port}`); 32 | this.emit('listening', this.ownAddress); 33 | }); 34 | this._server.on('error', (err) => { 35 | debug('transport error', err.message); 36 | this.emit('error', err); 37 | }); 38 | this._server.on('close', () => { 39 | debug('transport closed'); 40 | this.emit('close'); 41 | // close is to do by the client.close() which calls the transport.close which calls the _server.close 42 | }); 43 | } 44 | 45 | getBroadcastAddress() { 46 | return this._settings.broadcastAddress; 47 | } 48 | 49 | getMaxPayload() { 50 | return 1482; 51 | } 52 | 53 | send(buffer, offset, receiver) { 54 | if (!receiver) { 55 | receiver = this.getBroadcastAddress(); 56 | const dataToSend = Buffer.alloc(offset); 57 | // Sort out broadcasted messages that we also receive 58 | // TODO Find a better way? 59 | const hrTime = process.hrtime(); 60 | const messageKey = hrTime[0] * 1000000000 + hrTime[1]; 61 | buffer.copy(dataToSend, 0, 0, offset); 62 | this._lastSendMessages[messageKey] = dataToSend; 63 | setTimeout(() => { 64 | delete this._lastSendMessages[messageKey]; 65 | }, 10000); // delete after 10s, hopefully all cases are handled by that 66 | } 67 | const [address, port] = receiver.split(':'); 68 | debug('Send packet to ' + receiver + ': ' + buffer.toString('hex').substr(0, offset * 2)); 69 | this._server.send(buffer, 0, offset, port || DEFAULT_BACNET_PORT, address); 70 | } 71 | 72 | open() { 73 | this._server.bind(this._settings.port, this._settings.interface, () => { 74 | this._server.setBroadcast(true); 75 | }); 76 | } 77 | 78 | close() { 79 | this._server.close(); 80 | } 81 | } 82 | module.exports = Transport; 83 | -------------------------------------------------------------------------------- /npm-update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # this sh is to see possible updates from NPM 4 | 5 | npm cache verify 6 | 7 | npm outdated --depth=0 8 | 9 | npm install 10 | -------------------------------------------------------------------------------- /npm-upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # this sh is to upgrade all package dependencies from NPM 4 | # you need to install before: npm i -g npm-check-updates 5 | 6 | rm package-lock.json 7 | 8 | npm cache verify 9 | 10 | npm outdated --depth=0 11 | 12 | ncu -u -m 13 | 14 | npm install 15 | 16 | npm run build 17 | 18 | npm test 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-bacnet", 3 | "version": "0.2.4", 4 | "description": "The BACnet protocol library written in pure JavaScript.", 5 | "main": "index.js", 6 | "scripts": { 7 | "changelog": "conventional-changelog -p conventail -i CHANGELOG.md -s -r 0", 8 | "lint": "jshint lib/ test/ index.js && eslint lib/** test/** examples/** index.js", 9 | "lint:fix": "eslint --fix lib/** test/** examples/** index.js", 10 | "lint:fix:dry": "eslint --fix-dry-run lib/** test/** examples/** index.js", 11 | "test": "npm run lint && npm run test:unit && npm run test:integration", 12 | "coverage": "nyc --reporter=lcov --reporter=text-summary --report-dir reports/coverage npm test", 13 | "coverage:compliance": "nyc --reporter=lcov --reporter=text-summary --report-dir reports/coverage npm run test:compliance", 14 | "coverage:all": "DEBUG=bacnet* nyc --reporter=lcov --reporter=text-summary --report-dir /reports/coverage npm run test:all", 15 | "test:unit": "DEBUG=bacnet* mocha test/unit/*.spec.js", 16 | "test:integration": "DEBUG=bacnet* mocha test/integration/*.spec.js --timeout 5000", 17 | "test:compliance": "DEBUG=bacnet* mocha test/compliance/*.spec.js --timeout 5000", 18 | "test:all": "DEBUG=bacnet* mocha test/unit/*.spec.js test/integration/*.spec.js test/compliance/*.spec.js --timeout 5000", 19 | "docs": "cp -r images docs && jsdoc -r -d ./docs -t node_modules/@mocha/docdash ./lib ./index.js ./README.md", 20 | "release": "standard-version -a", 21 | "release:beta": "standard-version --prerelease beta", 22 | "release:alpha": "standard-version --prerelease alpha", 23 | "compose:build": "docker-compose build", 24 | "compose:up": "docker-compose up --abort-on-container-exit --exit-code-from bacnet-client", 25 | "docker": "npm run compose:build && npm run compose:up", 26 | "prepublishOnly": "npm run lint && npm run coverage && npm run docker && npm run docs" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/BiancoRoyal/node-bacstack.git" 31 | }, 32 | "keywords": [ 33 | "bacnet", 34 | "fieldbus", 35 | "building", 36 | "automation", 37 | "iot" 38 | ], 39 | "author": { 40 | "name": "Fabio Huser", 41 | "email": "fabio@fh1.ch" 42 | }, 43 | "contributors": [ 44 | { 45 | "name": "Klaus Landsdorf", 46 | "email": "klaus@iniationware.com" 47 | }, 48 | { 49 | "name": "Ingo Fischer", 50 | "email": "iobroker@fischer-ka.de" 51 | } 52 | ], 53 | "engines": { 54 | "node": ">= 10" 55 | }, 56 | "commitlint": { 57 | "extends": [ 58 | "@commitlint/config-conventional" 59 | ] 60 | }, 61 | "license": "MIT", 62 | "bugs": { 63 | "url": "https://github.com/BiancoRoyal/node-bacstack/issues" 64 | }, 65 | "homepage": "https://github.com/BiancoRoyal/node-bacstack#readme", 66 | "dependencies": { 67 | "debug": "^4.1.1", 68 | "iconv-lite": "^0.5.1", 69 | "underscore": "^1.10.2" 70 | }, 71 | "devDependencies": { 72 | "chai": "^4.2.0", 73 | "conventional-changelog-cli": "^2.0.27", 74 | "@mocha/docdash": "^2.1.3", 75 | "eslint": "^7.1.0", 76 | "jsdoc": "^3.6.4", 77 | "jshint": "^2.11.1", 78 | "mocha": "^7.1.2", 79 | "nyc": "^15.0.1", 80 | "standard-version": "^8.0.0" 81 | }, 82 | "files": [ 83 | "docs", 84 | "examples", 85 | "lib", 86 | "index.js" 87 | ], 88 | "directories": { 89 | "example": "examples", 90 | "test": "test" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /reports/coverage/git.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-bacstack/5e221994923df3cecf36295563c7f1c579af9a3b/reports/coverage/git.keep -------------------------------------------------------------------------------- /test/compliance/read-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - read property compliance', () => { 10 | let bacnetClient; 11 | let discoveredAddress; 12 | let onClose = null; 13 | 14 | before((done) => { 15 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 16 | bacnetClient.on('message', (msg, rinfo) => { 17 | debug(msg); 18 | if (rinfo) debug(rinfo); 19 | }); 20 | bacnetClient.on('iAm', (device) => { 21 | discoveredAddress = device.header.sender; 22 | }); 23 | bacnetClient.on('error', (err) => { 24 | console.error(err); 25 | bacnetClient.close(); 26 | }); 27 | bacnetClient.on('listening', () => { 28 | done(); 29 | }); 30 | }); 31 | 32 | after((done) => { 33 | setTimeout(() => { 34 | bacnetClient.close(); 35 | if (onClose) { 36 | onClose(done); 37 | } else { 38 | done(); 39 | } 40 | }, 1000); // do not close to fast 41 | }); 42 | 43 | it('should find the device simulator device', (next) => { 44 | bacnetClient.on('iAm', (device) => { 45 | if(device.payload.deviceId === utils.deviceUnderTest) { 46 | discoveredAddress = device.header.sender; 47 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 48 | expect(discoveredAddress).to.be.an('object'); 49 | expect(discoveredAddress.address).to.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/); 50 | next(); 51 | } 52 | }); 53 | bacnetClient.whoIs(); 54 | }); 55 | 56 | it('read property VENDOR_NAME (121) from device', (next) => { 57 | bacnetClient.readProperty(discoveredAddress, {type: 8, instance: utils.deviceUnderTest}, 121, (err, value) => { 58 | expect(err).to.equal(null); 59 | expect(value).to.be.an('object'); 60 | expect(value).to.deep.equal({'len':39,'objectId':{'type':8,'instance':utils.deviceUnderTest},'property':{'id':121,'index':utils.index},'values':[{'type':7,'value':'BACnet Stack at SourceForge','encoding':0}]}); 61 | next(); 62 | }); 63 | }); 64 | 65 | it('read property PRESENT_VALUE from analog-output,2 from device', (next) => { 66 | bacnetClient.readProperty(discoveredAddress, {type: 1, instance: 2}, 85, (err, value) => { 67 | expect(err).to.equal(null); 68 | expect(value).to.be.an('object'); 69 | expect(value).to.deep.equal({'len':14,'objectId':{'type':1,'instance':2},'property':{'id':85,'index':utils.index},'values':[{'type':4,'value':0}]}); 70 | next(); 71 | }); 72 | }); 73 | 74 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 75 | 76 | /* 77 | it('read property 76-3 from device', (next) => { 78 | bacnetClient.subscribeProperty(address, {type: 5, instance: 2}, {id: 85, index: utils.index}, 1000, false, false, (err, value) => { 79 | debug(err); 80 | next(); 81 | }); 82 | }); 83 | 84 | it('read property 76-3 from device', (next) => { 85 | bacnetClient.subscribeCov(discoveredAddress, {type: 5, instance: 2}, 107, false, false, 0, (err, value) => { 86 | debug(err); 87 | next(); 88 | }); 89 | }); 90 | */ 91 | }); 92 | -------------------------------------------------------------------------------- /test/compliance/subscribe-cov.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - subscribe cov compliance', () => { 10 | let bacnetClient; 11 | let discoveredAddress; 12 | let onClose = null; 13 | 14 | before((done) => { 15 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 16 | bacnetClient.on('message', (msg, rinfo) => { 17 | debug(msg); 18 | if (rinfo) debug(rinfo); 19 | }); 20 | bacnetClient.on('iAm', (device) => { 21 | discoveredAddress = device.header.sender; 22 | }); 23 | bacnetClient.on('error', (err) => { 24 | console.error(err); 25 | bacnetClient.close(); 26 | }); 27 | bacnetClient.on('listening', () => { 28 | done(); 29 | }); 30 | }); 31 | 32 | after((done) => { 33 | setTimeout(() => { 34 | bacnetClient.close(); 35 | if (onClose) { 36 | onClose(done); 37 | } else { 38 | done(); 39 | } 40 | }, 1000); // do not close to fast 41 | }); 42 | 43 | it('should find the device simulator device', (next) => { 44 | bacnetClient.on('iAm', (device) => { 45 | if(device.payload.deviceId === utils.deviceUnderTest) { 46 | discoveredAddress = device.header.sender; 47 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 48 | expect(discoveredAddress).to.be.an('object'); 49 | expect(discoveredAddress.address).to.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/); 50 | next(); 51 | } 52 | }); 53 | bacnetClient.whoIs(); 54 | }); 55 | 56 | it('subscribeCov property BINARY_VALUE,2 from device, expect error OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED', (next) => { 57 | bacnetClient.subscribeCov(discoveredAddress, {type: 5, instance: 2}, 107, false, false, 0, (err) => { 58 | expect(err.message).to.equal('BacnetError Class: OBJECT (1) Code: OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED (45)'); 59 | expect(err.bacnetErrorClass).to.equal(utils.bacnetClient.enum.ErrorClass.OBJECT); // 1 60 | expect(err.bacnetErrorCode).to.equal(utils.bacnetClient.enum.ErrorCode.OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED); // 45 61 | next(); 62 | }); 63 | }); 64 | 65 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /test/compliance/subscribe-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - subscribe property compliance', () => { 10 | let bacnetClient; 11 | let discoveredAddress; 12 | let onClose = null; 13 | 14 | before((done) => { 15 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 16 | bacnetClient.on('message', (msg, rinfo) => { 17 | debug(msg); 18 | if (rinfo) debug(rinfo); 19 | }); 20 | bacnetClient.on('iAm', (device) => { 21 | discoveredAddress = device.header.sender; 22 | }); 23 | bacnetClient.on('error', (err) => { 24 | console.error(err); 25 | bacnetClient.close(); 26 | }); 27 | bacnetClient.on('listening', () => { 28 | done(); 29 | }); 30 | }); 31 | 32 | after((done) => { 33 | setTimeout(() => { 34 | bacnetClient.close(); 35 | if (onClose) { 36 | onClose(done); 37 | } else { 38 | done(); 39 | } 40 | }, 1000); // do not close to fast 41 | }); 42 | 43 | it('should find the device simulator device', (next) => { 44 | bacnetClient.on('iAm', (device) => { 45 | if(device.payload.deviceId === utils.deviceUnderTest) { 46 | discoveredAddress = device.header.sender; 47 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 48 | expect(discoveredAddress).to.be.an('object'); 49 | expect(discoveredAddress.address).to.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/); 50 | next(); 51 | } 52 | }); 53 | bacnetClient.whoIs(); 54 | }); 55 | 56 | it('subscribe property BINARY_VALUE,2 from device, expect not supported error', (next) => { 57 | bacnetClient.subscribeProperty(discoveredAddress, {type: 5, instance: 2}, {id: 85, index: utils.index}, 1000, false, false, (err) => { 58 | expect(err.message).to.equal('BacnetAbort - Reason:9'); 59 | expect(err.bacnetAbortReason).to.equal(utils.bacnetClient.enum.AbortReason.OUT_OF_RESOURCES); // 9 60 | next(); 61 | }); 62 | }); 63 | 64 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /test/compliance/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const coreExports = { 4 | debug: require('debug')('bacnet:test:compliance:debug'), 5 | trace: require('debug')('bacnet:test:compliance:trace'), 6 | bacnetClient: require('../../'), 7 | deviceUnderTest: 1234, 8 | maxApdu: 1476, 9 | vendorId: 260, 10 | index: 4294967295, 11 | apduTimeout: 1000, 12 | clientListenerInterface: '0.0.0.0' 13 | }; 14 | 15 | module.exports = coreExports; 16 | -------------------------------------------------------------------------------- /test/compliance/who-is.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - whoIs compliance', () => { 10 | let bacnetClient; 11 | 12 | beforeEach((done) => { 13 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 14 | bacnetClient.on('message', (msg, rinfo) => { 15 | debug(msg); 16 | if (rinfo) debug(rinfo); 17 | }); 18 | bacnetClient.on('error', (err) => { 19 | console.error(err); 20 | bacnetClient.close(); 21 | }); 22 | bacnetClient.on('listening', () => { 23 | done(); 24 | }); 25 | }); 26 | 27 | afterEach((done) => { 28 | setTimeout(() => { 29 | bacnetClient.close(); 30 | done(); 31 | }, 1000); // do not close to fast 32 | }); 33 | 34 | it('should find the device simulator', (next) => { 35 | bacnetClient.on('iAm', (device) => { 36 | if(device.payload.deviceId === utils.deviceUnderTest) { 37 | expect(device.header).to.be.an('object'); 38 | expect(device.payload).to.be.an('object'); 39 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 40 | expect(device.payload.maxApdu).to.eql(utils.maxApdu); 41 | expect(device.payload.segmentation).to.eql(utils.bacnetClient.enum.Segmentation.NO_SEGMENTATION); 42 | expect(device.payload.vendorId).to.eql(utils.vendorId); 43 | expect(device.header.sender).to.be.an('object'); 44 | expect(device.header.sender).to.be.an('object'); 45 | expect(device.header.sender.address).to.be.an('string'); 46 | expect(device.header.sender.forwardedFrom).to.be.null; 47 | next(); 48 | } 49 | }); 50 | bacnetClient.whoIs(); 51 | }); 52 | 53 | it('should find the device simulator with provided min device ID', (next) => { 54 | bacnetClient.on('iAm', (device) => { 55 | if(device.payload.deviceId === utils.deviceUnderTest) { 56 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 57 | next(); 58 | } 59 | }); 60 | bacnetClient.whoIs({lowLimit: utils.deviceUnderTest - 1}); 61 | }); 62 | 63 | it('should find the device simulator with provided min/max device ID and IP', (next) => { 64 | bacnetClient.on('iAm', (device) => { 65 | if(device.payload.deviceId === utils.deviceUnderTest) { 66 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 67 | next(); 68 | } 69 | }); 70 | bacnetClient.whoIs({lowLimit: utils.deviceUnderTest -1, highLimit: utils.deviceUnderTest +1}); 71 | }); 72 | 73 | it('should NOT find any device', (next) => { 74 | bacnetClient.on('iAm', (device) => { 75 | expect(device, 'No discovery result to be expected').to.be.false; 76 | if (notFoundTimeout) { 77 | clearTimeout(notFoundTimeout); 78 | notFoundTimeout = null; 79 | } 80 | next(); 81 | }); 82 | bacnetClient.whoIs({lowLimit: utils.deviceUnderTest +1, highLimit: utils.deviceUnderTest +3}); 83 | // ok when no result came in 4s 84 | let notFoundTimeout = setTimeout(() => { 85 | notFoundTimeout = null; 86 | next(); 87 | }, 4000); 88 | }); 89 | 90 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/compliance/write-property-multiple.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - write property multiple compliance', () => { 10 | let bacnetClient; 11 | let discoveredAddress; 12 | let onClose = null; 13 | 14 | before((done) => { 15 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 16 | bacnetClient.on('message', (msg, rinfo) => { 17 | debug(msg); 18 | if (rinfo) debug(rinfo); 19 | }); 20 | bacnetClient.on('iAm', (device) => { 21 | discoveredAddress = device.header.sender; 22 | }); 23 | bacnetClient.on('error', (err) => { 24 | console.error(err); 25 | bacnetClient.close(); 26 | }); 27 | bacnetClient.on('listening', () => { 28 | done(); 29 | }); 30 | }); 31 | 32 | after((done) => { 33 | setTimeout(() => { 34 | bacnetClient.close(); 35 | if (onClose) { 36 | onClose(done); 37 | } else { 38 | done(); 39 | } 40 | }, 1000); // do not close to fast 41 | }); 42 | 43 | it('should find the device simulator device', (next) => { 44 | bacnetClient.on('iAm', (device) => { 45 | if(device.payload.deviceId === utils.deviceUnderTest) { 46 | discoveredAddress = device.header.sender; 47 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 48 | expect(discoveredAddress).to.be.an('object'); 49 | expect(discoveredAddress.address).to.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/); 50 | next(); 51 | } 52 | }); 53 | bacnetClient.whoIs(); 54 | }); 55 | 56 | it('read property PRESENT_VALUE from analog-output,2 from device', (next) => { 57 | bacnetClient.readProperty(discoveredAddress, {type: 1, instance: 2}, 85, (err, value) => { 58 | expect(err).to.equal(null); 59 | expect(value).to.be.an('object'); 60 | expect(value).to.deep.equal({'len':14,'objectId':{'type':1,'instance':2},'property':{'id':85,'index': utils.index},'values':[{'type':4,'value':0}]}); 61 | next(); 62 | }); 63 | }); 64 | 65 | it('write property using Multiple PRESENT_VALUE from analog-output,2 from device', (next) => { 66 | const values = [ 67 | {objectId: {type: 1, instance: 2}, values: [ 68 | {property: {id: 85}, value: [{type: utils.bacnetClient.enum.ApplicationTag.REAL, value: 100}], priority: 8} 69 | ]} 70 | ]; 71 | bacnetClient.writePropertyMultiple(discoveredAddress, values, (err) => { 72 | expect(err.message).to.equal('BacnetError Class: DEVICE (0) Code: CONFIGURATION_IN_PROGRESS (2)'); // not supported :-( 73 | next(); 74 | }); 75 | }); 76 | 77 | it('read property PRESENT_VALUE from analog-output,2 from device, expect written value', (next) => { 78 | bacnetClient.readProperty(discoveredAddress, {type: 1, instance: 2}, 85, (err, value) => { 79 | expect(err).to.equal(null); 80 | expect(value).to.be.an('object'); 81 | expect(value).to.deep.equal({'len':14,'objectId':{'type':1,'instance':2},'property':{'id':85,'index': utils.index},'values':[{'type':4,'value':0}]}); 82 | next(); 83 | }); 84 | }); 85 | 86 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 87 | }); 88 | -------------------------------------------------------------------------------- /test/compliance/write-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | // you need to have this run against the official backstack c 7 | // demo device started as deviceId 1234 8 | // use "npm run docker" to execute this 9 | describe('bacnet - write property compliance', () => { 10 | let bacnetClient; 11 | let discoveredAddress; 12 | let onClose = null; 13 | 14 | before((done) => { 15 | bacnetClient = new utils.bacnetClient({apduTimeout: utils.apduTimeout, interface: utils.clientListenerInterface}); 16 | bacnetClient.on('message', (msg, rinfo) => { 17 | debug(msg); 18 | if (rinfo) debug(rinfo); 19 | }); 20 | bacnetClient.on('iAm', (device) => { 21 | discoveredAddress = device.header.sender; 22 | }); 23 | bacnetClient.on('error', (err) => { 24 | console.error(err); 25 | bacnetClient.close(); 26 | }); 27 | bacnetClient.on('listening', () => { 28 | done(); 29 | }); 30 | }); 31 | 32 | after((done) => { 33 | setTimeout(() => { 34 | bacnetClient.close(); 35 | if (onClose) { 36 | onClose(done); 37 | } else { 38 | done(); 39 | } 40 | }, 1000); // do not close to fast 41 | }); 42 | 43 | it('should find the device simulator device', (next) => { 44 | bacnetClient.on('iAm', (device) => { 45 | if(device.payload.deviceId === utils.deviceUnderTest) { 46 | discoveredAddress = device.header.sender; 47 | expect(device.payload.deviceId).to.eql(utils.deviceUnderTest); 48 | expect(discoveredAddress).to.be.an('object'); 49 | expect(discoveredAddress.address).to.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/); 50 | next(); 51 | } 52 | }); 53 | bacnetClient.whoIs(); 54 | }); 55 | 56 | it('read property PRESENT_VALUE from analog-output,2 from device', (next) => { 57 | bacnetClient.readProperty(discoveredAddress, {type: 1, instance: 2}, 85, (err, value) => { 58 | expect(err).to.equal(null); 59 | expect(value).to.be.an('object'); 60 | expect(value).to.deep.equal({'len':14,'objectId':{'type':1,'instance':2},'property':{'id':85,'index': utils.index},'values':[{'type':4,'value':0}]}); 61 | next(); 62 | }); 63 | }); 64 | 65 | it('write property PRESENT_VALUE from analog-output,2 from device', (next) => { 66 | bacnetClient.writeProperty(discoveredAddress, {type: 1, instance: 2}, 85, [ 67 | {type: utils.bacnetClient.enum.ApplicationTag.REAL, value: 100} 68 | ], (err) => { 69 | expect(err).to.equal(null); 70 | next(); 71 | }); 72 | }); 73 | 74 | it('read property PRESENT_VALUE from analog-output,2 from device, expect written value', (next) => { 75 | bacnetClient.readProperty(discoveredAddress, {type: 1, instance: 2}, 85, (err, value) => { 76 | expect(err).to.equal(null); 77 | expect(value).to.be.an('object'); 78 | expect(value).to.deep.equal({'len':14,'objectId':{'type':1,'instance':2},'property':{'id':85,'index': utils.index},'values':[{'type':4,'value':100}]}); 79 | next(); 80 | }); 81 | }); 82 | 83 | // TODO tests missing for routing cases where "receiver" parameter is used to call whoIs 84 | }); 85 | -------------------------------------------------------------------------------- /test/integration/acknowledge-alarm.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - acknowledgeAlarm integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.acknowledgeAlarm('127.0.0.2', {type: 2, instance: 3}, 2, 'Alarm Acknowledge Test', 10 | {value: new Date(), type: 2}, {value: new Date(), type: 2}, 11 | (err, value) => { 12 | expect(err.message).to.eql('ERR_TIMEOUT'); 13 | expect(value).to.eql(undefined); 14 | client.close(); 15 | next(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/integration/add-list-element.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - addListElement integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.addListElement('127.0.0.2', {type: 19, instance: 101}, {id: 80, index: 0}, [ 10 | {type: 1, value: true} 11 | ], (err, value) => { 12 | expect(err.message).to.eql('ERR_TIMEOUT'); 13 | expect(value).to.eql(undefined); 14 | client.close(); 15 | next(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/integration/confirmed-cov-notification.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baEnum = require('../../lib/enum'); 6 | 7 | describe('bacnet - confirmedCOVNotification integration', () => { 8 | it('should return a timeout error if no device is available', (next) => { 9 | const client = new utils.bacnetClient({apduTimeout: 200}); 10 | client.confirmedCOVNotification( 11 | '127.0.0.2', 3, 433, {type: 2, instance: 122}, 120, 12 | [ 13 | { 14 | property: {id: 85}, 15 | value: [{type: baEnum.ApplicationTag.REAL, value: 12.3}] 16 | }, 17 | { 18 | property: {id: 111}, 19 | value: [{type: baEnum.ApplicationTag.BIT_STRING, value: 0xFFFF}] 20 | } 21 | ], 22 | (err, value) => { 23 | expect(err.message).to.eql('ERR_TIMEOUT'); 24 | expect(value).to.eql(undefined); 25 | client.close(); 26 | next(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/integration/confirmed-event-notification.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - confirmedEventNotification integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | const date = new Date(); 10 | date.setMilliseconds(880); 11 | client.confirmedEventNotification('127.0.0.2', { 12 | processId: 3, 13 | initiatingObjectId: {}, 14 | eventObjectId: {}, 15 | timeStamp: {type: 2, value: date}, 16 | notificationClass: 9, 17 | priority: 7, 18 | eventType: 2, 19 | messageText: 'Test1234$', 20 | notifyType: 1, 21 | changeOfValueTag: 0, 22 | changeOfValueChangeValue: 90, 23 | changeOfValueStatusFlags: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]} 24 | }, (err, value) => { 25 | expect(err.message).to.eql('ERR_TIMEOUT'); 26 | expect(value).to.eql(undefined); 27 | client.close(); 28 | next(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/integration/confirmed-private-transfer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - confirmedPrivateTransfer integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.confirmedPrivateTransfer('127.0.0.2', 0, 8, [0x00, 0xaa, 0xfa, 0xb1, 0x00], (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/create-object.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - createObject integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.createObject('127.0.0.2', {type: 2, instance: 300}, [ 10 | {property: {id: 85, index: 1}, value: [{type: 1, value: true}]} 11 | ], (err, value) => { 12 | expect(err.message).to.eql('ERR_TIMEOUT'); 13 | expect(value).to.eql(undefined); 14 | client.close(); 15 | next(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/integration/delete-object.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - deleteObject integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.deleteObject('127.0.0.2', {type: 2, instance: 15}, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/device-communication-control.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - deviceCommunicationControl integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.deviceCommunicationControl('127.0.0.2', 60, 1, {password: 'Test1234'}, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/get-alarm-summary.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - getAlarmSummary integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.getAlarmSummary('127.0.0.2', (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/get-enrollment-summary.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - getEnrollmentSummary integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.getEnrollmentSummary('127.0.0.2', 0, {notificationClassFilter: 5}, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/get-event-information.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - getEventInformation integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.getEventInformation('127.0.0.2', {type: 5, instance: 33}, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/read-file.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - readFile integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.readFile('127.0.0.2', {type: 10, instance: 100}, 0, 100, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/read-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - readProperty integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.readProperty('127.0.0.2', {type: 8, instance: 44301}, 28, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/read-range.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - readRange integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.readRange('127.0.0.2', {type: 20, instance: 0}, 0, 200, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/register-foreign-device.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const services = require('../../lib/services/index'); 5 | 6 | describe('bacnet - register foreign device integration', () => { 7 | // TODO: this is just documentation what it does for now - needs a review 8 | it('should encode', () => { 9 | const buffer = {buffer: Buffer.alloc(16, 12), offset: 0}; 10 | const testBuffer = {buffer: Buffer.alloc(16, 12), offset: 2}; 11 | const testBufferChange = Buffer.from([0, 0, 12, 12]); 12 | testBuffer.buffer.fill(testBufferChange, 0, 4); 13 | services.registerForeignDevice.encode(buffer, 0); 14 | expect(buffer).to.deep.equal(testBuffer); 15 | }); 16 | 17 | it('should decode', () => { 18 | const buffer = Buffer.alloc(16, 23); 19 | const bufferCompare = Buffer.alloc(16, 23); 20 | services.registerForeignDevice.decode(buffer, 0); 21 | expect(buffer).to.deep.equal(bufferCompare); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/integration/reinitialize-sevice.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - reinitializeDevice integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.reinitializeDevice('127.0.0.2', 1, {password: 'Test1234'}, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/remove-list-element.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - removeListElement integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.removeListElement('127.0.0.2', {type: 19, instance: 100}, {id: 80, index: 0}, [ 10 | {type: 1, value: true} 11 | ], (err, value) => { 12 | expect(err.message).to.eql('ERR_TIMEOUT'); 13 | expect(value).to.eql(undefined); 14 | client.close(); 15 | next(); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/integration/subscribe-cov.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - subscribeCov integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.subscribeCov('127.0.0.2', {type: 5, instance: 3}, 7, false, false, 0, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/subscribe-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - subscribeProperty integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.subscribeProperty('127.0.0.2', {type: 5, instance: 33}, {id: 80, index: 0}, 8, false, false, (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/time-sync-utc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - timeSyncUTC integration', () => { 7 | it('should send a time UTC sync package', () => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.timeSyncUTC('127.0.0.2', new Date()); 10 | client.close(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/integration/time-sync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - timeSync integration', () => { 7 | it('should send a time sync package', () => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.timeSync('127.0.0.2', new Date()); 10 | client.close(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/integration/unconfirmed-cov-notification.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baEnum = require('../../lib/enum'); 6 | 7 | describe('bacnet - unconfirmedCOVNotification integration', () => { 8 | it('should correctly send a telegram', () => { 9 | const client = new utils.bacnetClient({apduTimeout: 200}); 10 | client.unconfirmedCOVNotification( 11 | '127.0.0.2', 3, 433, {type: 2, instance: 122}, 120, [ 12 | { 13 | property: {id: 85}, 14 | value: [{type: baEnum.ApplicationTag.REAL, value: 12.3}] 15 | }, 16 | { 17 | property: {id: 111}, 18 | value: 19 | [{type: baEnum.ApplicationTag.BIT_STRING, value: 0xFFFF}] 20 | } 21 | ]); 22 | client.close(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/integration/unconfirmed-event-notification.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - unconfirmedEventNotification integration', () => { 7 | it('should correctly send a telegram', () => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | const date = new Date(); 10 | date.setMilliseconds(880); 11 | client.unconfirmedEventNotification('127.0.0.2', { 12 | processId: 3, 13 | initiatingObjectId: {type: 60, instance: 12}, 14 | eventObjectId: {type: 61, instance: 1121}, 15 | timeStamp: {type: 2, value: date}, 16 | notificationClass: 9, 17 | priority: 7, 18 | eventType: 0, 19 | messageText: 'Test1234$', 20 | notifyType: 1, 21 | ackRequired: true, 22 | fromState: 5, 23 | toState: 6, 24 | changeOfBitstringReferencedBitString: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]}, 25 | changeOfBitstringStatusFlags: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]} 26 | }); 27 | client.close(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/integration/unconfirmed-private-transfer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - unconfirmedPrivateTransfer integration', () => { 7 | it('should correctly send a telegram', () => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.unconfirmedPrivateTransfer('127.0.0.2', 0, 7, [0x00, 0xaa, 0xfa, 0xb1, 0x00]); 10 | client.close(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/integration/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const coreExports = { 4 | debug: require('debug')('bacnet:test:integration:debug'), 5 | trace: require('debug')('bacnet:test:integration:trace'), 6 | bacnetClient: require('../../') 7 | }; 8 | 9 | module.exports = coreExports; 10 | 11 | const EventEmitter = require('events').EventEmitter; 12 | 13 | class Transport extends EventEmitter { 14 | constructor() { 15 | super(); 16 | } 17 | getBroadcastAddress() { 18 | return '255.255.255.255'; 19 | } 20 | getMaxPayload() { 21 | return 1482; 22 | } 23 | send() { } 24 | open() { } 25 | close() { } 26 | } 27 | module.exports.transportStub = Transport; 28 | 29 | module.exports.propertyFormater = (object) => { 30 | const converted = {}; 31 | object.forEach(property => converted[property.id] = property.value); 32 | return converted; 33 | }; 34 | -------------------------------------------------------------------------------- /test/integration/who-is.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - whoIs integration', () => { 7 | it('should not invoke a event if no device is available', (next) => { 8 | const timeOutWithoutDevice = setTimeout(() => { 9 | client.close(); 10 | next(); 11 | }, 1000); 12 | const client = new utils.bacnetClient({apduTimeout: 200}); 13 | client.on('iAm', (receiver, deviceId, maxApdu, segmentation, vendorId) => { 14 | client.close(); 15 | expect(parseInt(receiver.payload.deviceId, 10)).to.be.a('number'); // if there are device you'll get a response here 16 | clearTimeout(timeOutWithoutDevice); 17 | next(); 18 | }); 19 | client.whoIs(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/integration/write-file.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - writeFile integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.writeFile('127.0.0.2', {type: 10, instance: 2}, 0, [[5, 6, 7, 8], [5, 6, 7, 8]], (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.equal(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/write-property-multiple.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - writePropertyMultiple integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | const values = [ 10 | {objectId: {type: 8, instance: 44301}, values: [ 11 | {property: {id: 28, index: 12}, value: [{type: 1, value: true}], priority: 8} 12 | ]} 13 | ]; 14 | client.writePropertyMultiple('127.0.0.2', values, (err, value) => { 15 | expect(err.message).to.eql('ERR_TIMEOUT'); 16 | expect(value).to.eql(undefined); 17 | client.close(); 18 | next(); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/integration/write-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | 6 | describe('bacnet - writeProperty integration', () => { 7 | it('should return a timeout error if no device is available', (next) => { 8 | const client = new utils.bacnetClient({apduTimeout: 200}); 9 | client.writeProperty('127.0.0.2', {type: 8, instance: 44301}, 28, [{type: 4, value: 100}], (err, value) => { 10 | expect(err.message).to.eql('ERR_TIMEOUT'); 11 | expect(value).to.eql(undefined); 12 | client.close(); 13 | next(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/asn1.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baAsn1 = require('../../lib/asn1'); 6 | 7 | describe('bacnet - ASN1 layer', () => { 8 | describe('decodeUnsigned', () => { 9 | it('should successfully decode 8-bit unsigned integer', () => { 10 | const result = baAsn1.decodeUnsigned(Buffer.from([0x00, 0xFF, 0xFF, 0xFF, 0xFF]), 1, 1); 11 | expect(result).to.deep.equal({len: 1, value: 255}); 12 | }); 13 | 14 | it('should successfully decode 16-bit unsigned integer', () => { 15 | const result = baAsn1.decodeUnsigned(Buffer.from([0x00, 0xFF, 0xFF, 0xFF, 0xFF]), 1, 2); 16 | expect(result).to.deep.equal({len: 2, value: 65535}); 17 | }); 18 | 19 | it('should successfully decode length 0', () => { 20 | const result = baAsn1.decodeUnsigned(Buffer.from([]), 0, 0); 21 | expect(result).to.deep.equal({len: 0, value: 0}); 22 | }); 23 | 24 | it('should successfully decode 32-bit unsigned integer', () => { 25 | const result = baAsn1.decodeUnsigned(Buffer.from([0x00, 0xFF, 0xFF, 0xFF, 0xFF]), 1, 4); 26 | expect(result).to.deep.equal({len: 4, value: 4294967295}); 27 | }); 28 | }); 29 | 30 | describe('encodeBacnetObjectId', () => { 31 | it('should successfully encode with object-type > 512', () => { 32 | const buffer = {buffer: Buffer.alloc(4), offset: 0}; 33 | baAsn1.encodeBacnetObjectId(buffer, 600, 600); 34 | expect(buffer).to.deep.equal({buffer: Buffer.from([150, 0, 2, 88]), offset: 4}); 35 | }); 36 | 37 | it('should successfully encode with opening-tag > 14 = 15', () => { 38 | const buffer = {buffer: Buffer.alloc(15, 10), offset: 0}; 39 | baAsn1.encodeOpeningTag(buffer, 15); 40 | expect(buffer).to.deep.equal({buffer: Buffer.from([254, 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]), offset: 2}); 41 | }); 42 | 43 | it('should successfully encode with opening-tag > 253 = 255', () => { 44 | const buffer = {buffer: Buffer.alloc(255, 12), offset: 0}; 45 | const testBuffer = Buffer.alloc(255, 12); 46 | const testBufferChange = Buffer.from([142, 12, 12, 12]); 47 | testBuffer.fill(testBufferChange, 0, 4); 48 | const bufferToCompare = {buffer: testBuffer, offset: 1}; 49 | baAsn1.encodeOpeningTag(buffer, 8); 50 | expect(buffer).to.deep.equal(bufferToCompare); 51 | }); 52 | 53 | it('should successfully encode with closing-tag > 14 = 15', () => { 54 | const buffer = {buffer: Buffer.alloc(15, 10), offset: 0}; 55 | baAsn1.encodeClosingTag(buffer, 15); 56 | expect(buffer).to.deep.equal({buffer: Buffer.from([255, 15, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]), offset: 2}); 57 | }); 58 | 59 | it('should successfully encode with closing-tag > 253 = 255', () => { 60 | const buffer = {buffer: Buffer.alloc(255, 12), offset: 0}; 61 | const testBuffer = Buffer.alloc(255, 12); 62 | const testBufferChange = Buffer.from([143, 12, 12, 12]); 63 | testBuffer.fill(testBufferChange, 0, 4); 64 | const bufferToCompare = {buffer: testBuffer, offset: 1}; 65 | baAsn1.encodeClosingTag(buffer, 8); 66 | expect(buffer).to.deep.equal(bufferToCompare); 67 | }); 68 | 69 | it('should successfully encode with Date 1-1-1', () => { 70 | const buffer = {buffer: Buffer.alloc(15, 10), offset: 0}; 71 | const testBuffer = Buffer.alloc(15, 10); 72 | const testBufferChange = Buffer.from([1, 1, 1, 5]); 73 | testBuffer.fill(testBufferChange, 0, 4); 74 | const bufferToCompare = {buffer: testBuffer, offset: 4}; 75 | baAsn1.encodeBacnetDate(buffer, new Date(1, 1, 1)); 76 | expect(buffer).to.deep.equal(bufferToCompare); 77 | }); 78 | 79 | it('should successfully encode with Date 257-1-1', () => { 80 | const buffer = {buffer: Buffer.alloc(15, 10), offset: 0}; 81 | const bufferToCompare = {buffer: Buffer.alloc(15, 10), offset: 0}; 82 | try{ 83 | baAsn1.encodeBacnetDate(buffer, new Date(257, 1, 1)); 84 | } catch (e) { 85 | expect(e.message).to.be.equal('invaide year: 257'); 86 | expect(buffer).to.deep.equal(bufferToCompare); 87 | } 88 | }); 89 | 90 | it('should successfully encode with Date 2020-6-1', () => { 91 | const buffer = {buffer: Buffer.alloc(15, 10), offset: 0}; 92 | const testBuffer = Buffer.alloc(15, 10); 93 | const testBufferChange = Buffer.from([120, 6, 1, 3]); 94 | testBuffer.fill(testBufferChange, 0, 4); 95 | const bufferToCompare = {buffer: testBuffer, offset: 4}; 96 | baAsn1.encodeBacnetDate(buffer, new Date(2020, 6, 1)); 97 | expect(buffer).to.deep.equal(bufferToCompare); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/unit/bvlc.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baBvlc = require('../../lib/bvlc'); 6 | 7 | describe('bacnet - BVLC layer', () => { 8 | it('should successfuly encode and decode a package', () => { 9 | const buffer = utils.getBuffer(); 10 | baBvlc.encode(buffer.buffer, 10, 1482); 11 | const result = baBvlc.decode(buffer.buffer, 0); 12 | expect(result).to.deep.equal({ 13 | len: 4, 14 | func: 10, 15 | msgLength: 1482, 16 | originatingIP: null, 17 | }); 18 | }); 19 | 20 | it('should successfuly encode and decode a forwarded package', () => { 21 | const buffer = utils.getBuffer(); 22 | baBvlc.encode(buffer.buffer, 4, 1482, '1.2.255.0'); 23 | const result = baBvlc.decode(buffer.buffer, 0); 24 | expect(result).to.deep.equal({ 25 | len: 10, 26 | func: 4, 27 | msgLength: 1482, 28 | originatingIP: '1.2.255.0', // omit port if default 29 | }); 30 | }); 31 | 32 | it('should successfuly encode and decode a forwarded package on a different port', () => { 33 | const buffer = utils.getBuffer(); 34 | baBvlc.encode(buffer.buffer, 4, 1482, '1.2.255.0:47810'); 35 | const result = baBvlc.decode(buffer.buffer, 0); 36 | expect(result).to.deep.equal({ 37 | len: 10, 38 | func: 4, 39 | msgLength: 1482, 40 | originatingIP: '1.2.255.0:47810', // include port if non-default 41 | }); 42 | }); 43 | 44 | it('should fail forwarding a non FORWARDED_NPU', () => { 45 | const buffer = utils.getBuffer(); 46 | expect(() => { 47 | baBvlc.encode(buffer.buffer, 3, 1482, '1.2.255.0'); 48 | }).to.throw(Error); 49 | }); 50 | 51 | it('should fail if invalid BVLC type', () => { 52 | const buffer = utils.getBuffer(); 53 | baBvlc.encode(buffer.buffer, 10, 1482); 54 | buffer.buffer[0] = 8; 55 | const result = baBvlc.decode(buffer.buffer, 0); 56 | expect(result).to.equal(undefined); 57 | }); 58 | 59 | it('should fail if invalid length', () => { 60 | const buffer = utils.getBuffer(); 61 | baBvlc.encode(buffer.buffer, 10, 1481); 62 | buffer.buffer[0] = 8; 63 | const result = baBvlc.decode(buffer.buffer, 0); 64 | expect(result).to.equal(undefined); 65 | }); 66 | 67 | it('should fail if invalid function', () => { 68 | const buffer = utils.getBuffer(); 69 | baBvlc.encode(buffer.buffer, 100, 1482); 70 | const result = baBvlc.decode(buffer.buffer, 0); 71 | expect(result).to.equal(undefined); 72 | }); 73 | 74 | it('should fail if unsuported function', () => { 75 | const buffer = utils.getBuffer(); 76 | baBvlc.encode(buffer.buffer, 99, 1482); 77 | const result = baBvlc.decode(buffer.buffer, 0); 78 | expect(result).to.equal(undefined); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/unit/client.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baEnum = require('../../lib/enum'); 6 | const client = require('../../lib/client'); 7 | 8 | describe('bacnet - client', () => { 9 | it('should successfuly encode a bitstring > 32 bits', () => { 10 | const result = client.createBitstring([ 11 | baEnum.ServicesSupported.CONFIRMED_COV_NOTIFICATION, 12 | baEnum.ServicesSupported.READ_PROPERTY, 13 | baEnum.ServicesSupported.WHO_IS, 14 | ]); 15 | expect(result).to.deep.equal({ 16 | value: [2, 16, 0, 0, 4], 17 | bitsUsed: 35, 18 | }); 19 | }); 20 | it('should successfuly encode a bitstring < 8 bits', () => { 21 | const result = client.createBitstring([ 22 | baEnum.ServicesSupported.GET_ALARM_SUMMARY, 23 | ]); 24 | expect(result).to.deep.equal({ 25 | value: [8], 26 | bitsUsed: 4, 27 | }); 28 | }); 29 | it('should successfuly encode a bitstring of only one bit', () => { 30 | const result = client.createBitstring([ 31 | baEnum.ServicesSupported.ACKNOWLEDGE_ALARM, 32 | ]); 33 | expect(result).to.deep.equal({ 34 | value: [1], 35 | bitsUsed: 1, 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/enum.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const bacnetEnum = require('../../lib/enum'); 5 | 6 | describe('bacnet - ENUM tests', (done) => { 7 | it('enum get name of BOOLEAN should be defined with 1', () => { 8 | expect(bacnetEnum.getEnumName(bacnetEnum.ApplicationTag, 1, false)).to.be.equal(bacnetEnum.ApplicationTagName[1]); 9 | }); 10 | 11 | it('enum get name of BOOLEAN(1) should be defined with 1', () => { 12 | expect(bacnetEnum.getEnumName(bacnetEnum.ApplicationTag, 1)).to.be.equal('BOOLEAN(1)'); 13 | }); 14 | 15 | it('enum get undefined with -1', () => { 16 | expect(bacnetEnum.getEnumName(bacnetEnum.ApplicationTag, -1, false)).to.be.equal('-1'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/npdu.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baNpdu = require('../../lib/npdu'); 6 | 7 | describe('bacnet - NPDU layer', () => { 8 | it('should successfully decode the NPDU function', () => { 9 | const result = baNpdu.decodeFunction([0, 1, 12], 1); 10 | expect(result).to.equal(12); 11 | }); 12 | 13 | it('should fail decoding the NPDU function if invalid version', () => { 14 | const result = baNpdu.decodeFunction([0, 2, 12], 1); 15 | expect(result).to.equal(undefined); 16 | }); 17 | 18 | it('should successfully encode and decode a basic NPDU package', () => { 19 | const buffer = utils.getBuffer(); 20 | baNpdu.encode(buffer, 1); 21 | const result = baNpdu.decode(buffer.buffer, 0); 22 | expect(result).to.deep.equal({ 23 | len: 2, 24 | funct: 1, 25 | destination: undefined, 26 | source: undefined, 27 | hopCount: 0, 28 | networkMsgType: 0, 29 | vendorId: 0 30 | }); 31 | }); 32 | 33 | it('should successfully encode and decode a NPDU package with destination', () => { 34 | const buffer = utils.getBuffer(); 35 | const destination = {net: 1000, adr: [1, 2, 3]}; 36 | baNpdu.encode(buffer, 1, destination, undefined, 11, 5, 7); 37 | const result = baNpdu.decode(buffer.buffer, 0); 38 | expect(result).to.deep.equal({ 39 | len: 9, 40 | funct: 33, 41 | destination: {type: 0, net: 1000, adr: [1, 2, 3]}, 42 | source: undefined, 43 | hopCount: 11, 44 | networkMsgType: 0, 45 | vendorId: 0 46 | }); 47 | }); 48 | 49 | it('should successfully encode and decode a NPDU package with destination and source', () => { 50 | const buffer = utils.getBuffer(); 51 | const destination = {net: 1000, adr: [1, 2, 3]}; 52 | const source = {net: 1000, adr: [1, 2, 3]}; 53 | baNpdu.encode(buffer, 1, destination, source, 13, 10, 11); 54 | const result = baNpdu.decode(buffer.buffer, 0); 55 | expect(result).to.deep.equal({ 56 | len: 15, 57 | funct: 41, 58 | destination: {type: 0, net: 1000, adr: [1, 2, 3]}, 59 | source: {type: 0, net: 1000, adr: [1, 2, 3]}, 60 | hopCount: 13, 61 | networkMsgType: 0, 62 | vendorId: 0 63 | }); 64 | }); 65 | 66 | it('should successfully encode and decode a NPDU package with broadcast destination and source', () => { 67 | const buffer = utils.getBuffer(); 68 | const destination = {net: 65535}; 69 | const source = {net: 1000}; 70 | baNpdu.encode(buffer, 1, destination, source, 12, 8, 9); 71 | const result = baNpdu.decode(buffer.buffer, 0); 72 | expect(result).to.deep.equal({ 73 | len: 9, 74 | funct: 41, 75 | destination: {type: 0, net: 65535}, 76 | source: {type: 0, net: 1000}, 77 | hopCount: 12, 78 | networkMsgType: 0, 79 | vendorId: 0 80 | }); 81 | }); 82 | 83 | it('should successfully encode and decode a network layer NPDU package', () => { 84 | const buffer = utils.getBuffer(); 85 | baNpdu.encode(buffer, 128, undefined, undefined, 1, 128, 7777); 86 | const result = baNpdu.decode(buffer.buffer, 0); 87 | expect(result).to.deep.equal({ 88 | len: 5, 89 | funct: 128, 90 | destination: undefined, 91 | source: undefined, 92 | hopCount: 0, 93 | networkMsgType: 128, 94 | vendorId: 7777 95 | }); 96 | }); 97 | 98 | it('should successfully encode and decode a who is router to network layer NPDU package', () => { 99 | const buffer = utils.getBuffer(); 100 | baNpdu.encode(buffer, 128, undefined, undefined, 1, 0, 7777); 101 | const result = baNpdu.decode(buffer.buffer, 0); 102 | expect(result).to.deep.equal({ 103 | len: 5, 104 | funct: 128, 105 | destination: undefined, 106 | source: undefined, 107 | hopCount: 0, 108 | networkMsgType: 0, 109 | vendorId: 0 110 | }); 111 | }); 112 | 113 | it('should fail if invalid BACNET version', () => { 114 | const buffer = utils.getBuffer(); 115 | baNpdu.encode(buffer, 12, undefined, undefined, 1, 2, 3); 116 | buffer.buffer[0] = 2; 117 | const result = baNpdu.decode(buffer.buffer, 0); 118 | expect(result).to.equal(undefined); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/unit/service-add-list-element.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer AddListElement unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.addListElement.encode(buffer, {type: 11, instance: 560}, 85, 2, [ 11 | {type: 1, value: false}, 12 | {type: 2, value: 1} 13 | ]); 14 | const result = baServices.addListElement.decode(buffer.buffer, 0, buffer.offset); 15 | delete result.len; 16 | expect(result).to.deep.equal({ 17 | objectId: {type: 11, instance: 560}, 18 | property: {id: 85, index: 2}, 19 | values: [ 20 | {type: 1, value: false}, 21 | {type: 2, value: 1} 22 | ] 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/service-alarm-acknowledge.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | const baEnum = require('../../lib/enum'); 7 | 8 | describe('bacnet - Services layer AlarmAcknowledge unit', () => { 9 | it('should successfully encode and decode with time timestamp', () => { 10 | const buffer = utils.getBuffer(); 11 | const eventTime = new Date(1, 1, 1); 12 | eventTime.setMilliseconds(990); 13 | const ackTime = new Date(1, 1, 1); 14 | ackTime.setMilliseconds(880); 15 | baServices.alarmAcknowledge.encode(buffer, 57, {type: 0, instance: 33}, 5, 'Alarm Acknowledge Test', {value: eventTime, type: baEnum.TimeStamp.TIME}, {value: ackTime, type: baEnum.TimeStamp.TIME}); 16 | const result = baServices.alarmAcknowledge.decode(buffer.buffer, 0, buffer.offset); 17 | delete result.len; 18 | expect(result).to.deep.equal({ 19 | acknowledgedProcessId: 57, 20 | eventObjectId: { 21 | type: 0, 22 | instance: 33 23 | }, 24 | eventStateAcknowledged: 5, 25 | acknowledgeSource: 'Alarm Acknowledge Test', 26 | eventTimeStamp: eventTime, 27 | acknowledgeTimeStamp: ackTime 28 | }); 29 | }); 30 | 31 | it('should successfully encode and decode with sequence timestamp', () => { 32 | const buffer = utils.getBuffer(); 33 | const eventTime = 5; 34 | const ackTime = 6; 35 | baServices.alarmAcknowledge.encode(buffer, 57, {type: 0, instance: 33}, 5, 'Alarm Acknowledge Test', {value: eventTime, type: baEnum.TimeStamp.SEQUENCE_NUMBER}, {value: ackTime, type: baEnum.TimeStamp.SEQUENCE_NUMBER}); 36 | const result = baServices.alarmAcknowledge.decode(buffer.buffer, 0, buffer.offset); 37 | delete result.len; 38 | expect(result).to.deep.equal({ 39 | acknowledgedProcessId: 57, 40 | eventObjectId: { 41 | type: 0, 42 | instance: 33 43 | }, 44 | eventStateAcknowledged: 5, 45 | acknowledgeSource: 'Alarm Acknowledge Test', 46 | eventTimeStamp: eventTime, 47 | acknowledgeTimeStamp: ackTime 48 | }); 49 | }); 50 | 51 | it('should successfully encode and decode with datetime timestamp', () => { 52 | const buffer = utils.getBuffer(); 53 | const eventTime = new Date(1, 1, 1); 54 | eventTime.setMilliseconds(990); 55 | const ackTime = new Date(1, 1, 2); 56 | ackTime.setMilliseconds(880); 57 | baServices.alarmAcknowledge.encode(buffer, 57, {type: 0, instance: 33}, 5, 'Alarm Acknowledge Test', {value: eventTime, type: baEnum.TimeStamp.DATETIME}, {value: ackTime, type: baEnum.TimeStamp.DATETIME}); 58 | const result = baServices.alarmAcknowledge.decode(buffer.buffer, 0, buffer.offset); 59 | delete result.len; 60 | expect(result).to.deep.equal({ 61 | acknowledgedProcessId: 57, 62 | eventObjectId: { 63 | type: 0, 64 | instance: 33 65 | }, 66 | eventStateAcknowledged: 5, 67 | acknowledgeSource: 'Alarm Acknowledge Test', 68 | eventTimeStamp: eventTime, 69 | acknowledgeTimeStamp: ackTime 70 | }); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /test/unit/service-alarm-summary.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer AlarmSummary unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.alarmSummary.encode(buffer, [ 11 | {objectId: {type: 12, instance: 12}, alarmState: 12, acknowledgedTransitions: {value: [12], bitsUsed: 5}}, 12 | {objectId: {type: 13, instance: 13}, alarmState: 13, acknowledgedTransitions: {value: [13], bitsUsed: 6}} 13 | ]); 14 | const result = baServices.alarmSummary.decode(buffer.buffer, 0, buffer.offset); 15 | delete result.len; 16 | expect(result).to.deep.equal({ 17 | alarms: [ 18 | {objectId: {type: 12, instance: 12}, alarmState: 12, acknowledgedTransitions: {value: [12], bitsUsed: 5}}, 19 | {objectId: {type: 13, instance: 13}, alarmState: 13, acknowledgedTransitions: {value: [13], bitsUsed: 6}} 20 | ] 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/service-atomic-read-file.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer AtomicReadFile unit', () => { 8 | it('should successfully encode and decode as stream', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.atomicReadFile.encode(buffer, true, {type: 13, instance: 5000}, -50, 12); 11 | const result = baServices.atomicReadFile.decode(buffer.buffer, 0); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | objectId: {type: 13, instance: 5000}, 15 | count: 12, 16 | isStream: true, 17 | position: -50 18 | }); 19 | }); 20 | 21 | it('should successfully encode and decode as non-stream', () => { 22 | const buffer = utils.getBuffer(); 23 | baServices.atomicReadFile.encode(buffer, false, {type: 14, instance: 5001}, 60, 13); 24 | const result = baServices.atomicReadFile.decode(buffer.buffer, 0); 25 | delete result.len; 26 | expect(result).to.deep.equal({ 27 | objectId: {type: 14, instance: 5001}, 28 | count: 13, 29 | isStream: false, 30 | position: 60 31 | }); 32 | }); 33 | }); 34 | 35 | describe('AtomicReadFileAcknowledge', () => { 36 | it('should successfully encode and decode as stream', () => { 37 | const buffer = utils.getBuffer(); 38 | baServices.atomicReadFile.encodeAcknowledge(buffer, true, false, 0, 90, [[12, 12, 12]], [3]); 39 | const result = baServices.atomicReadFile.decodeAcknowledge(buffer.buffer, 0); 40 | delete result.len; 41 | expect(result).to.deep.equal({ 42 | isStream: true, 43 | position: 0, 44 | endOfFile: false, 45 | buffer: Buffer.from([12, 12, 12]) 46 | }); 47 | }); 48 | 49 | it('should successfully encode and decode as non-stream', () => { 50 | const buffer = utils.getBuffer(); 51 | baServices.atomicReadFile.encodeAcknowledge(buffer, false, false, 0, 90, [12, 12, 12], 3); 52 | // TODO: AtomicReadFileAcknowledge as non-stream not yet implemented 53 | expect(() => { 54 | baServices.atomicReadFile.decodeAcknowledge(buffer.buffer, 0); 55 | }).to.throw('NotImplemented'); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/service-atomic-write-file.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer AtomicWriteFile unit', () => { 8 | it('should successfully encode and decode as stream', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.atomicWriteFile.encode(buffer, true, {type: 12, instance: 51}, 5, [[12, 12]]); 11 | const result = baServices.atomicWriteFile.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | objectId: {type: 12, instance: 51}, 15 | isStream: true, 16 | position: 5, 17 | blocks: [[12, 12]] 18 | }); 19 | }); 20 | 21 | it('should successfully encode and decode as non-stream', () => { 22 | const buffer = utils.getBuffer(); 23 | baServices.atomicWriteFile.encode(buffer, false, {type: 12, instance: 88}, 10, [[12, 12], [12, 12]]); 24 | const result = baServices.atomicWriteFile.decode(buffer.buffer, 0, buffer.offset); 25 | delete result.len; 26 | expect(result).to.deep.equal({ 27 | objectId: {type: 12, instance: 88}, 28 | isStream: false, 29 | position: 10, 30 | blocks: [[12, 12], [12, 12]] 31 | }); 32 | }); 33 | }); 34 | 35 | describe('AtomicWriteFileAcknowledge', () => { 36 | it('should successfully encode and decode streamed file', () => { 37 | const buffer = utils.getBuffer(); 38 | baServices.atomicWriteFile.encodeAcknowledge(buffer, true, -10); 39 | const result = baServices.atomicWriteFile.decodeAcknowledge(buffer.buffer, 0); 40 | delete result.len; 41 | expect(result).to.deep.equal({ 42 | isStream: true, 43 | position: -10 44 | }); 45 | }); 46 | 47 | it('should successfully encode and decode non-streamed file', () => { 48 | const buffer = utils.getBuffer(); 49 | baServices.atomicWriteFile.encodeAcknowledge(buffer, false, 10); 50 | const result = baServices.atomicWriteFile.decodeAcknowledge(buffer.buffer, 0); 51 | delete result.len; 52 | expect(result).to.deep.equal({ 53 | isStream: false, 54 | position: 10 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/service-cov-notify.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer COVNotify unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | const date = new Date(1, 1, 1); 11 | const time = new Date(1, 1, 1); 12 | time.setMilliseconds(990); 13 | baServices.covNotify.encode(buffer, 7, 443, {type: 2, instance: 12}, 120, [ 14 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 15 | {type: 0}, 16 | {type: 1, value: null}, 17 | {type: 1, value: true}, 18 | {type: 1, value: false}, 19 | {type: 2, value: 1}, 20 | {type: 2, value: 1000}, 21 | {type: 2, value: 1000000}, 22 | {type: 2, value: 1000000000}, 23 | {type: 3, value: -1}, 24 | {type: 3, value: -1000}, 25 | {type: 3, value: -1000000}, 26 | {type: 3, value: -1000000000}, 27 | {type: 4, value: 0.1}, 28 | {type: 5, value: 100.121212}, 29 | {type: 6, value: [1, 2, 100, 200]}, 30 | {type: 7, value: 'Test1234$'}, 31 | {type: 8, value: {bitsUsed: 0, value: []}}, 32 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 33 | {type: 9, value: 4}, 34 | {type: 10, value: date}, 35 | {type: 11, value: time} 36 | ], priority: 0}, 37 | {property: {id: 82, index: 0}, value: [ 38 | {type: 12, value: {type: 3, instance: 0}} 39 | ], priority: 8} 40 | ]); 41 | const result = baServices.covNotify.decode(buffer.buffer, 0, buffer.offset); 42 | delete result.len; 43 | result.values[0].value[12].value = Math.floor(result.values[0].value[12].value * 1000) / 1000; 44 | expect(result).to.deep.equal({ 45 | initiatingDeviceId: { 46 | type: 8, 47 | instance: 443 48 | }, 49 | monitoredObjectId: { 50 | type: 2, 51 | instance: 12 52 | }, 53 | subscriberProcessId: 7, 54 | timeRemaining: 120, 55 | values: [ 56 | { 57 | priority: 0, 58 | property: { 59 | index: 0xFFFFFFFF, 60 | id: 81 61 | }, 62 | value: [ 63 | {type: 0, value: null}, 64 | {type: 0, value: null}, 65 | {type: 1, value: true}, 66 | {type: 1, value: false}, 67 | {type: 2, value: 1}, 68 | {type: 2, value: 1000}, 69 | {type: 2, value: 1000000}, 70 | {type: 2, value: 1000000000}, 71 | {type: 3, value: -1}, 72 | {type: 3, value: -1000}, 73 | {type: 3, value: -1000000}, 74 | {type: 3, value: -1000000000}, 75 | {type: 4, value: 0.1}, 76 | {type: 5, value: 100.121212}, 77 | {type: 6, value: [1, 2, 100, 200]}, 78 | {type: 7, value: 'Test1234$', encoding: 0}, 79 | {type: 8, value: {bitsUsed: 0, value: []}}, 80 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 81 | {type: 9, value: 4}, 82 | {type: 10, value: date}, 83 | {type: 11, value: time} 84 | ] 85 | }, 86 | { 87 | priority: 0, 88 | property: { 89 | index: 0xFFFFFFFF, 90 | id: 82 91 | }, 92 | value: [ 93 | {type: 12, value: {type: 3, instance: 0}} 94 | ] 95 | } 96 | ] 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/unit/service-create-object.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer CreateObject unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | const date = new Date(1, 1, 1); 11 | const time = new Date(1, 1, 1); 12 | time.setMilliseconds(990); 13 | baServices.createObject.encode(buffer, {type: 1, instance: 10}, [ 14 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 15 | {type: 0}, 16 | {type: 1, value: null}, 17 | {type: 1, value: true}, 18 | {type: 1, value: false}, 19 | {type: 2, value: 1}, 20 | {type: 2, value: 1000}, 21 | {type: 2, value: 1000000}, 22 | {type: 2, value: 1000000000}, 23 | {type: 3, value: -1}, 24 | {type: 3, value: -1000}, 25 | {type: 3, value: -1000000}, 26 | {type: 3, value: -1000000000}, 27 | {type: 4, value: 0.1}, 28 | {type: 5, value: 100.121212}, 29 | {type: 6, value: [1, 2, 100, 200]}, 30 | {type: 7, value: 'Test1234$'}, 31 | {type: 8, value: {bitsUsed: 0, value: []}}, 32 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 33 | {type: 9, value: 4}, 34 | {type: 10, value: date}, 35 | {type: 11, value: time} 36 | ], priority: 0}, 37 | {property: {id: 82, index: 0}, value: [ 38 | {type: 12, value: {type: 3, instance: 0}} 39 | ], priority: 0} 40 | ]); 41 | const result = baServices.createObject.decode(buffer.buffer, 0, buffer.offset); 42 | delete result.len; 43 | result.values[0].value[12].value = Math.floor(result.values[0].value[12].value * 1000) / 1000; 44 | expect(result).to.deep.equal({ 45 | objectId: { 46 | type: 1, 47 | instance: 10 48 | }, 49 | values: [ 50 | { 51 | property: { 52 | index: 0xFFFFFFFF, 53 | id: 81 54 | }, 55 | value: [ 56 | {type: 0, value: null}, 57 | {type: 0, value: null}, 58 | {type: 1, value: true}, 59 | {type: 1, value: false}, 60 | {type: 2, value: 1}, 61 | {type: 2, value: 1000}, 62 | {type: 2, value: 1000000}, 63 | {type: 2, value: 1000000000}, 64 | {type: 3, value: -1}, 65 | {type: 3, value: -1000}, 66 | {type: 3, value: -1000000}, 67 | {type: 3, value: -1000000000}, 68 | {type: 4, value: 0.1}, 69 | {type: 5, value: 100.121212}, 70 | {type: 6, value: [1, 2, 100, 200]}, 71 | {type: 7, value: 'Test1234$', encoding: 0}, 72 | {type: 8, value: {bitsUsed: 0, value: []}}, 73 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 74 | {type: 9, value: 4}, 75 | {type: 10, value: date}, 76 | {type: 11, value: time} 77 | ] 78 | }, 79 | { 80 | property: { 81 | index: 0xFFFFFFFF, 82 | id: 82 83 | }, 84 | value: [ 85 | {type: 12, value: {type: 3, instance: 0}} 86 | ] 87 | } 88 | ] 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /test/unit/service-delete-object.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer DeleteObject unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.deleteObject.encode(buffer, {type: 1, instance: 10}); 11 | const result = baServices.deleteObject.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | objectType: 1, 15 | instance: 10 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/service-device-communication-control.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer DeviceCommunicationControl unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.deviceCommunicationControl.encode(buffer, 30, 1); 11 | const result = baServices.deviceCommunicationControl.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | timeDuration: 30, 15 | enableDisable: 1 16 | }); 17 | }); 18 | 19 | it('should successfully encode and decode with password', () => { 20 | const buffer = utils.getBuffer(); 21 | baServices.deviceCommunicationControl.encode(buffer, 30, 1, 'Test1234!'); 22 | const result = baServices.deviceCommunicationControl.decode(buffer.buffer, 0, buffer.offset); 23 | delete result.len; 24 | expect(result).to.deep.equal({ 25 | timeDuration: 30, 26 | enableDisable: 1, 27 | password: 'Test1234!' 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/unit/service-error.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer Error unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.error.encode(buffer, 15, 25); 11 | const result = baServices.error.decode(buffer.buffer, 0); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | class: 15, 15 | code: 25 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/service-event-information.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer EventInformation unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | const date1 = new Date(); 11 | date1.setMilliseconds(990); 12 | const date2 = new Date(); 13 | date2.setMilliseconds(990); 14 | const date3 = new Date(); 15 | date3.setMilliseconds(990); 16 | baServices.eventInformation.encode(buffer, [ 17 | {objectId: {type: 0, instance: 32}, eventState: 12, acknowledgedTransitions: {value: [14], bitsUsed: 6}, eventTimeStamps: [date1, date2, date3], notifyType: 5, eventEnable: {value: [15], bitsUsed: 7}, eventPriorities: [2, 3, 4]} 18 | ], false); 19 | const result = baServices.eventInformation.decode(buffer.buffer, 0, buffer.offset); 20 | delete result.len; 21 | expect(result).to.deep.equal({ 22 | alarms: [ 23 | { 24 | objectId: { 25 | type: 0, 26 | instance: 32 27 | }, 28 | eventState: 12, 29 | acknowledgedTransitions: { 30 | bitsUsed: 6, 31 | value: [14] 32 | }, 33 | eventTimeStamps: [ 34 | date1, 35 | date2, 36 | date3 37 | ], 38 | notifyType: 5, 39 | eventEnable: { 40 | bitsUsed: 7, 41 | value: [15] 42 | }, 43 | eventPriorities: [2, 3, 4] 44 | } 45 | ], 46 | moreEvents: false 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/service-get-enrollment-summary.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer GetEnrollmentSummary unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.getEnrollmentSummary.encode(buffer, 2); 11 | const result = baServices.getEnrollmentSummary.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | acknowledgmentFilter: 2 15 | }); 16 | }); 17 | 18 | it('should successfully encode and decode full payload', () => { 19 | const buffer = utils.getBuffer(); 20 | baServices.getEnrollmentSummary.encode(buffer, 2, {objectId: {type: 5, instance: 33}, processId: 7}, 1, 3, {min: 1, max: 65}, 5); 21 | const result = baServices.getEnrollmentSummary.decode(buffer.buffer, 0, buffer.offset); 22 | delete result.len; 23 | expect(result).to.deep.equal({ 24 | acknowledgmentFilter: 2, 25 | enrollmentFilter: {objectId: {type: 5, instance: 33}, processId: 7}, 26 | eventStateFilter: 1, 27 | eventTypeFilter: 3, 28 | priorityFilter: {min: 1, max: 65}, 29 | notificationClassFilter: 5 30 | }); 31 | }); 32 | }); 33 | 34 | describe('GetEnrollmentSummaryAcknowledge', () => { 35 | it('should successfully encode and decode', () => { 36 | const buffer = utils.getBuffer(); 37 | baServices.getEnrollmentSummary.encodeAcknowledge(buffer, [ 38 | {objectId: {type: 12, instance: 120}, eventType: 3, eventState: 2, priority: 18, notificationClass: 11} 39 | ]); 40 | const result = baServices.getEnrollmentSummary.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 41 | delete result.len; 42 | expect(result).to.deep.equal({ 43 | enrollmentSummaries: [{objectId: {type: 12, instance: 120}, eventType: 3, eventState: 2, priority: 18, notificationClass: 11}] 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/service-get-event-information.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | const baEnum = require('../../lib/enum'); 7 | 8 | describe('bacnet - Services layer GetEventInformation unit', () => { 9 | it('should successfully encode and decode', () => { 10 | const buffer = utils.getBuffer(); 11 | baServices.getEventInformation.encode(buffer, {type: 8, instance: 15}); 12 | const result = baServices.getEventInformation.decode(buffer.buffer, 0); 13 | delete result.len; 14 | expect(result).to.deep.equal({ 15 | lastReceivedObjectId: {type: 8, instance: 15} 16 | }); 17 | }); 18 | }); 19 | 20 | describe('GetEventInformationAcknowledge', () => { 21 | it('should successfully encode and decode', () => { 22 | const timeStamp = new Date(1, 1, 1); 23 | timeStamp.setMilliseconds(990); 24 | const buffer = utils.getBuffer(); 25 | baServices.getEventInformation.encodeAcknowledge(buffer, [ 26 | { 27 | objectId: {type: 2, instance: 17}, 28 | eventState: 3, 29 | acknowledgedTransitions: {value: [14], bitsUsed: 6}, 30 | eventTimeStamps: [{value: timeStamp, type: baEnum.TimeStamp.DATETIME}, {value: 5, type: baEnum.TimeStamp.SEQUENCE_NUMBER}, {value: timeStamp, type: baEnum.TimeStamp.TIME}], 31 | notifyType: 12, 32 | eventEnable: {value: [14], bitsUsed: 6}, 33 | eventPriorities: [1, 2, 3] 34 | } 35 | ], false); 36 | const result = baServices.getEventInformation.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 37 | delete result.len; 38 | expect(result).to.deep.equal({ 39 | events: [ 40 | { 41 | objectId: {type: 2, instance: 17}, 42 | eventState: 3, 43 | acknowledgedTransitions: {value: [14], bitsUsed: 6}, eventTimeStamps: [{value: timeStamp, type: baEnum.TimeStamp.DATETIME}, {value: 5, type: baEnum.TimeStamp.SEQUENCE_NUMBER}, {value: timeStamp, type: baEnum.TimeStamp.TIME}], 44 | notifyType: 12, 45 | eventEnable: {value: [14], bitsUsed: 6}, 46 | eventPriorities: [1, 2, 3] 47 | } 48 | ], 49 | moreEvents: false 50 | }); 51 | }); 52 | 53 | it('should successfully encode and decode empty payload', () => { 54 | const buffer = utils.getBuffer(); 55 | baServices.getEventInformation.encodeAcknowledge(buffer, [], true); 56 | const result = baServices.getEventInformation.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 57 | delete result.len; 58 | expect(result).to.deep.equal({ 59 | events: [], 60 | moreEvents: true 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/unit/service-i-am.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer iAm unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.iAm.encode(buffer, 47, 1, 1, 7); 11 | const result = baServices.iAm.decode(buffer.buffer, 0); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | deviceId: 47, 15 | maxApdu: 1, 16 | segmentation: 1, 17 | vendorId: 7 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/unit/service-i-have.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer iHave unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.iHave.encode(buffer, {type: 8, instance: 443}, {type: 0, instance: 4}, 'LgtCmd01'); 11 | const result = baServices.iHave.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | deviceId: {type: 8, instance: 443}, 15 | objectId: {type: 0, instance: 4}, 16 | objectName: 'LgtCmd01' 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/service-life-safety-operation.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer LifeSafetyOperation unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.lifeSafetyOperation.encode(buffer, 8, 'User01', 7, {type: 0, instance: 77}); 11 | const result = baServices.lifeSafetyOperation.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | processId: 8, 15 | requestingSource: 'User01', 16 | operation: 7, 17 | targetObjectId: {type: 0, instance: 77} 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/unit/service-private-transfer.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer PrivateTransfer unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.privateTransfer.encode(buffer, 255, 8, [1, 2, 3, 4, 5]); 11 | const result = baServices.privateTransfer.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | vendorId: 255, 15 | serviceNumber: 8, 16 | data: [1, 2, 3, 4, 5] 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/service-read-property-multiple.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer ReadPropertyMultiple unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.readPropertyMultiple.encode(buffer, [ 11 | {objectId: {type: 51, instance: 1}, properties: [ 12 | {id: 85, index: 0xFFFFFFFF}, 13 | {id: 85, index: 4} 14 | ]} 15 | ]); 16 | const result = baServices.readPropertyMultiple.decode(buffer.buffer, 0, buffer.offset); 17 | delete result.len; 18 | expect(result).to.deep.equal({properties: [{objectId: {type: 51, instance: 1}, properties: [ 19 | {id: 85, index: 0xFFFFFFFF}, 20 | {id: 85, index: 4} 21 | ]}]}); 22 | }); 23 | }); 24 | 25 | describe('ReadPropertyMultipleAcknowledge', () => { 26 | it('should successfully encode and decode', () => { 27 | const buffer = utils.getBuffer(); 28 | const date = new Date(1, 1, 1); 29 | const time = new Date(1, 1, 1); 30 | time.setMilliseconds(990); 31 | baServices.readPropertyMultiple.encodeAcknowledge(buffer, [ 32 | {objectId: {type: 9, instance: 50000}, values: [ 33 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 34 | {type: 0}, 35 | {type: 1, value: null}, 36 | {type: 1, value: true}, 37 | {type: 1, value: false}, 38 | {type: 2, value: 1}, 39 | {type: 2, value: 1000}, 40 | {type: 2, value: 1000000}, 41 | {type: 2, value: 1000000000}, 42 | {type: 3, value: -1}, 43 | {type: 3, value: -1000}, 44 | {type: 3, value: -1000000}, 45 | {type: 3, value: -1000000000}, 46 | {type: 4, value: 0.1}, 47 | {type: 5, value: 100.121212}, 48 | {type: 6, value: [1, 2, 100, 200]}, 49 | {type: 7, value: 'Test1234$'}, 50 | {type: 8, value: {bitsUsed: 0, value: []}}, 51 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 52 | {type: 9, value: 4}, 53 | {type: 10, value: date}, 54 | {type: 11, value: time}, 55 | {type: 12, value: {type: 3, instance: 0}} 56 | ]} 57 | ]} 58 | ]); 59 | const result = baServices.readPropertyMultiple.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 60 | delete result.len; 61 | expect(Math.floor(0.1 * 10000)).to.equal(Math.floor(result.values[0].values[0].value[12].value * 10000)); 62 | result.values[0].values[0].value[12].value = 0; 63 | expect(result).to.deep.equal({ 64 | values: [{ 65 | objectId: { 66 | type: 9, 67 | instance: 50000 68 | }, 69 | values: [{ 70 | index: 4294967295, 71 | id: 81, 72 | value: [ 73 | {type: 0, value: null}, 74 | {type: 0, value: null}, 75 | {type: 1, value: true}, 76 | {type: 1, value: false}, 77 | {type: 2, value: 1}, 78 | {type: 2, value: 1000}, 79 | {type: 2, value: 1000000}, 80 | {type: 2, value: 1000000000}, 81 | {type: 3, value: -1}, 82 | {type: 3, value: -1000}, 83 | {type: 3, value: -1000000}, 84 | {type: 3, value: -1000000000}, 85 | {type: 4, value: 0}, 86 | {type: 5, value: 100.121212}, 87 | {type: 6, value: [1, 2, 100, 200]}, 88 | {type: 7, value: 'Test1234$', encoding: 0}, 89 | {type: 8, value: {bitsUsed: 0, value: []}}, 90 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 91 | {type: 9, value: 4}, 92 | {type: 10, value: date}, 93 | {type: 11, value: time}, 94 | {type: 12, value: {type: 3, instance: 0}} 95 | ] 96 | }] 97 | }] 98 | }); 99 | }); 100 | 101 | it('should successfully encode and decode an error', () => { 102 | const buffer = utils.getBuffer(); 103 | baServices.readPropertyMultiple.encodeAcknowledge(buffer, [ 104 | {objectId: {type: 9, instance: 50000}, values: [ 105 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 106 | {type: 0, value: {type: 'BacnetError', errorClass: 12, errorCode: 13}} 107 | ]} 108 | ]} 109 | ]); 110 | const result = baServices.readPropertyMultiple.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 111 | delete result.len; 112 | expect(result).to.deep.equal({ 113 | values: [{ 114 | objectId: { 115 | type: 9, 116 | instance: 50000 117 | }, 118 | values: [{ 119 | index: 4294967295, 120 | id: 81, 121 | value: [{ 122 | type: 105, 123 | value: { 124 | errorClass: 12, 125 | errorCode: 13 126 | } 127 | }] 128 | }] 129 | }] 130 | }); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/unit/service-read-range.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | const baEnum = require('../../lib/enum'); 7 | 8 | describe('bacnet - Services layer ReadRange unit', () => { 9 | it('should successfully encode and decode by position', () => { 10 | const buffer = utils.getBuffer(); 11 | baServices.readRange.encode(buffer, {type: 61, instance: 35}, 85, 0xFFFFFFFF, baEnum.ReadRangeType.BY_POSITION, 10, null, 0); 12 | const result = baServices.readRange.decode(buffer.buffer, 0, buffer.offset); 13 | delete result.len; 14 | expect(result).to.deep.equal({ 15 | count: 0, 16 | objectId: {type: 61, instance: 35}, 17 | position: 10, 18 | property: { 19 | index: 0xFFFFFFFF, 20 | id: 85 21 | }, 22 | requestType: baEnum.ReadRangeType.BY_POSITION, 23 | time: undefined 24 | }); 25 | }); 26 | 27 | it('should successfully encode and decode by position with array index', () => { 28 | const buffer = utils.getBuffer(); 29 | baServices.readRange.encode(buffer, {type: 61, instance: 35}, 12, 2, baEnum.ReadRangeType.BY_SEQUENCE_NUMBER, 10, null, 0); 30 | const result = baServices.readRange.decode(buffer.buffer, 0, buffer.offset); 31 | delete result.len; 32 | expect(result).to.deep.equal({ 33 | count: 0, 34 | objectId: {type: 61, instance: 35}, 35 | position: 10, 36 | property: { 37 | index: 2, 38 | id: 12 39 | }, 40 | requestType: baEnum.ReadRangeType.BY_SEQUENCE_NUMBER, 41 | time: undefined 42 | }); 43 | }); 44 | 45 | it('should successfully encode and decode by sequence', () => { 46 | const buffer = utils.getBuffer(); 47 | baServices.readRange.encode(buffer, {type: 61, instance: 35}, 85, 0xFFFFFFFF, baEnum.ReadRangeType.BY_SEQUENCE_NUMBER, 11, null, 1111); 48 | const result = baServices.readRange.decode(buffer.buffer, 0, buffer.offset); 49 | delete result.len; 50 | expect(result).to.deep.equal({ 51 | count: 1111, 52 | objectId: {type: 61, instance: 35}, 53 | position: 11, 54 | property: { 55 | index: 0xFFFFFFFF, 56 | id: 85 57 | }, 58 | requestType: baEnum.ReadRangeType.BY_SEQUENCE_NUMBER, 59 | time: undefined 60 | }); 61 | }); 62 | 63 | it('should successfully encode and decode by time', () => { 64 | const buffer = utils.getBuffer(); 65 | const date = new Date(1, 1, 1); 66 | date.setMilliseconds(990); 67 | baServices.readRange.encode(buffer, {type: 61, instance: 35}, 85, 0xFFFFFFFF, baEnum.ReadRangeType.BY_TIME_REFERENCE_TIME_COUNT, null, date, -1111); 68 | const result = baServices.readRange.decode(buffer.buffer, 0, buffer.offset); 69 | delete result.len; 70 | expect(result).to.deep.equal({ 71 | count: -1111, 72 | objectId: {type: 61, instance: 35}, 73 | position: undefined, 74 | property: { 75 | index: 0xFFFFFFFF, 76 | id: 85 77 | }, 78 | requestType: baEnum.ReadRangeType.BY_TIME_REFERENCE_TIME_COUNT, 79 | time: date 80 | }); 81 | }); 82 | }); 83 | 84 | describe('ReadRangeAcknowledge', () => { 85 | it('should successfully encode and decode', () => { 86 | const buffer = utils.getBuffer(); 87 | baServices.readRange.encodeAcknowledge(buffer, {type: 12, instance: 500}, 5048, 0xFFFFFFFF, {bitsUsed: 24, value: [1, 2, 3]}, 12, Buffer.from([1, 2, 3]), 2, 2); 88 | const result = baServices.readRange.decodeAcknowledge(buffer.buffer, 0, buffer.offset); 89 | delete result.len; 90 | expect(result).to.deep.equal({ 91 | objectId: {type: 12, instance: 500}, 92 | itemCount: 12, 93 | property: {id: 5048, index: 0xFFFFFFFF}, 94 | resultFlag: {bitsUsed: 24, value: [1, 2, 3]}, 95 | rangeBuffer: Buffer.from([1, 2, 3]) 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /test/unit/service-reinitialize-device.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer ReinitializeDevice unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.reinitializeDevice.encode(buffer, 5); 11 | const result = baServices.reinitializeDevice.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | state: 5 15 | }); 16 | }); 17 | 18 | it('should successfully encode and decode with password', () => { 19 | const buffer = utils.getBuffer(); 20 | baServices.reinitializeDevice.encode(buffer, 5, 'Test1234$'); 21 | const result = baServices.reinitializeDevice.decode(buffer.buffer, 0, buffer.offset); 22 | delete result.len; 23 | expect(result).to.deep.equal({ 24 | state: 5, 25 | password: 'Test1234$' 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/service-subscribe-cov.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer SubscribeCOV unit', () => { 8 | it('should successfully encode and decode a cancelation request', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.subscribeCov.encode(buffer, 10, {type: 3, instance: 1}, true); 11 | const result = baServices.subscribeCov.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | cancellationRequest: true, 15 | monitoredObjectId: {type: 3, instance: 1}, 16 | subscriberProcessId: 10 17 | }); 18 | }); 19 | 20 | it('should successfully encode and decode subscription request', () => { 21 | const buffer = utils.getBuffer(); 22 | baServices.subscribeCov.encode(buffer, 11, {type: 3, instance: 2}, false, true, 5000); 23 | const result = baServices.subscribeCov.decode(buffer.buffer, 0, buffer.offset); 24 | delete result.len; 25 | expect(result).to.deep.equal({ 26 | cancellationRequest: false, 27 | issueConfirmedNotifications: true, 28 | lifetime: 5000, 29 | monitoredObjectId: {type: 3, instance: 2}, 30 | subscriberProcessId: 11 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/unit/service-subscribe-property.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer SubscribeProperty unit', () => { 8 | it('should successfully encode and decode with cancellation request', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.subscribeProperty.encode(buffer, 7, {type: 148, instance: 362}, true, false, 1, {id: 85, index: 0xFFFFFFFF}, true, 1); 11 | const result = baServices.subscribeProperty.decode(buffer.buffer, 0); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | cancellationRequest: true, 15 | covIncrement: 1, 16 | issueConfirmedNotifications: false, 17 | lifetime: 0, 18 | monitoredObjectId: { 19 | instance: 362, 20 | type: 148 21 | }, 22 | monitoredProperty: { 23 | index: 4294967295, 24 | id: 85 25 | }, 26 | subscriberProcessId: 7 27 | }); 28 | }); 29 | 30 | it('should successfully encode and decode without cancellation request', () => { 31 | const buffer = utils.getBuffer(); 32 | baServices.subscribeProperty.encode(buffer, 8, {type: 149, instance: 363}, false, true, 2, {id: 86, index: 3}, false, 10); 33 | const result = baServices.subscribeProperty.decode(buffer.buffer, 0); 34 | delete result.len; 35 | expect(result).to.deep.equal({ 36 | cancellationRequest: false, 37 | covIncrement: 0, 38 | issueConfirmedNotifications: true, 39 | lifetime: 2, 40 | monitoredObjectId: { 41 | instance: 363, 42 | type: 149 43 | }, 44 | monitoredProperty: { 45 | index: 3, 46 | id: 86 47 | }, 48 | subscriberProcessId: 8 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/service-time-sync.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer TimeSync unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | const date = new Date(); 11 | date.setMilliseconds(990); 12 | baServices.timeSync.encode(buffer, date); 13 | const result = baServices.timeSync.decode(buffer.buffer, 0, buffer.offset); 14 | delete result.len; 15 | expect(result).to.deep.equal({ 16 | value: date 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/service-who-has.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer WhoHas unit', () => { 8 | it('should successfully encode and decode by id', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.whoHas.encode(buffer, 3, 4000, {type: 3, instance: 15}); 11 | const result = baServices.whoHas.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | lowLimit: 3, 15 | highLimit: 4000, 16 | objectId: { 17 | type: 3, 18 | instance: 15 19 | } 20 | }); 21 | }); 22 | 23 | it('should successfully encode and decode by name', () => { 24 | const buffer = utils.getBuffer(); 25 | baServices.whoHas.encode(buffer, 3, 4000, {}, 'analog-output-1'); 26 | const result = baServices.whoHas.decode(buffer.buffer, 0, buffer.offset); 27 | delete result.len; 28 | expect(result).to.deep.equal({ 29 | lowLimit: 3, 30 | highLimit: 4000, 31 | objectName: 'analog-output-1' 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/service-who-is.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer WhoIs unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | baServices.whoIs.encode(buffer, 1, 3000); 11 | const result = baServices.whoIs.decode(buffer.buffer, 0, buffer.offset); 12 | delete result.len; 13 | expect(result).to.deep.equal({ 14 | lowLimit: 1, 15 | highLimit: 3000 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/service-write-property-multiple.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const utils = require('./utils'); 5 | const baServices = require('../../lib/services'); 6 | 7 | describe('bacnet - Services layer WritePropertyMultiple unit', () => { 8 | it('should successfully encode and decode', () => { 9 | const buffer = utils.getBuffer(); 10 | const date = new Date(1, 1, 1); 11 | const time = new Date(1, 1, 1); 12 | time.setMilliseconds(990); 13 | baServices.writePropertyMultiple.encode(buffer, {type: 39, instance: 2400}, [ 14 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 15 | {type: 0, value: null}, 16 | {type: 0, value: null}, 17 | {type: 1, value: true}, 18 | {type: 1, value: false}, 19 | {type: 2, value: 1}, 20 | {type: 2, value: 1000}, 21 | {type: 2, value: 1000000}, 22 | {type: 2, value: 1000000000}, 23 | {type: 3, value: -1}, 24 | {type: 3, value: -1000}, 25 | {type: 3, value: -1000000}, 26 | {type: 3, value: -1000000000}, 27 | {type: 4, value: 0.1}, 28 | {type: 5, value: 100.121212}, 29 | {type: 6, value: [1, 2, 100, 200]}, 30 | {type: 7, value: 'Test1234$'}, 31 | {type: 8, value: {bitsUsed: 0, value: []}}, 32 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 33 | {type: 9, value: 4}, 34 | {type: 10, value: date}, 35 | {type: 11, value: time}, 36 | {type: 12, value: {type: 3, instance: 0}} 37 | ], priority: 0} 38 | ]); 39 | const result = baServices.writePropertyMultiple.decode(buffer.buffer, 0, buffer.offset); 40 | delete result.len; 41 | result.values[0].value[12].value = Math.floor(result.values[0].value[12].value * 1000) / 1000; 42 | expect(result).to.deep.equal({ 43 | objectId: { 44 | type: 39, 45 | instance: 2400 46 | }, 47 | values: [ 48 | { 49 | priority: 0, 50 | property: { 51 | index: 0xFFFFFFFF, 52 | id: 81 53 | }, 54 | value: [ 55 | {type: 0, value: null}, 56 | {type: 0, value: null}, 57 | {type: 1, value: true}, 58 | {type: 1, value: false}, 59 | {type: 2, value: 1}, 60 | {type: 2, value: 1000}, 61 | {type: 2, value: 1000000}, 62 | {type: 2, value: 1000000000}, 63 | {type: 3, value: -1}, 64 | {type: 3, value: -1000}, 65 | {type: 3, value: -1000000}, 66 | {type: 3, value: -1000000000}, 67 | {type: 4, value: 0.1}, 68 | {type: 5, value: 100.121212}, 69 | {type: 6, value: [1, 2, 100, 200]}, 70 | {type: 7, value: 'Test1234$', encoding: 0}, 71 | {type: 8, value: {bitsUsed: 0, value: []}}, 72 | {type: 8, value: {bitsUsed: 24, value: [0xAA, 0xAA, 0xAA]}}, 73 | {type: 9, value: 4}, 74 | {type: 10, value: date}, 75 | {type: 11, value: time}, 76 | {type: 12, value: {type: 3, instance: 0}} 77 | ] 78 | } 79 | ] 80 | }); 81 | }); 82 | 83 | it('should successfully encode and decode with defined priority', () => { 84 | const buffer = utils.getBuffer(); 85 | const date = new Date(1, 1, 1); 86 | const time = new Date(1, 1, 1); 87 | time.setMilliseconds(990); 88 | baServices.writePropertyMultiple.encode(buffer, {type: 39, instance: 2400}, [ 89 | {property: {id: 81, index: 0xFFFFFFFF}, value: [ 90 | {type: 7, value: 'Test1234$'} 91 | ], priority: 12} 92 | ]); 93 | const result = baServices.writePropertyMultiple.decode(buffer.buffer, 0, buffer.offset); 94 | delete result.len; 95 | expect(result).to.deep.equal({ 96 | objectId: { 97 | type: 39, 98 | instance: 2400 99 | }, 100 | values: [ 101 | { 102 | priority: 12, 103 | property: { 104 | index: 0xFFFFFFFF, 105 | id: 81 106 | }, 107 | value: [ 108 | {type: 7, value: 'Test1234$', encoding: 0} 109 | ] 110 | } 111 | ] 112 | }); 113 | }); 114 | 115 | it('should successfully encode and decode with defined array index', () => { 116 | const buffer = utils.getBuffer(); 117 | const date = new Date(1, 1, 1); 118 | const time = new Date(1, 1, 1); 119 | time.setMilliseconds(990); 120 | baServices.writePropertyMultiple.encode(buffer, {type: 39, instance: 2400}, [ 121 | {property: {id: 81, index: 414141}, value: [ 122 | {type: 7, value: 'Test1234$'} 123 | ], priority: 0} 124 | ]); 125 | const result = baServices.writePropertyMultiple.decode(buffer.buffer, 0, buffer.offset); 126 | delete result.len; 127 | expect(result).to.deep.equal({ 128 | objectId: { 129 | type: 39, 130 | instance: 2400 131 | }, 132 | values: [ 133 | { 134 | priority: 0, 135 | property: { 136 | index: 414141, 137 | id: 81 138 | }, 139 | value: [ 140 | {type: 7, value: 'Test1234$', encoding: 0} 141 | ] 142 | } 143 | ] 144 | }); 145 | }); 146 | }); 147 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const coreExports = { 4 | debug: require('debug')('bacnet:test:unit:debug'), 5 | trace: require('debug')('bacnet:test:unit:trace'), 6 | bacnetClient: require('../../') 7 | }; 8 | 9 | module.exports = coreExports; 10 | 11 | module.exports.getBuffer = () => { 12 | return { 13 | buffer: Buffer.alloc(1482), 14 | offset: 0 15 | }; 16 | }; 17 | --------------------------------------------------------------------------------