├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/dictionaries/klaus.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | bacnet
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/jshint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------