├── .eslintrc.json ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bugs.yaml │ └── features.yaml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── greet-collaborators.yml │ ├── snyk-analysis.yml │ └── stale.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _config.yml ├── clean.sh ├── examples ├── bacnet-room-simulator-yabe.json └── bacnet-simple-demo.json ├── gulpfile.js ├── images ├── BACnetFlowExample.png ├── BACnetFlowExampleSmall.png ├── BACnetFlowExamples.png ├── bacnet-icon-quad.png ├── bacnet-icon-quad128.png ├── bacnet-icon-quad64.png ├── bacnet-icon-small.xcf ├── bacnet-icon.xcf └── bacnet-nodes.png ├── index.js ├── npm-update.sh ├── npm-upgrade.sh ├── package-lock.json ├── package.json ├── src ├── bacnet-client.html ├── bacnet-client.js ├── bacnet-command.html ├── bacnet-command.js ├── bacnet-device.html ├── bacnet-device.js ├── bacnet-instance.html ├── bacnet-instance.js ├── bacnet-read.html ├── bacnet-read.js ├── bacnet-write.html ├── bacnet-write.js ├── core │ └── bacnet-core.js ├── icons │ └── bacnet-icon.png └── locales │ └── en-US │ ├── bacnet-client.json │ ├── bacnet-command.json │ ├── bacnet-device.json │ ├── bacnet-instance.json │ ├── bacnet-read.json │ ├── bacnet-write.json │ └── messages.json ├── supporter.js └── test ├── bacnet-command-test.js ├── bacnet-read-test.js └── bacnet-write-test.js /.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 | 480 11 | ], 12 | "semi": [ 13 | 2, 14 | "never" 15 | ], 16 | "curly": 0, 17 | "valid-jsdoc": 0 18 | }, 19 | "env": { 20 | "mocha": true, 21 | "node": true, 22 | "es2017": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to node-red-contrib-bacnet 2 | 3 | As in Node-RED we have some guidelines for the BACnet contribution package. 4 | We welcome contributions, but request you follow these guidelines. 5 | 6 | - [Coding rules](#coding-rules) 7 | - [Raising issues](#raising-issues) 8 | - [Feature requests](#feature-requests) 9 | - [Pull-Requests](#pull-requests) 10 | 11 | ## Coding rules 12 | 13 | 1. Code for one another, and use tools to perform mechanical optimizations. 14 | 2. Keep it simple; compactness != succinctness. 15 | 3. Just because you can doesn’t mean you should. 16 | 4. Utilize familiar paradigms and patterns. 17 | 5. Consistency is king. 18 | 6. Lay good foundations. Be mindful of evolutionary complexity. 19 | 20 | ## Raising issues 21 | 22 | Please raise any bug reports on the relevant project's issue tracker. 23 | Be sure to search the list to see if your issue has already been raised. 24 | 25 | A good bug report is one that make it easy for us to understand what you were 26 | trying to do and what went wrong. 27 | 28 | Provide as much context as possible so we can try to recreate the issue. 29 | If possible, include the relevant part of your flow. To do this, select the 30 | relevant nodes, press Ctrl-E and copy the flow data from the Export dialog. 31 | 32 | At a minimum, please include: 33 | 34 | - Version of node.js? (should be >= 16) 35 | - Version of Node-RED? (should be >= 3) 36 | - Version of node-red-contrib-bacnet? (should be >= 0.2) 37 | 38 | - What is your platform? (Linux, macOS, ...) 39 | - What does `DEBUG=bacnet:* node-red -v` say? (log files are welcome) 40 | 41 | ## Feature requests 42 | 43 | For feature requests, please raise them on the relevant project's issue tracker. 44 | 45 | ## Pull-Requests 46 | 47 | If you want to raise a pull-request with a new feature, or a refactoring 48 | of existing code, it may well get rejected if you haven't discussed it on the relevant project's issue tracker first. 49 | 50 | ### Coding standards 51 | 52 | Please ensure you follow the coding standards used through-out the existing code base. 53 | 54 | Some basic rules include: 55 | 56 | - all files must have the MIT license in the header. 57 | - indent with 2-spaces, no tabs. No arguments. 58 | - follow ES6 coding standards 59 | - follow standard.js coding standards 60 | 61 | ### Update 62 | 63 | ### Upgrade 64 | 65 | ### Return of Code Investment 66 | 67 | Please, make pull requests! 68 | If you are not sure how to do this, then ask for help please! 69 | It is open source to collaborate and to give some return of investment with your code. 70 | 71 | 72 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: ["https://github.com/sponsors/BiancoRoyal", "https://bianco-royal.space/supporter/", "https://opencollective.com/node-red-contrib-modbus", "https://www.noderedplus.de/"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bugs.yaml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: Report a bug or unexpected behavior encountered. 3 | labels: 4 | - bug 5 | body: 6 | - type: "input" 7 | id: "version" 8 | attributes: 9 | label: Which node-red-contrib-bacnet version are you using? 10 | description: | 11 | Please check the `package.json` file in your Node-RED data directory (default: `$HOME/.node-red/`). 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: descriptions 16 | attributes: 17 | label: What happened? 18 | description: | 19 | Please provide a clear and thorough description of what happened. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: reproduction 24 | attributes: 25 | label: How can this be reproduced? 26 | description: | 27 | Please provide a step-by-step description of how to reproduce this behavior. 28 | Please attach relevant flows as a `.json` file. (In the top-right menu, click `Export`) 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: expected 33 | attributes: 34 | label: What did you expect to happen? 35 | description: | 36 | Describe the expected behavior and how the actual behavior differed. 37 | validations: 38 | required: false 39 | 40 | - type: textarea 41 | id: other 42 | attributes: 43 | label: Other Information 44 | description: | 45 | Please provide any other information you feel is relevant, such as Node-RED or NodeJS versions. 46 | validations: 47 | required: false 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/features.yaml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: Request a new feature 3 | labels: 4 | - feature 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the requested feature 10 | description: | 11 | Describe the feature you would like to see added. 12 | Feel free to attach diagrams or screenshots. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: motivation 17 | attributes: 18 | label: Motivation 19 | description: | 20 | Describe why you would like to see this feature or the use-case for the feature. 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | **Which issues are addressed by this Pull Request?** 9 | 10 | **What does this Pull Request change?** 11 | 12 | **Does this Pull Request introduce any potentially breaking changes?** 13 | 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | target-branch: "develop" 6 | schedule: 7 | interval: "weekly" 8 | allow: 9 | - dependency-type: "production" 10 | ignore: 11 | - dependency-name: "modbus*" 12 | - dependency-name: "*modbus*" 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | push: 7 | branches: 8 | - develop 9 | - development 10 | - alpha 11 | - beta 12 | - master 13 | - main 14 | jobs: 15 | build: 16 | runs-on: ubuntu-20.04 17 | strategy: 18 | matrix: 19 | node: [14, 16, 18, 20] 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node }} 25 | - run: npm install 26 | - run: npm run build 27 | - run: tar -czvf distrib.tar.gz bacnet examples package.json docs index.js README.md CHANGELOG.md 28 | - uses: actions/upload-artifact@v3 29 | with: 30 | name: build-folders 31 | path: distrib.tar.gz 32 | 33 | publish: 34 | runs-on: ubuntu-20.04 35 | needs: build 36 | if: github.ref == 'refs/heads/master' 37 | steps: 38 | - uses: actions/checkout@v3 39 | - uses: actions/setup-node@v3 40 | with: 41 | node-version: 16 42 | - run: npm install 43 | - uses: JS-DevTools/npm-publish@v1 44 | with: 45 | token: ${{ secrets.NPM_TOKEN }} 46 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: 17 | - main 18 | - master 19 | - develop 20 | - development 21 | pull_request: 22 | # The branches below must be a subset of the branches above 23 | branches: 24 | - develop 25 | schedule: 26 | - cron: '39 20 * * 0' 27 | 28 | jobs: 29 | analyze: 30 | name: Analyze 31 | runs-on: ubuntu-latest 32 | permissions: 33 | actions: read 34 | contents: read 35 | security-events: write 36 | 37 | strategy: 38 | fail-fast: false 39 | matrix: 40 | language: [ 'javascript' ] 41 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 42 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 43 | 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v2 47 | 48 | # Initializes the CodeQL tools for scanning. 49 | - name: Initialize CodeQL 50 | uses: github/codeql-action/init@v2 51 | with: 52 | languages: ${{ matrix.language }} 53 | # If you wish to specify custom queries, you can do so here or in a config file. 54 | # By default, queries listed here will override any specified in a config file. 55 | # Prefix the list here with "+" to use these queries and those in the config file. 56 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 57 | 58 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 59 | # If this step fails, then you should remove it and run the build manually (see below) 60 | - name: Autobuild 61 | uses: github/codeql-action/autobuild@v2 62 | 63 | # ℹ️ Command-line programs to run using the OS shell. 64 | # 📚 https://git.io/JvXDl 65 | 66 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 67 | # and modify them (or add more) to build your code if your project 68 | # uses a compiled language 69 | 70 | #- run: | 71 | # make bootstrap 72 | # make release 73 | 74 | - name: Perform CodeQL Analysis 75 | uses: github/codeql-action/analyze@v2 76 | -------------------------------------------------------------------------------- /.github/workflows/greet-collaborators.yml: -------------------------------------------------------------------------------- 1 | name: "Greet Contributors" 2 | on: 3 | pull_request: 4 | types: [opened, synchronize] 5 | 6 | jobs: 7 | GreetCommitter: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: "Greet Contributor" 11 | uses: ibakshay/greet-contributors-action@v3 12 | env: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/snyk-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Snyk Vulnerability Analysis" 2 | on: 3 | push: 4 | branches: 5 | - 6-additional-github-actions 6 | - main 7 | pull_request: 8 | types: 9 | - synchronize 10 | - opened 11 | jobs: 12 | snyk: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Run Snyk vulnerability scan 17 | uses: snyk/actions/node@master 18 | env: 19 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 20 | with: 21 | command: monitor 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v4 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. It will be closed in 30 days, but can be saved by removing the stale label or commenting.' 13 | days-before-stale: 60 14 | days-before-close: 30 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | node_modules/ 4 | bacnet/ 5 | error*.txt 6 | .DS_Store 7 | maps/ 8 | docs/gen/ 9 | code/ 10 | *.log 11 | 12 | coverage/ 13 | -------------------------------------------------------------------------------- /.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 | src 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | matrix: 3 | include: 4 | - node_js: 14 5 | - node_js: "lts/*" 6 | - node_js: 10 7 | allow_failures: 8 | - node_js: 14 9 | - node_js: 10 10 | install: 11 | - npm install 12 | - npm install --only=dev 13 | script: 14 | - npm test 15 | - npm run coverage 16 | - npm run build 17 | cache: 18 | directories: 19 | - "node_modules" 20 | branches: 21 | only: 22 | - master 23 | - develop 24 | - alpha 25 | - beta 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.3.0](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.7...v0.3.0) (2024-04-15) 2 | 3 | 4 | 5 | ## [0.2.7](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.6...v0.2.7) (2022-09-25) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * Node-RED PLUS changed to PLUS for Node-RED ([bf86f55](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/bf86f55dc3521915ec16b8969618fe20ef8ec31d)) 11 | 12 | 13 | 14 | ## [0.2.6](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.5...v0.2.6) (2022-07-29) 15 | 16 | 17 | 18 | ## [0.2.5](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.4...v0.2.5) (2022-07-29) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * upgrade debug from 4.3.1 to 4.3.4 ([8b510c5](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/8b510c5b7b3cf53add8d4c11c7228b6cc03b0de5)) 24 | * upgrade underscore from 1.11.0 to 1.13.4 ([74a057d](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/74a057d0e5f77dbd046c3b0ba228ca6cff2a0010)) 25 | 26 | 27 | ### Features 28 | 29 | * **write:** priority handling and number checks ([289a386](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/289a386c48c722655138e19d185c272b5872ad5e)) 30 | 31 | 32 | 33 | ## [0.2.2](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.1...v0.2.2) (2020-06-03) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * **write:** enum typo ([79d9979](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/79d99793271ba0e36cc02f9e96dab0f6116762b0)) 39 | 40 | 41 | 42 | ## [0.2.1](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.2.0-beta.1...v0.2.1) (2020-06-03) 43 | 44 | 45 | ### Bug Fixes 46 | 47 | * **lib:** update to node-bacnet ([484fd52](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/484fd5209826571f1a5d5abc1c0dbde572b0c4f6)) 48 | * **lib:** update to node-bacnet ([d1e07e1](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/d1e07e16decf3069d8a9823324a68ed3651e69b1)) 49 | 50 | 51 | 52 | ## [0.1.2](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.0.16...v0.1.2) (2020-04-21) 53 | 54 | 55 | ### Bug Fixes 56 | 57 | * install error ([79af02a](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/79af02a145d3819d0b1b6bb6240f80553e524953)) 58 | * typos ([203523a](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/203523a64dd4e3051e881ea63f3bdb04ee3400fe)) 59 | * **write:** type instead of tag for values ([dc92ad9](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/dc92ad92d6828880fe17cb0f9eb7f0375a34af3c)) 60 | 61 | 62 | ### Features 63 | 64 | * **device:** new device config ([5bad469](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/5bad469d04fea7d0da0a7686c84b515dbcb51553)) 65 | 66 | 67 | 68 | ## [0.0.16](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.0.15...v0.0.16) (2018-04-23) 69 | 70 | 71 | 72 | ## [0.0.15](https://github.com/BiancoRoyal/node-red-contrib-bacnet/compare/v0.0.14...v0.0.15) (2018-04-23) 73 | 74 | 75 | ### Bug Fixes 76 | 77 | * **write:** IP address missing via payload ([e1a3ef1](https://github.com/BiancoRoyal/node-red-contrib-bacnet/commit/e1a3ef1313f468a2123d339a54c6d737cc211201)) 78 | 79 | 80 | 81 | ## 0.0.14 (2018-02-19) 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017,2018,2019,2020,2021,2022 Klaus Landsdorf (http://node-red.plus/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Platform Node-RED](https://img.shields.io/badge/Platform-Node--RED-red.png) 2 | ![Node-RED BACnet](https://img.shields.io/badge/Contribution-BACnet-blue.png) 3 | [![Repository GitHub](https://img.shields.io/badge/Repository-GitHub-orange.png)](https://github.com/BiancoRoyal/node-red-contrib-bacnet) 4 | [![NPM download](https://img.shields.io/npm/dm/node-red-contrib-bacnet.svg)](http://www.npm-stats.com/~packages/node-red-contrib-bacnet) 5 | [![Quality Travis CI](https://img.shields.io/badge/Quality-Travis_CI-green.png)](https://travis-ci.org/BiancoRoyal/node-red-contrib-bacnet) 6 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/6cbeb40ab5604b3ab99e6badc9469e8a)](https://www.codacy.com/gh/BiancoRoyal/node-red-contrib-bacnet?utm_source=github.com&utm_medium=referral&utm_content=BiancoRoyal/node-red-contrib-bacnet&utm_campaign=Badge_Grade) 7 | [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/BiancoRoyal/node-red-contrib-bacnet) 8 | 9 | [![nodemodbus64](images/bacnet-icon-quad64.png)](http://www.bacnet.org/) 10 | 11 | # node-red-contrib-bacnet 12 | 13 | Building Automation and Control Networks Protocol toolbox for Node-RED. 14 | 15 | ### International IIoT Website for Node-RED 16 | 17 | For an international area, [Iniationware][6] has provided the [PLUS for Node-RED International][8] website. 18 | 19 | ### IIoT Webseite Deutschland für Node-RED 20 | 21 | Für einen deutschsprachigen Bereich hat [Iniationware][9] die Webseite [PLUS for Node-RED Germany][7] bereitgestellt. 22 | 23 | 24 | ## Install 25 | 26 | Run command on Node-RED installation directory. 27 | 28 | npm install node-red-contrib-bacnet 29 | 30 | or run command for global installation. 31 | 32 | npm install -g node-red-contrib-bacnet 33 | 34 | try these options on npm install to build, if you have problems to install 35 | 36 | --unsafe-perm --build-from-source 37 | 38 | ![Flow Example](images/BACnetFlowExamples.png) 39 | 40 | ### License 41 | 42 | The MIT License with support via [Subscription bundle][3] or GitHub Sponsoring 43 | [Klaus Landsdorf][1] and Community driven work 44 | 45 | ### Important 46 | 47 | This is **not** an official product of the BACnet Advocacy Group. 48 | It is just to provide BACnet to Node-RED based on node-bacstack package. 49 | BACnet® is a registered trademark of American Society of Heating, Refrigerating and Air-Conditioning Engineers (ASHRAE). 50 | 51 | ### Contribution NodeJS BACnet® Library 52 | 53 | I'd like to give special thanks to [fh1ch][2] and [Apollon77][4] for their work on the NodeJS-BACnet-Library. 54 | 55 | 56 | [1]:https://github.com/sponsors/biancode 57 | [2]:https://github.com/fh1ch 58 | [3]:https://plus4nodered.com/ 59 | [4]:https://github.com/Apollon77 60 | [5]:https://github.com/BiancoRoyal 61 | [6]:https://iniationware.com/ 62 | [7]:https://www.p4nr.com/de/ 63 | [8]:https://www.p4nr.com/ 64 | [9]:https://iniationware.de/ -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf node_modules/ 4 | 5 | rm -rf bacnet/ 6 | 7 | rm -rf code/ 8 | 9 | rm -rf coverage/ 10 | 11 | rm package-lock.json 12 | 13 | npm cache verify 14 | 15 | npm install 16 | 17 | npm i --only=dev 18 | 19 | npm test 20 | 21 | npm run build 22 | 23 | npm run rewrite-changelog 24 | -------------------------------------------------------------------------------- /examples/bacnet-room-simulator-yabe.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "b387cb.56722838", 4 | "type": "tab", 5 | "label": "BACnet Room", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "a8baa94.6a35458", 11 | "type": "BACnet-Read", 12 | "z": "b387cb.56722838", 13 | "name": "Room Simulator", 14 | "objectType": "8", 15 | "instance": "cf0dca49.2a9ac", 16 | "propertyId": "28", 17 | "device": "7adbdd83.bce224", 18 | "server": "e0bf099a.ee8468", 19 | "multipleRead": false, 20 | "x": 440, 21 | "y": 220, 22 | "wires": [ 23 | [ 24 | "863b5c82.8792c" 25 | ] 26 | ] 27 | }, 28 | { 29 | "id": "5be7ac40.2603fc", 30 | "type": "inject", 31 | "z": "b387cb.56722838", 32 | "name": "", 33 | "props": [ 34 | { 35 | "p": "payload" 36 | }, 37 | { 38 | "p": "topic", 39 | "vt": "str" 40 | } 41 | ], 42 | "repeat": "", 43 | "crontab": "", 44 | "once": false, 45 | "onceDelay": 0.1, 46 | "topic": "", 47 | "payload": "", 48 | "payloadType": "date", 49 | "x": 240, 50 | "y": 220, 51 | "wires": [ 52 | [ 53 | "a8baa94.6a35458" 54 | ] 55 | ] 56 | }, 57 | { 58 | "id": "863b5c82.8792c", 59 | "type": "debug", 60 | "z": "b387cb.56722838", 61 | "name": "", 62 | "active": true, 63 | "tosidebar": true, 64 | "console": false, 65 | "tostatus": false, 66 | "complete": "true", 67 | "targetType": "full", 68 | "statusVal": "", 69 | "statusType": "auto", 70 | "x": 830, 71 | "y": 440, 72 | "wires": [] 73 | }, 74 | { 75 | "id": "a795559.a54daa8", 76 | "type": "BACnet-Command", 77 | "z": "b387cb.56722838", 78 | "name": "Who is?", 79 | "commandType": "whoIs", 80 | "timeDuration": 0, 81 | "enableDisable": 0, 82 | "deviceState": 0, 83 | "isUtc": true, 84 | "lowLimit": 0, 85 | "highLimit": 0, 86 | "device": "7adbdd83.bce224", 87 | "server": "e0bf099a.ee8468", 88 | "x": 500, 89 | "y": 440, 90 | "wires": [ 91 | [ 92 | "863b5c82.8792c" 93 | ] 94 | ] 95 | }, 96 | { 97 | "id": "492b7237.75fb5c", 98 | "type": "inject", 99 | "z": "b387cb.56722838", 100 | "name": "", 101 | "props": [ 102 | { 103 | "p": "payload" 104 | }, 105 | { 106 | "p": "topic", 107 | "vt": "str" 108 | } 109 | ], 110 | "repeat": "", 111 | "crontab": "", 112 | "once": false, 113 | "onceDelay": 0.1, 114 | "topic": "", 115 | "payload": "", 116 | "payloadType": "date", 117 | "x": 320, 118 | "y": 440, 119 | "wires": [ 120 | [ 121 | "a795559.a54daa8" 122 | ] 123 | ] 124 | }, 125 | { 126 | "id": "be42b68d.9c2ed", 127 | "type": "BACnet-Read", 128 | "z": "b387cb.56722838", 129 | "name": "Read Temperature Indoor", 130 | "objectType": "0", 131 | "instance": "ef226397.1987f", 132 | "propertyId": "85", 133 | "device": "7adbdd83.bce224", 134 | "server": "e0bf099a.ee8468", 135 | "multipleRead": false, 136 | "x": 460, 137 | "y": 280, 138 | "wires": [ 139 | [ 140 | "863b5c82.8792c" 141 | ] 142 | ] 143 | }, 144 | { 145 | "id": "ada8e128.2f4ae8", 146 | "type": "inject", 147 | "z": "b387cb.56722838", 148 | "name": "", 149 | "props": [ 150 | { 151 | "p": "payload" 152 | }, 153 | { 154 | "p": "topic", 155 | "vt": "str" 156 | } 157 | ], 158 | "repeat": "", 159 | "crontab": "", 160 | "once": false, 161 | "onceDelay": 0.1, 162 | "topic": "", 163 | "payload": "", 164 | "payloadType": "date", 165 | "x": 230, 166 | "y": 280, 167 | "wires": [ 168 | [ 169 | "be42b68d.9c2ed" 170 | ] 171 | ] 172 | }, 173 | { 174 | "id": "e9ea2327.8e1048", 175 | "type": "BACnet-Read", 176 | "z": "b387cb.56722838", 177 | "name": "Read Temperature Water", 178 | "objectType": "2", 179 | "instance": "ef226397.1987f", 180 | "propertyId": "85", 181 | "device": "7adbdd83.bce224", 182 | "server": "e0bf099a.ee8468", 183 | "multipleRead": false, 184 | "x": 450, 185 | "y": 340, 186 | "wires": [ 187 | [ 188 | "863b5c82.8792c" 189 | ] 190 | ] 191 | }, 192 | { 193 | "id": "dbed4100.eece28", 194 | "type": "inject", 195 | "z": "b387cb.56722838", 196 | "name": "", 197 | "props": [ 198 | { 199 | "p": "payload" 200 | }, 201 | { 202 | "p": "topic", 203 | "vt": "str" 204 | } 205 | ], 206 | "repeat": "", 207 | "crontab": "", 208 | "once": false, 209 | "onceDelay": 0.1, 210 | "topic": "", 211 | "payload": "", 212 | "payloadType": "date", 213 | "x": 220, 214 | "y": 340, 215 | "wires": [ 216 | [ 217 | "e9ea2327.8e1048" 218 | ] 219 | ] 220 | }, 221 | { 222 | "id": "cf0dca49.2a9ac", 223 | "type": "BACnet-Instance", 224 | "name": "Room Simulator", 225 | "instanceAddress": "1753211" 226 | }, 227 | { 228 | "id": "7adbdd83.bce224", 229 | "type": "BACnet-Device", 230 | "name": "Windows VM", 231 | "deviceAddress": "192.168.8.162" 232 | }, 233 | { 234 | "id": "e0bf099a.ee8468", 235 | "type": "BACnet-Client", 236 | "name": "", 237 | "adpuTimeout": "3000", 238 | "port": "47808", 239 | "interface": "192.168.8.133", 240 | "broadcastAddress": "192.168.8.255" 241 | }, 242 | { 243 | "id": "ef226397.1987f", 244 | "type": "BACnet-Instance", 245 | "name": "Temperature Water", 246 | "instanceAddress": "1" 247 | } 248 | ] -------------------------------------------------------------------------------- /examples/bacnet-simple-demo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "300fd127.935c9e", 4 | "type": "tab", 5 | "label": "BACnet Tests", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "1474e606.834fea", 11 | "type": "BACnet-Command", 12 | "z": "300fd127.935c9e", 13 | "name": "Who is?", 14 | "commandType": "whoIs", 15 | "timeDuration": 0, 16 | "enableDisable": 0, 17 | "deviceState": 0, 18 | "isUtc": true, 19 | "lowLimit": "1", 20 | "highLimit": "254", 21 | "device": "b289851b.dec6f8", 22 | "server": "977c5141.b0d14", 23 | "x": 560, 24 | "y": 60, 25 | "wires": [ 26 | [ 27 | "200bcd1a.e47c92" 28 | ] 29 | ] 30 | }, 31 | { 32 | "id": "200bcd1a.e47c92", 33 | "type": "debug", 34 | "z": "300fd127.935c9e", 35 | "name": "", 36 | "active": true, 37 | "tosidebar": true, 38 | "console": false, 39 | "tostatus": false, 40 | "complete": "true", 41 | "x": 950, 42 | "y": 280, 43 | "wires": [] 44 | }, 45 | { 46 | "id": "b302933.0273a7", 47 | "type": "inject", 48 | "z": "300fd127.935c9e", 49 | "name": "", 50 | "repeat": "", 51 | "crontab": "", 52 | "once": false, 53 | "onceDelay": 0.1, 54 | "topic": "", 55 | "payload": "", 56 | "payloadType": "date", 57 | "x": 220, 58 | "y": 60, 59 | "wires": [ 60 | [ 61 | "1474e606.834fea" 62 | ] 63 | ] 64 | }, 65 | { 66 | "id": "4e2a4428.2a8d9c", 67 | "type": "BACnet-Read", 68 | "z": "300fd127.935c9e", 69 | "name": "Multiple Read", 70 | "objectType": "0", 71 | "instance": "cf0dca49.2a9ac", 72 | "propertyId": "85", 73 | "device": "b289851b.dec6f8", 74 | "server": "977c5141.b0d14", 75 | "multipleRead": true, 76 | "x": 580, 77 | "y": 300, 78 | "wires": [ 79 | [ 80 | "200bcd1a.e47c92" 81 | ] 82 | ] 83 | }, 84 | { 85 | "id": "2e0dd7f5.12157", 86 | "type": "inject", 87 | "z": "300fd127.935c9e", 88 | "name": "", 89 | "repeat": "", 90 | "crontab": "", 91 | "once": false, 92 | "onceDelay": 0.1, 93 | "topic": "", 94 | "payload": "", 95 | "payloadType": "date", 96 | "x": 220, 97 | "y": 300, 98 | "wires": [ 99 | [ 100 | "4e2a4428.2a8d9c" 101 | ] 102 | ] 103 | }, 104 | { 105 | "id": "86a69fc0.d172a8", 106 | "type": "BACnet-Write", 107 | "z": "300fd127.935c9e", 108 | "name": "", 109 | "objectType": "1", 110 | "instance": "cf0dca49.2a9ac", 111 | "valueTag": "1", 112 | "valueValue": "", 113 | "propertyId": "28", 114 | "device": "b289851b.dec6f8", 115 | "server": "977c5141.b0d14", 116 | "multipleWrite": true, 117 | "x": 600, 118 | "y": 440, 119 | "wires": [ 120 | [ 121 | "200bcd1a.e47c92" 122 | ] 123 | ] 124 | }, 125 | { 126 | "id": "53594965.4933c8", 127 | "type": "inject", 128 | "z": "300fd127.935c9e", 129 | "name": "", 130 | "repeat": "", 131 | "crontab": "", 132 | "once": false, 133 | "onceDelay": 0.1, 134 | "topic": "", 135 | "payload": "", 136 | "payloadType": "date", 137 | "x": 220, 138 | "y": 440, 139 | "wires": [ 140 | [ 141 | "70ffebee.2bde24" 142 | ] 143 | ] 144 | }, 145 | { 146 | "id": "2f3b2773.acef9", 147 | "type": "BACnet-Command", 148 | "z": "300fd127.935c9e", 149 | "name": "Time sync", 150 | "commandType": "timeSync", 151 | "timeDuration": 0, 152 | "enableDisable": 0, 153 | "deviceState": 0, 154 | "isUtc": false, 155 | "lowLimit": "1", 156 | "highLimit": "254", 157 | "device": "b289851b.dec6f8", 158 | "server": "977c5141.b0d14", 159 | "x": 570, 160 | "y": 120, 161 | "wires": [ 162 | [ 163 | "200bcd1a.e47c92" 164 | ] 165 | ] 166 | }, 167 | { 168 | "id": "8b2f4c26.5b25b", 169 | "type": "inject", 170 | "z": "300fd127.935c9e", 171 | "name": "", 172 | "repeat": "", 173 | "crontab": "", 174 | "once": false, 175 | "onceDelay": 0.1, 176 | "topic": "", 177 | "payload": "", 178 | "payloadType": "date", 179 | "x": 220, 180 | "y": 120, 181 | "wires": [ 182 | [ 183 | "2f3b2773.acef9" 184 | ] 185 | ] 186 | }, 187 | { 188 | "id": "9188383b.0b6d98", 189 | "type": "BACnet-Command", 190 | "z": "300fd127.935c9e", 191 | "name": "Device warm start", 192 | "commandType": "reinitializeDevice", 193 | "timeDuration": 0, 194 | "enableDisable": 0, 195 | "deviceState": "1", 196 | "isUtc": true, 197 | "lowLimit": "1", 198 | "highLimit": "254", 199 | "device": "b289851b.dec6f8", 200 | "server": "977c5141.b0d14", 201 | "x": 590, 202 | "y": 180, 203 | "wires": [ 204 | [ 205 | "200bcd1a.e47c92" 206 | ] 207 | ] 208 | }, 209 | { 210 | "id": "1727085a.2ad738", 211 | "type": "inject", 212 | "z": "300fd127.935c9e", 213 | "name": "", 214 | "repeat": "", 215 | "crontab": "", 216 | "once": false, 217 | "onceDelay": 0.1, 218 | "topic": "", 219 | "payload": "", 220 | "payloadType": "date", 221 | "x": 220, 222 | "y": 180, 223 | "wires": [ 224 | [ 225 | "9188383b.0b6d98" 226 | ] 227 | ] 228 | }, 229 | { 230 | "id": "a9b32191.c9f9b8", 231 | "type": "inject", 232 | "z": "300fd127.935c9e", 233 | "name": "", 234 | "repeat": "", 235 | "crontab": "", 236 | "once": false, 237 | "onceDelay": 0.1, 238 | "topic": "", 239 | "payload": "", 240 | "payloadType": "date", 241 | "x": 220, 242 | "y": 360, 243 | "wires": [ 244 | [ 245 | "9606ff81.93a5" 246 | ] 247 | ] 248 | }, 249 | { 250 | "id": "a5b92606.bafb38", 251 | "type": "BACnet-Write", 252 | "z": "300fd127.935c9e", 253 | "name": "", 254 | "objectType": "40", 255 | "instance": "be63e3d0.b280c", 256 | "valueTag": "7", 257 | "valueValue": "Comfort Plus", 258 | "propertyId": "85", 259 | "priority": "8", 260 | "device": "b289851b.dec6f8", 261 | "server": "977c5141.b0d14", 262 | "multipleWrite": false, 263 | "x": 600, 264 | "y": 500, 265 | "wires": [ 266 | [ 267 | "200bcd1a.e47c92" 268 | ] 269 | ] 270 | }, 271 | { 272 | "id": "35d9327f.6ce47e", 273 | "type": "inject", 274 | "z": "300fd127.935c9e", 275 | "name": "", 276 | "repeat": "", 277 | "crontab": "", 278 | "once": false, 279 | "onceDelay": 0.1, 280 | "topic": "", 281 | "payload": "", 282 | "payloadType": "date", 283 | "x": 220, 284 | "y": 500, 285 | "wires": [ 286 | [ 287 | "a5b92606.bafb38" 288 | ] 289 | ] 290 | }, 291 | { 292 | "id": "9606ff81.93a5", 293 | "type": "BACnet-Read", 294 | "z": "300fd127.935c9e", 295 | "name": "Simple Read", 296 | "objectType": "40", 297 | "instance": "be63e3d0.b280c", 298 | "propertyId": "85", 299 | "device": "b289851b.dec6f8", 300 | "server": "977c5141.b0d14", 301 | "multipleRead": false, 302 | "x": 570, 303 | "y": 360, 304 | "wires": [ 305 | [ 306 | "200bcd1a.e47c92" 307 | ] 308 | ] 309 | }, 310 | { 311 | "id": "9eae08cc.131018", 312 | "type": "BACnet-Command", 313 | "z": "300fd127.935c9e", 314 | "name": "DCC disable 10 sec.", 315 | "commandType": "deviceCommunicationControl", 316 | "timeDuration": "10", 317 | "enableDisable": "1", 318 | "deviceState": "255", 319 | "isUtc": true, 320 | "lowLimit": "1", 321 | "highLimit": "254", 322 | "device": "b289851b.dec6f8", 323 | "server": "977c5141.b0d14", 324 | "x": 600, 325 | "y": 240, 326 | "wires": [ 327 | [ 328 | "200bcd1a.e47c92" 329 | ] 330 | ] 331 | }, 332 | { 333 | "id": "c94f841c.dd7fb", 334 | "type": "inject", 335 | "z": "300fd127.935c9e", 336 | "name": "", 337 | "repeat": "", 338 | "crontab": "", 339 | "once": false, 340 | "onceDelay": 0.1, 341 | "topic": "", 342 | "payload": "", 343 | "payloadType": "date", 344 | "x": 220, 345 | "y": 240, 346 | "wires": [ 347 | [ 348 | "9eae08cc.131018" 349 | ] 350 | ] 351 | }, 352 | { 353 | "id": "70ffebee.2bde24", 354 | "type": "function", 355 | "z": "300fd127.935c9e", 356 | "name": "bacnet write msg", 357 | "func": "msg.payload = {\n // deviceIPAddress: '127.0.0.1'\n values: [\n {\n // objectId: {type: 8, instance: 44301}, \n values: [\n {property: {id: 28, index: 12}, value: [{type: 1, value: true}], priority: 8}\n ]\n }\n ]\n}\nreturn msg;", 358 | "outputs": 1, 359 | "noerr": 0, 360 | "x": 390, 361 | "y": 440, 362 | "wires": [ 363 | [ 364 | "86a69fc0.d172a8" 365 | ] 366 | ] 367 | }, 368 | { 369 | "id": "4002625d.6c648c", 370 | "type": "BACnet-Write", 371 | "z": "300fd127.935c9e", 372 | "name": "", 373 | "objectType": "40", 374 | "instance": "be63e3d0.b280c", 375 | "valueTag": "7", 376 | "valueValue": "Comfort", 377 | "propertyId": "85", 378 | "priority": "8", 379 | "device": "b289851b.dec6f8", 380 | "server": "977c5141.b0d14", 381 | "multipleWrite": false, 382 | "x": 600, 383 | "y": 580, 384 | "wires": [ 385 | [ 386 | "200bcd1a.e47c92" 387 | ] 388 | ] 389 | }, 390 | { 391 | "id": "c845655.0cd0198", 392 | "type": "inject", 393 | "z": "300fd127.935c9e", 394 | "name": "", 395 | "repeat": "", 396 | "crontab": "", 397 | "once": false, 398 | "onceDelay": 0.1, 399 | "topic": "", 400 | "payload": "", 401 | "payloadType": "date", 402 | "x": 220, 403 | "y": 580, 404 | "wires": [ 405 | [ 406 | "4002625d.6c648c" 407 | ] 408 | ] 409 | }, 410 | { 411 | "id": "b289851b.dec6f8", 412 | "type": "BACnet-Device", 413 | "name": "Windows VM", 414 | "deviceAddress": "192.168.8.162" 415 | }, 416 | { 417 | "id": "977c5141.b0d14", 418 | "type": "BACnet-Client", 419 | "z": "300fd127.935c9e", 420 | "name": "Local BACnet", 421 | "adpuTimeout": "3000", 422 | "port": "47808", 423 | "interface": "192.168.8.133", 424 | "broadcastAddress": "192.168.8.255" 425 | }, 426 | { 427 | "id": "cf0dca49.2a9ac", 428 | "type": "BACnet-Instance", 429 | "name": "Room Simulator YABE", 430 | "instanceAddress": "0" 431 | }, 432 | { 433 | "id": "be63e3d0.b280c", 434 | "type": "BACnet-Instance", 435 | "name": "Text 1", 436 | "instanceAddress": "1" 437 | } 438 | ] -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017-2022 Klaus Landsdorf (http://node-red.plus/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 9 | 'use strict' 10 | 11 | const { series, src, dest } = require('gulp') 12 | const htmlmin = require('gulp-htmlmin') 13 | const jsdoc = require('gulp-jsdoc3') 14 | const clean = require('gulp-clean') 15 | const uglify = require('gulp-uglify') 16 | const babel = require('gulp-babel') 17 | const sourcemaps = require('gulp-sourcemaps') 18 | const pump = require('pump') 19 | const replace = require('gulp-replace') 20 | const changelog = require('gulp-conventional-changelog') 21 | 22 | function releaseIcons () { 23 | return src('src/icons/**/*').pipe(dest('bacnet/icons')) 24 | } 25 | 26 | function docIcons () { 27 | return src('src/icons/**/*').pipe(dest('docs/gen/icons')) 28 | } 29 | 30 | function docImages () { 31 | return src('images/**/*').pipe(dest('docs/gen/images')) 32 | } 33 | 34 | function releaseLocal () { 35 | return src('src/locales/**/*').pipe(dest('bacnet/locales')) 36 | } 37 | 38 | function releasePublicData () { 39 | return src('src/public/**/*').pipe(dest('bacnet/public')) 40 | } 41 | 42 | function cleanProject () { 43 | return src(['bacnet', 'docs/gen', 'jcoverage'], { allowEmpty: true }) 44 | .pipe(clean({ force: true })) 45 | } 46 | 47 | function changelogUpdate () { 48 | return src('CHANGELOG.md') 49 | .pipe(changelog({ 50 | // conventional-changelog options go here 51 | preset: 'angular', 52 | releaseCount: 0 53 | }, { 54 | // context goes here 55 | }, { 56 | // git-raw-commits options go here 57 | }, { 58 | // conventional-commits-parser options go here 59 | }, { 60 | // conventional-changelog-writer options go here 61 | })) 62 | .pipe(dest('./')) 63 | } 64 | 65 | function releaseWebContent () { 66 | return src('src/*.htm*') 67 | .pipe(htmlmin({ 68 | minifyJS: true, 69 | minifyCSS: true, 70 | minifyURLs: true, 71 | maxLineLength: 120, 72 | preserveLineBreaks: false, 73 | collapseWhitespace: true, 74 | collapseInlineTagWhitespace: true, 75 | conservativeCollapse: true, 76 | processScripts: ['text/x-red'], 77 | quoteCharacter: "'" 78 | })) 79 | .pipe(dest('bacnet')) 80 | } 81 | 82 | function releaseJSContent (cb) { 83 | const anchor = '// SOURCE-MAP-REQUIRED' 84 | 85 | pump([ 86 | src('src/**/*.js') 87 | .pipe(sourcemaps.init({ loadMaps: true })) 88 | .pipe(replace(anchor, 'require(\'source-map-support\').install()')) 89 | .pipe(babel({ presets: ['@babel/env'] })) 90 | .pipe(uglify()) 91 | .pipe(sourcemaps.write('maps')), dest('bacnet')], 92 | cb) 93 | } 94 | 95 | function codeJSContent (cb) { 96 | const anchor = '// SOURCE-MAP-REQUIRED' 97 | 98 | pump([ 99 | src('src/**/*.js') 100 | .pipe(sourcemaps.init({ loadMaps: true })) 101 | .pipe(replace(anchor, 'require(\'source-map-support\').install()')) 102 | .pipe(babel({ presets: ['@babel/env'] })) 103 | .pipe(sourcemaps.write('maps')), dest('code')], 104 | cb) 105 | } 106 | 107 | function doc (cb) { 108 | src(['README.md', 'src/**/*.js'], { read: false }) 109 | .pipe(jsdoc(cb)) 110 | } 111 | 112 | exports.default = series(cleanProject, releaseWebContent, releaseJSContent, codeJSContent, releaseLocal, releasePublicData, releaseIcons, doc, docIcons, docImages, changelogUpdate) 113 | exports.clean = cleanProject 114 | exports.build = series(cleanProject, releaseWebContent, releaseJSContent, releaseLocal, codeJSContent) 115 | exports.buildDocs = series(doc, docIcons, docImages) 116 | exports.changelog = changelogUpdate 117 | exports.publish = series(cleanProject, releaseWebContent, releaseJSContent, releaseLocal, codeJSContent, releasePublicData, releaseIcons, doc, docIcons, docImages, changelogUpdate) 118 | -------------------------------------------------------------------------------- /images/BACnetFlowExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/BACnetFlowExample.png -------------------------------------------------------------------------------- /images/BACnetFlowExampleSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/BACnetFlowExampleSmall.png -------------------------------------------------------------------------------- /images/BACnetFlowExamples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/BACnetFlowExamples.png -------------------------------------------------------------------------------- /images/bacnet-icon-quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-icon-quad.png -------------------------------------------------------------------------------- /images/bacnet-icon-quad128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-icon-quad128.png -------------------------------------------------------------------------------- /images/bacnet-icon-quad64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-icon-quad64.png -------------------------------------------------------------------------------- /images/bacnet-icon-small.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-icon-small.xcf -------------------------------------------------------------------------------- /images/bacnet-icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-icon.xcf -------------------------------------------------------------------------------- /images/bacnet-nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/images/bacnet-nodes.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | -------------------------------------------------------------------------------- /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 test 17 | 18 | npm run build -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-bacnet", 3 | "version": "0.3.0", 4 | "private": false, 5 | "description": "The BACnet toolbox package for Node-RED from the P4NR B2B Community.", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/BiancoRoyal/node-red-contrib-bacnet.git" 9 | }, 10 | "dependencies": { 11 | "debug": "~4.3.4", 12 | "node-bacnet": "~0.2.4", 13 | "underscore": "~1.13.6" 14 | }, 15 | "keywords": [ 16 | "node-red", 17 | "plusfornodered", 18 | "bacnet", 19 | "fieldbus", 20 | "biancoroyal" 21 | ], 22 | "author": "P4NR Support (https://plus4nodered.com/)", 23 | "contributors": [ 24 | { 25 | "name": "P4NR B2B Community (https://p4nr.com/)" 26 | }, 27 | { 28 | "name": "Iniationware GmbH (https://iniationware.com/)" 29 | }, 30 | { 31 | "name": "Ingo Fischer (Gameforge)" 32 | }, 33 | { 34 | "name": "Fabio Huser (node-bacstack)" 35 | } 36 | ], 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/BiancoRoyal/node-red-contrib-bacnet/issues" 40 | }, 41 | "node-red": { 42 | "version": ">=2", 43 | "nodes": { 44 | "BACnet-Client": "bacnet/bacnet-client.js", 45 | "BACnet-Device": "bacnet/bacnet-device.js", 46 | "BACnet-Instance": "bacnet/bacnet-instance.js", 47 | "BACnet-Read": "bacnet/bacnet-read.js", 48 | "BACnet-Write": "bacnet/bacnet-write.js", 49 | "BACnet-Command": "bacnet/bacnet-command.js" 50 | } 51 | }, 52 | "engines": { 53 | "node": ">=14" 54 | }, 55 | "homepage": "https://p4nr.com/", 56 | "scripts": { 57 | "test": "standard --fix && mocha test --recursive --reporter dot --timeout 5000", 58 | "test-with-coverage": "istanbul cover _mocha --report lcovonly -- --recursive --timeout 5000 -R spec && cat ./coverage/lcov.info | codacy-coverage --token $CODACY_COVERAGE_TOKEN && rm -rf ./coverage", 59 | "coverage": "npm run build && npm test && istanbul cover _mocha -- --recursive --timeout 5000", 60 | "build": "standard --fix && gulp", 61 | "prepublishOnly": "npm run build && npm run rewrite-changelog && npm test", 62 | "ci-publish": "ci-publish", 63 | "release": "standard-version -a", 64 | "release:beta": "standard-version --prerelease beta", 65 | "release:alpha": "standard-version --prerelease alpha", 66 | "rewrite-changelog": "gulp changelog", 67 | "postinstall": "node ./supporter.js", 68 | "clean": "gulp clean", 69 | "dev-link": "npm i && npm run build && npm link", 70 | "dev-unlink": "npm unlink node-red-contrib-bacnet -g" 71 | }, 72 | "files": [ 73 | "docs", 74 | "examples", 75 | "bacnet", 76 | "supporter.js" 77 | ], 78 | "devDependencies": { 79 | "@babel/cli": "^7.7.0", 80 | "@babel/core": "^7.7.2", 81 | "@babel/preset-env": "^7.7.1", 82 | "chai": "^4.2.0", 83 | "codacy-coverage": "^3.4.0", 84 | "conventional-changelog-cli": "^2.0.27", 85 | "gulp": "^4.0.2", 86 | "gulp-babel": "^8.0.0", 87 | "gulp-clean": "^0.4.0", 88 | "gulp-conventional-changelog": "^2.0.35", 89 | "gulp-htmlmin": "^5.0.1", 90 | "gulp-jsdoc3": "^3.0.0", 91 | "gulp-replace": "^1.0.0", 92 | "gulp-sequence": "^1.0.0", 93 | "gulp-sourcemaps": "^2.6.5", 94 | "gulp-uglify": "^3.0.2", 95 | "istanbul": "0.4.5", 96 | "jasmine-node": "^3.0.0", 97 | "js-beautify": "^1.10.2", 98 | "mocha": "^7.2.0", 99 | "nock": "^12.0.3", 100 | "node-red": "^1.0.6", 101 | "node-red-node-test-helper": "^0.2.4", 102 | "pump": "^3.0.0", 103 | "should": "^13.2.3", 104 | "sinon": "^9.0.2", 105 | "standard": "^14.3.1", 106 | "standard-version": "^8.0.0", 107 | "supertest": "^4.0.2", 108 | "uglify-js": "^3.6.9", 109 | "uglify-js-harmony": "^2.7.7", 110 | "when": "^3.7.8" 111 | }, 112 | "directories": { 113 | "doc": "docs", 114 | "example": "examples", 115 | "test": "test" 116 | }, 117 | "standard": { 118 | "ignore": [ 119 | "code/", 120 | "node_modules/", 121 | "examples/", 122 | "bacnet/", 123 | "docs", 124 | "src/public/", 125 | "test" 126 | ] 127 | }, 128 | "main": "index.js" 129 | } 130 | -------------------------------------------------------------------------------- /src/bacnet-client.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 47 | 48 | 57 | -------------------------------------------------------------------------------- /src/bacnet-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | const bacnetCore = require('./core/bacnet-core') 12 | const BACnet = require('node-bacnet') 13 | 14 | function BACnetClient (config) { 15 | RED.nodes.createNode(this, config) 16 | this.name = config.name 17 | this.adpuTimeout = parseInt(config.adpuTimeout) || 6000 18 | this.port = parseInt(config.port) || 47808 19 | this.IPAddress = config.IPAddress || '0.0.0.0' 20 | this.broadcastAddress = config.broadcastAddress || '0.0.0.255' 21 | 22 | const node = this 23 | node.devices = [] 24 | 25 | if (node.IPAddress) { 26 | bacnetCore.internalDebugLog('client with IP settings') 27 | node.client = new BACnet({ adpuTimeout: node.adpuTimeout, port: node.port, interface: node.IPAddress, broadcastAddress: node.broadcastAddress }) 28 | } else { 29 | bacnetCore.internalDebugLog('client without IP settings') 30 | node.client = new BACnet({ adpuTimeout: node.adpuTimeout, port: node.port }) 31 | } 32 | 33 | if (node.client) { 34 | node.client.on('iAm', (device) => { 35 | node.devices.push(device) 36 | bacnetCore.internalDebugLog('iAm Event') 37 | bacnetCore.internalDebugLog('address: ', device.address) 38 | bacnetCore.internalDebugLog('deviceId: ', device.deviceId) 39 | bacnetCore.internalDebugLog('maxAdpu: ', device.maxAdpu) 40 | bacnetCore.internalDebugLog('segmentation: ', device.segmentation) 41 | bacnetCore.internalDebugLog('vendorId: ', device.vendorId) 42 | }) 43 | 44 | node.client.on('timeout', function () { 45 | bacnetCore.internalDebugLog('timeout') 46 | }) 47 | 48 | node.client.whoIs() 49 | 50 | node.client.on('error', function (err) { 51 | node.error(err, { payload: 'BACnet Client Error' }) 52 | node.client.close() 53 | node.client = null 54 | node.devices = [] 55 | node.client = new BACnet({ adpuTimeout: node.adpuTimeout, port: node.port, interface: node.IPAddress, broadcastAddress: node.broadcastAddress }) 56 | }) 57 | } 58 | 59 | node.on('input', function (msg) { 60 | msg.devices = node.devices 61 | node.send(msg) 62 | }) 63 | 64 | node.on('close', function (done) { 65 | if (node.client) { 66 | node.client.close() 67 | node.client = null 68 | } 69 | done() 70 | }) 71 | 72 | node.whoIsExplicit = function (lowLimit, highLimit, deviceIPAddress, cb) { 73 | node.devices = [] 74 | const options = { 75 | lowLimit, 76 | highLimit, 77 | deviceIPAddress 78 | } 79 | node.client.whoIs(options) 80 | setTimeout(cb, 3000) 81 | } 82 | 83 | node.whoIs = function (cb) { 84 | node.devices = [] 85 | node.client.whoIs() 86 | setTimeout(cb, 3000) 87 | } 88 | } 89 | 90 | RED.nodes.registerType('BACnet-Client', BACnetClient) 91 | } 92 | -------------------------------------------------------------------------------- /src/bacnet-command.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 179 | 180 | 255 | 256 | 303 | -------------------------------------------------------------------------------- /src/bacnet-command.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | const bacnetCore = require('./core/bacnet-core') 12 | const BACnet = require('node-bacnet') 13 | const _ = require('underscore') 14 | 15 | function BACnetCommand (config) { 16 | RED.nodes.createNode(this, config) 17 | 18 | this.name = config.name 19 | this.commandType = config.commandType 20 | this.timeDuration = parseInt(config.timeDuration) || 0 21 | this.enableDisable = config.enableDisable || BACnet.enum.EnableDisable.ENABLE 22 | this.deviceState = config.deviceState || BACnet.enum.ReinitializedState.COLDSTART 23 | this.isUtc = config.isUtc || true 24 | this.lowLimit = config.lowLimit || null 25 | this.highLimit = config.highLimit || null 26 | this.credentials = config.credentials 27 | 28 | this.device = RED.nodes.getNode(config.device) 29 | this.deviceIPAddress = this.device.deviceAddress || '127.0.0.1' 30 | 31 | this.connector = RED.nodes.getNode(config.server) 32 | 33 | const node = this 34 | 35 | node.status({ fill: 'green', shape: 'dot', text: 'active' }) 36 | 37 | node.on('input', function (msg) { 38 | if (!node.connector) { 39 | node.error(new Error('Client Not Ready To Read'), msg) 40 | } 41 | 42 | bacnetCore.internalDebugLog('Command') 43 | 44 | const commandType = msg.payload.commandType || node.commandType 45 | let options = msg.payload.options || null 46 | 47 | if (!options) { 48 | options = { 49 | maxSegments: BACnet.enum.MaxSegmentsAccepted.SEGMENTS_65, 50 | maxAdpu: BACnet.enum.MaxApduLengthAccepted.OCTETS_1476, 51 | invokeId: null, 52 | password: (node.credentials) ? node.credentials.password : null 53 | } 54 | } else { 55 | if (!msg.payload.options.password) { 56 | msg.payload.options.password = node.credentials.password 57 | } 58 | } 59 | 60 | switch (commandType) { 61 | case 'deviceCommunicationControl': 62 | node.connector.client.deviceCommunicationControl( 63 | msg.payload.deviceIPAddress || node.deviceIPAddress, 64 | msg.payload.timeDuration || node.timeDuration, 65 | msg.payload.enableDisable || node.enableDisable, 66 | options, 67 | function (err, value) { 68 | if (err) { 69 | const translatedError = bacnetCore.translateErrorMessage(err) 70 | bacnetCore.internalDebugLog(translatedError) 71 | node.error(translatedError, msg) 72 | } else { 73 | bacnetCore.internalDebugLog('value: ', value) 74 | msg.input = msg.payload 75 | msg.payload = value 76 | } 77 | }) 78 | break 79 | 80 | case 'reinitializeDevice': 81 | node.connector.client.reinitializeDevice( 82 | msg.payload.deviceIPAddress || node.deviceIPAddress, 83 | msg.payload.deviceState || node.deviceState, 84 | options, 85 | function (err, value) { 86 | if (err) { 87 | const translatedError = bacnetCore.translateErrorMessage(err) 88 | bacnetCore.internalDebugLog(translatedError) 89 | node.error(translatedError, msg) 90 | } else { 91 | bacnetCore.internalDebugLog('value: ', value) 92 | msg.input = msg.payload 93 | msg.payload = value 94 | } 95 | }) 96 | break 97 | 98 | case 'timeSync': 99 | if (msg.payload.isUtc || node.isUtc) { 100 | node.connector.client.timeSyncUTC( 101 | msg.payload.deviceIPAddress || node.deviceIPAddress, 102 | msg.payload.syncDateTime || new Date()) 103 | } else { 104 | node.connector.client.timeSync( 105 | msg.payload.deviceIPAddress || node.deviceIPAddress, 106 | msg.payload.syncDateTime || new Date()) 107 | } 108 | break 109 | 110 | case 'whoIsExplicit': 111 | node.connector.whoIsExplicit( 112 | msg.payload.lowLimit || node.lowLimit, 113 | msg.payload.highLimit || node.highLimit, 114 | msg.payload.deviceIPAddress || node.deviceIPAddress, 115 | function () { 116 | msg.input = msg.payload 117 | msg.payload = node.connector.devices 118 | node.send(msg) 119 | }) 120 | break 121 | 122 | case 'whoIs': 123 | node.connector.whoIs( 124 | function () { 125 | msg.input = msg.payload 126 | msg.payload = node.connector.devices 127 | node.send(msg) 128 | }) 129 | break 130 | 131 | default: 132 | bacnetCore.internalDebugLog('Unknown Command Type Selected ' + commandType) 133 | } 134 | 135 | msg.devices = node.connector.devices 136 | 137 | node.send(msg) 138 | }) 139 | } 140 | 141 | RED.nodes.registerType('BACnet-Command', BACnetCommand, { 142 | credentials: { 143 | password: { type: 'password' } 144 | } 145 | }) 146 | 147 | RED.httpAdmin.get('/bacnet/BacnetEnableDisable', RED.auth.needsPermission('bacnet.CMD.read'), function (req, res) { 148 | const typeList = BACnet.enum.EnableDisable 149 | const invertedTypeList = _.toArray(_.invert(typeList)) 150 | const resultTypeList = [] 151 | 152 | let typelistEntry 153 | for (typelistEntry of invertedTypeList) { 154 | resultTypeList.push({ typeValue: typeList[typelistEntry], label: typelistEntry }) 155 | } 156 | 157 | res.json(resultTypeList) 158 | }) 159 | 160 | RED.httpAdmin.get('/bacnet/BacnetReinitializedStates', RED.auth.needsPermission('bacnet.CMD.read'), function (req, res) { 161 | const typeList = BACnet.enum.ReinitializedState 162 | const invertedTypeList = _.toArray(_.invert(typeList)) 163 | const resultTypeList = [] 164 | 165 | let typelistEntry 166 | for (typelistEntry of invertedTypeList) { 167 | resultTypeList.push({ typeValue: typeList[typelistEntry], label: typelistEntry }) 168 | } 169 | 170 | res.json(resultTypeList) 171 | }) 172 | } 173 | -------------------------------------------------------------------------------- /src/bacnet-device.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /src/bacnet-device.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | function BACnetDevice (config) { 12 | RED.nodes.createNode(this, config) 13 | this.name = config.name 14 | this.deviceAddress = config.deviceAddress || '127.0.0.1' 15 | } 16 | 17 | RED.nodes.registerType('BACnet-Device', BACnetDevice) 18 | } 19 | -------------------------------------------------------------------------------- /src/bacnet-instance.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /src/bacnet-instance.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | function BACnetInstance (config) { 12 | RED.nodes.createNode(this, config) 13 | this.name = config.name 14 | this.instanceAddress = parseInt(config.instanceAddress) || 0 15 | } 16 | 17 | RED.nodes.registerType('BACnet-Instance', BACnetInstance) 18 | } 19 | -------------------------------------------------------------------------------- /src/bacnet-read.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 112 | 113 | 156 | 157 | 233 | -------------------------------------------------------------------------------- /src/bacnet-read.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | const bacnetCore = require('./core/bacnet-core') 12 | 13 | function BACnetRead (config) { 14 | RED.nodes.createNode(this, config) 15 | 16 | this.name = config.name 17 | this.objectType = parseInt(config.objectType) 18 | this.propertyId = parseInt(config.propertyId) 19 | this.multipleRead = config.multipleRead 20 | 21 | this.instance = RED.nodes.getNode(config.instance) 22 | this.objectInstance = parseInt(this.instance.instanceAddress) || 0 23 | 24 | this.device = RED.nodes.getNode(config.device) 25 | this.deviceIPAddress = this.device.deviceAddress || '127.0.0.1' 26 | 27 | this.connector = RED.nodes.getNode(config.server) 28 | 29 | const node = this 30 | 31 | node.status({ fill: 'green', shape: 'dot', text: 'active' }) 32 | 33 | node.on('input', function (msg) { 34 | if (!node.connector) { 35 | node.error(new Error('Client Not Ready To Read'), msg) 36 | return 37 | } 38 | 39 | const options = msg.payload.options || {} 40 | 41 | if (node.multipleRead) { 42 | bacnetCore.internalDebugLog('Multiple Read') 43 | 44 | const defaultRequestArray = [{ 45 | objectId: { 46 | type: node.objectType, 47 | instance: parseInt(node.objectInstance) 48 | }, 49 | properties: [{ id: parseInt(node.propertyId) }] 50 | }] 51 | 52 | try { 53 | bacnetCore.internalDebugLog('readProperty node.deviceIPAddress: ' + node.deviceIPAddress) 54 | bacnetCore.internalDebugLog('readProperty msg.payload.deviceIPAddress: ' + msg.payload.deviceIPAddress) 55 | bacnetCore.internalDebugLog('readPropertyMultiple default requestArray: ' + JSON.stringify(defaultRequestArray)) 56 | bacnetCore.internalDebugLog('readPropertyMultiple msg.payload.requestArray: ' + JSON.stringify(msg.payload.requestArray)) 57 | bacnetCore.internalDebugLog('readPropertyMultiple node.propertyId: ' + node.propertyId) 58 | bacnetCore.internalDebugLog('readPropertyMultiple msg.payload.propertyId: ' + msg.payload.propertyId) 59 | } catch (e) { 60 | bacnetCore.internalDebugLog('readPropertyMultiple error: ' + e) 61 | } 62 | 63 | node.connector.client.readPropertyMultiple( 64 | msg.payload.deviceIPAddress || node.deviceIPAddress, 65 | msg.payload.requestArray || defaultRequestArray, 66 | options, 67 | function (err, result) { 68 | if (err) { 69 | const translatedError = bacnetCore.translateErrorMessage(err) 70 | bacnetCore.internalDebugLog(translatedError) 71 | node.error(translatedError, msg) 72 | } else { 73 | msg.input = msg.payload 74 | msg.payload = result 75 | node.send(msg) 76 | } 77 | }) 78 | } else { 79 | bacnetCore.internalDebugLog('Read') 80 | 81 | const objectId = { 82 | type: parseInt(node.objectType), 83 | instance: parseInt(node.objectInstance) 84 | } 85 | 86 | try { 87 | bacnetCore.internalDebugLog('readProperty node.deviceIPAddress: ' + node.deviceIPAddress) 88 | bacnetCore.internalDebugLog('readProperty msg.payload.deviceIPAddress: ' + msg.payload.deviceIPAddress) 89 | bacnetCore.internalDebugLog('readProperty default objectId: ' + JSON.stringify(objectId)) 90 | bacnetCore.internalDebugLog('readProperty msg.payload.objectId: ' + JSON.stringify(msg.payload.objectId)) 91 | bacnetCore.internalDebugLog('readProperty node.propertyId: ' + node.propertyId) 92 | bacnetCore.internalDebugLog('readProperty msg.payload.propertyId: ' + msg.payload.propertyId) 93 | } catch (e) { 94 | bacnetCore.internalDebugLog('readProperty error: ' + e) 95 | } 96 | 97 | node.connector.client.readProperty( 98 | msg.payload.deviceIPAddress || node.deviceIPAddress, 99 | msg.payload.objectId || objectId, 100 | parseInt(msg.payload.propertyId) || parseInt(node.propertyId), 101 | options, 102 | function (err, result) { 103 | if (err) { 104 | const translatedError = bacnetCore.translateErrorMessage(err) 105 | bacnetCore.internalDebugLog(translatedError) 106 | node.error(translatedError, msg) 107 | } else { 108 | msg.input = msg.payload 109 | msg.payload = result 110 | node.send(msg) 111 | } 112 | }) 113 | } 114 | }) 115 | } 116 | 117 | RED.nodes.registerType('BACnet-Read', BACnetRead) 118 | } 119 | -------------------------------------------------------------------------------- /src/bacnet-write.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 144 | 145 | 205 | 206 | 308 | -------------------------------------------------------------------------------- /src/bacnet-write.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | module.exports = function (RED) { 11 | const bacnetCore = require('./core/bacnet-core') 12 | const BACnet = require('node-bacnet') 13 | const _ = require('underscore') 14 | 15 | function BACnetWrite (config) { 16 | RED.nodes.createNode(this, config) 17 | 18 | this.name = config.name 19 | this.objectType = parseInt(config.objectType) 20 | this.valueTag = parseInt(config.valueTag) 21 | this.valueValue = config.valueValue 22 | this.propertyId = parseInt(config.propertyId) 23 | this.priority = parseInt(config.priority) 24 | 25 | this.multipleWrite = config.multipleWrite 26 | 27 | this.instance = RED.nodes.getNode(config.instance) 28 | this.objectInstance = parseInt(this.instance.instanceAddress) || 0 29 | 30 | this.device = RED.nodes.getNode(config.device) 31 | this.deviceIPAddress = this.device.deviceAddress || '127.0.0.1' // IPv6 it is :: - but configure Node-RED too 32 | 33 | this.connector = RED.nodes.getNode(config.server) 34 | 35 | const node = this 36 | 37 | node.status({ fill: 'green', shape: 'dot', text: 'active' }) 38 | 39 | node.on('input', function (msg) { 40 | if (!node.connector) { 41 | node.error(new Error('Client Not Ready To Write'), msg) 42 | return 43 | } 44 | 45 | node.priority = (node.priority < 1 ? 16 : node.priority) 46 | node.priority = (node.priority > 16 ? 16 : node.priority) 47 | 48 | const options = msg.payload.options || { priority: node.priority } 49 | 50 | if (node.multipleWrite) { 51 | bacnetCore.internalDebugLog('Multiple Write') 52 | 53 | if (!msg.payload.values || !msg.payload.values[0].values) { 54 | node.error(new Error('msg.payload.values missing or invalid array for multiple write'), msg) 55 | return 56 | } 57 | 58 | msg.payload.values.forEach(function (item) { 59 | if (!item.objectId) { 60 | item.objectId = { 61 | type: node.objectType, 62 | instance: parseInt(node.objectInstance) 63 | } 64 | } 65 | }) 66 | 67 | try { 68 | bacnetCore.internalDebugLog('writeProperty node.deviceIPAddress: ' + node.deviceIPAddress) 69 | bacnetCore.internalDebugLog('writeProperty msg.payload.deviceIPAddress: ' + msg.payload.deviceIPAddress) 70 | bacnetCore.internalDebugLog('writePropertyMultiple msg.payload.values: ' + JSON.stringify(msg.payload.values)) 71 | } catch (e) { 72 | bacnetCore.internalDebugLog('writePropertyMultiple error: ' + e) 73 | } 74 | 75 | node.connector.client.writePropertyMultiple( 76 | msg.payload.deviceIPAddress || node.deviceIPAddress, 77 | msg.payload.values, 78 | options, 79 | function (err, value) { 80 | if (err) { 81 | const translatedError = bacnetCore.translateErrorMessage(err) 82 | bacnetCore.internalDebugLog(translatedError) 83 | node.error(translatedError, msg) 84 | } else { 85 | msg.input = msg.payload 86 | msg.payload = value 87 | node.send(msg) 88 | } 89 | }) 90 | } else { 91 | bacnetCore.internalDebugLog('Write') 92 | 93 | if (msg.payload.values && !msg.payload.values[0]) { 94 | node.error(new Error('invalid msg.payload.values array for write'), msg) 95 | return 96 | } 97 | 98 | const objectId = { 99 | type: parseInt(node.objectType), 100 | instance: parseInt(node.objectInstance) 101 | } 102 | 103 | const defaultValues = [{ 104 | type: parseInt(node.valueTag), 105 | value: node.valueValue 106 | }] 107 | 108 | try { 109 | bacnetCore.internalDebugLog('writeProperty node.deviceIPAddress: ' + node.deviceIPAddress) 110 | bacnetCore.internalDebugLog('writeProperty msg.payload.deviceIPAddress: ' + msg.payload.deviceIPAddress) 111 | bacnetCore.internalDebugLog('writeProperty default objectId: ' + JSON.stringify(objectId)) 112 | bacnetCore.internalDebugLog('writeProperty default values: ' + JSON.stringify(defaultValues)) 113 | bacnetCore.internalDebugLog('writeProperty msg.payload.values: ' + JSON.stringify(msg.payload.values)) 114 | bacnetCore.internalDebugLog('writeProperty node.propertyId: ' + node.propertyId) 115 | bacnetCore.internalDebugLog('writeProperty msg.payload.propertyId: ' + msg.payload.propertyId) 116 | } catch (e) { 117 | bacnetCore.internalDebugLog('writeProperty error: ' + e) 118 | } 119 | 120 | node.connector.client.writeProperty( 121 | msg.payload.deviceIPAddress || node.deviceIPAddress, 122 | msg.payload.objectId || objectId, 123 | parseInt(msg.payload.propertyId) || parseInt(node.propertyId) || 85, 124 | msg.payload.values || defaultValues, 125 | options, 126 | function (err, value) { 127 | if (err) { 128 | const translatedError = bacnetCore.translateErrorMessage(err) 129 | bacnetCore.internalDebugLog(translatedError) 130 | node.error(translatedError, msg) 131 | } else { 132 | msg.input = msg.payload 133 | msg.payload = value || 'write done' 134 | node.send(msg) 135 | } 136 | }) 137 | } 138 | }) 139 | } 140 | 141 | RED.nodes.registerType('BACnet-Write', BACnetWrite) 142 | 143 | RED.httpAdmin.get('/bacnet/ApplicationTags', RED.auth.needsPermission('bacnet.CMD.write'), function (req, res) { 144 | const typeList = BACnet.enum.ApplicationTag 145 | const invertedTypeList = _.toArray(_.invert(typeList)) 146 | const resultTypeList = [] 147 | 148 | let typelistEntry 149 | for (typelistEntry of invertedTypeList) { 150 | resultTypeList.push({ typeValue: parseInt(typeList[typelistEntry]) || 0, label: typelistEntry }) 151 | } 152 | 153 | res.json(resultTypeList) 154 | }) 155 | 156 | RED.httpAdmin.get('/bacnet/PropertyIds', RED.auth.needsPermission('bacnet.CMD.write'), function (req, res) { 157 | const typeList = BACnet.enum.PropertyIdentifier 158 | const invertedTypeList = _.toArray(_.invert(typeList)) 159 | const resultTypeList = [] 160 | 161 | let typelistEntry 162 | for (typelistEntry of invertedTypeList) { 163 | resultTypeList.push({ typeValue: parseInt(typeList[typelistEntry]) || 0, label: typelistEntry }) 164 | } 165 | 166 | res.json(resultTypeList) 167 | }) 168 | 169 | RED.httpAdmin.get('/bacnet/ObjectTypes', RED.auth.needsPermission('bacnet.CMD.write'), function (req, res) { 170 | const typeList = BACnet.enum.ObjectType 171 | const invertedTypeList = _.toArray(_.invert(typeList)) 172 | const resultTypeList = [] 173 | 174 | let typelistEntry 175 | for (typelistEntry of invertedTypeList) { 176 | resultTypeList.push({ typeValue: parseInt(typeList[typelistEntry]) || 0, label: typelistEntry }) 177 | } 178 | 179 | res.json(resultTypeList) 180 | }) 181 | } 182 | -------------------------------------------------------------------------------- /src/core/bacnet-core.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 5 | All rights reserved. 6 | node-red-contrib-bacnet 7 | */ 8 | 'use strict' 9 | 10 | var de = de || { biancoroyal: { bacnet: { core: {} } } } // eslint-disable-line no-use-before-define 11 | de.biancoroyal.bacnet.core.internalDebugLog = de.biancoroyal.bacnet.core.internalDebugLog || require('debug')('bacnet:nodered:core') // eslint-disable-line no-use-before-define 12 | de.biancoroyal.bacnet.core.detailDebugLog = de.biancoroyal.bacnet.core.detailDebugLog || require('debug')('bacnet:nodered:core:details') // eslint-disable-line no-use-before-define 13 | de.biancoroyal.bacnet.core.specialDebugLog = de.biancoroyal.bacnet.core.specialDebugLog || require('debug')('bacnet:nodered:core:special') // eslint-disable-line no-use-before-define 14 | de.biancoroyal.bacnet.core.errorCodeList = de.biancoroyal.bacnet.core.errorCodeList || [] // eslint-disable-line no-use-before-define 15 | de.biancoroyal.bacnet.core.errorClassList = de.biancoroyal.bacnet.core.errorClassList || [] // eslint-disable-line no-use-before-define 16 | 17 | de.biancoroyal.bacnet.core.initCodeLists = function () { 18 | const BACnet = require('node-bacnet') 19 | const _ = require('underscore') 20 | 21 | const errorCodeList = BACnet.enum.ErrorCode 22 | const invertedErrorCodeList = _.toArray(_.invert(errorCodeList)) 23 | de.biancoroyal.bacnet.core.errorCodeList = [] 24 | 25 | let listCodeEntry 26 | for (listCodeEntry of invertedErrorCodeList) { 27 | de.biancoroyal.bacnet.core.errorCodeList.push({ typeValue: errorCodeList[listCodeEntry], label: listCodeEntry }) 28 | } 29 | _.sortBy(de.biancoroyal.bacnet.core.errorCodeList, 'typeValue') 30 | 31 | const errorClassList = BACnet.enum.ErrorClass 32 | const invertedErrorClassList = _.toArray(_.invert(errorClassList)) 33 | de.biancoroyal.bacnet.core.errorClassList = [] 34 | 35 | let listClassEntry 36 | for (listClassEntry of invertedErrorClassList) { 37 | de.biancoroyal.bacnet.core.errorClassList.push({ typeValue: errorClassList[listClassEntry], label: listClassEntry }) 38 | } 39 | _.sortBy(de.biancoroyal.bacnet.core.errorClassList, 'typeValue') 40 | 41 | de.biancoroyal.bacnet.core.internalDebugLog('List init done with ' + 42 | de.biancoroyal.bacnet.core.errorClassList.length + ' class errors and ' + 43 | de.biancoroyal.bacnet.core.errorCodeList.length + ' code errors') 44 | } 45 | 46 | de.biancoroyal.bacnet.core.translateErrorMessage = function (err) { 47 | const message = err.message 48 | const messageParts = message.split('-') 49 | if (messageParts.length === 3) { 50 | const errorClassMessage = messageParts[1].split(':') 51 | const errorCodeMessage = messageParts[2].split(':') 52 | 53 | de.biancoroyal.bacnet.core.internalDebugLog(errorClassMessage) 54 | de.biancoroyal.bacnet.core.internalDebugLog(errorCodeMessage) 55 | 56 | errorClassMessage[1] = de.biancoroyal.bacnet.core.errorClassToString(errorClassMessage[1]) 57 | errorCodeMessage[1] = de.biancoroyal.bacnet.core.errorCodeToString(errorCodeMessage[1]) 58 | 59 | err.message = message + ' ' + errorClassMessage.join(':') + ' ' + errorCodeMessage.join(':') 60 | } 61 | return err 62 | } 63 | 64 | de.biancoroyal.bacnet.core.errorCodeToString = function (errorCodeId) { 65 | if (de.biancoroyal.bacnet.core.errorCodeList.length < 1) { 66 | de.biancoroyal.bacnet.core.initCodeLists() 67 | } 68 | let listEntry, entry 69 | for (listEntry of de.biancoroyal.bacnet.core.errorCodeList) { 70 | if (parseInt(listEntry.typeValue) === parseInt(errorCodeId)) { 71 | de.biancoroyal.bacnet.core.detailDebugLog(listEntry.typeValue + ' --> ' + listEntry.label) 72 | entry = listEntry 73 | } 74 | } 75 | return (entry) ? entry.label : errorCodeId 76 | } 77 | 78 | de.biancoroyal.bacnet.core.errorClassToString = function (errorClassId) { 79 | if (de.biancoroyal.bacnet.core.errorClassList.length < 1) { 80 | de.biancoroyal.bacnet.core.initCodeLists() 81 | } 82 | let listEntry, entry 83 | for (listEntry of de.biancoroyal.bacnet.core.errorClassList) { 84 | if (parseInt(listEntry.typeValue) === parseInt(errorClassId)) { 85 | de.biancoroyal.bacnet.core.detailDebugLog(listEntry.typeValue + ' --> ' + listEntry.label) 86 | entry = listEntry 87 | } 88 | } 89 | return (entry) ? entry.label : errorClassId 90 | } 91 | 92 | module.exports = de.biancoroyal.bacnet.core 93 | -------------------------------------------------------------------------------- /src/icons/bacnet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BiancoRoyal/node-red-contrib-bacnet/dfd86509cba91f1d4054af4e53a7ff091944207e/src/icons/bacnet-icon.png -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "port": "Port", 5 | "adpuTimeout": "adpu Timeout", 6 | "showErrors": "Show Errors", 7 | "interface": "Interface", 8 | "broadcastAddress": "Broadcast" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-command.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "commandType": "Command", 5 | "device": "Device", 6 | "timeDuration": "Time Duration", 7 | "enableDisable": "Enable/Disable", 8 | "password": "Password", 9 | "isUtc": "UTC Date", 10 | "lowLimit": "Low Limit", 11 | "highLimit": "High Limit", 12 | "deviceState": "State", 13 | "server": "Client", 14 | "showActivities": "Show Activities", 15 | "showErrors": "Show Errors" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-device.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "showErrors": "Show Errors", 5 | "deviceAddress": "Address" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "showErrors": "Show Errors", 5 | "instanceAddress": "Instance" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-read.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "device": "Device", 5 | "objectType": "Type", 6 | "instance": "Instance", 7 | "propertyId": "Property Id", 8 | "arrayIndex": "Array Index", 9 | "multipleRead": "Multiple Read", 10 | "server": "Client", 11 | "showActivities": "Show Activities", 12 | "showErrors": "Show Errors" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/locales/en-US/bacnet-write.json: -------------------------------------------------------------------------------- 1 | { 2 | "bacnet-contrib": { 3 | "label": { 4 | "device": "Device", 5 | "objectType": "Type", 6 | "instance": "Instance", 7 | "valueTag": "App-Tag", 8 | "valueValue": "Value", 9 | "propertyId": "Property", 10 | "propertyIndex": "Index", 11 | "invokeId": "Invoke Id", 12 | "priority": "Priority", 13 | "multipleWrite": "Multiple Write", 14 | "server": "Client", 15 | "showActivities": "Show Activities", 16 | "showErrors": "Show Errors" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/locales/en-US/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "label": { 4 | "payload": "Payload", 5 | "topic": "Topic", 6 | "name": "Name", 7 | "username": "Username", 8 | "password": "Password" 9 | }, 10 | "status": { 11 | "connected": "connected", 12 | "not-connected": "not connected", 13 | "disconnected": "disconnected", 14 | "connecting": "connecting", 15 | "error": "error", 16 | "ok": "OK" 17 | }, 18 | "notification": { 19 | "error": "Error: __message__", 20 | "errors": { 21 | "not-deployed": "node not deployed", 22 | "no-response": "no response from server", 23 | "unexpected": "unexpected error (__status__) __message__" 24 | } 25 | }, 26 | "errors": { 27 | "nooverride": "Warning: msg properties can no longer override set node properties. See bit.ly/nr-override-msg-props" 28 | } 29 | }, 30 | "bacnet-contrib": { 31 | "common": { 32 | "label": { 33 | "type": "Type", 34 | "server": "Server", 35 | "timeout": "Timeout", 36 | "name": "Name", 37 | "port": "Port", 38 | "connector": "Connector", 39 | "topic": "Topic", 40 | "user": "User", 41 | "password": "Password", 42 | "showActivities": "Show Activities", 43 | "showErrors": "Show Errors" 44 | }, 45 | "response": { 46 | }, 47 | "getter": { 48 | }, 49 | "read": { 50 | }, 51 | "write": { 52 | }, 53 | "server": { 54 | }, 55 | "client": { 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /supporter.js: -------------------------------------------------------------------------------- 1 | const message = 2 | '\u001b[96m\u001b[1mThank you for using our contribution!\u001b[96m\u001b[1m\n\u001b[0m\u001b[96mIf you rely on this package, please consider supporting our open source work:\u001b[22m\u001b[39m\n> \u001b[94mhttps://bianco-royal.space/supporter/\u001b[0m\n\n' 3 | console.log(message) 4 | -------------------------------------------------------------------------------- /test/bacnet-command-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Original Work Copyright 2014 IBM Corp. 3 | * node-red 4 | * 5 | * Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 6 | * All rights reserved. 7 | * node-red-contrib-bacnet - The MIT License 8 | * 9 | **/ 10 | 11 | 'use strict' 12 | 13 | var commandNode = require('../src/bacnet-command.js') 14 | var deviceNode = require('../src/bacnet-device.js') 15 | var clientNode = require('../src/bacnet-client.js') 16 | 17 | var helper = require('node-red-node-test-helper') 18 | helper.init(require.resolve('node-red')) 19 | 20 | // https://www.dailycred.com/article/bcrypt-calculator 21 | var testCredentials = { 22 | user: 'peter', 23 | password: '$2a$04$Dj8UfDYcMLjttad0Qi67DeKtqJM6SZ8XR.Oy70.GUvle4MlrVWaYC' 24 | } 25 | 26 | describe('Command node Testing', function () { 27 | beforeEach(function (done) { 28 | helper.startServer(function () { 29 | done() 30 | }) 31 | }) 32 | 33 | afterEach(function (done) { 34 | helper.unload().then(function () { 35 | helper.stopServer(function () { 36 | done() 37 | }) 38 | }).catch(function () { 39 | helper.stopServer(function () { 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | describe('Node', function () { 46 | it('simple read node should be loaded', function (done) { 47 | helper.load([deviceNode, clientNode, commandNode], [ 48 | { 49 | id: '8f8d8daf.248e', 50 | type: 'BACnet-Command', 51 | z: 'ad26e8b.6b24498', 52 | name: 'bacnetCommand', 53 | commandType: 'deviceCommunicationControl', 54 | timeDuration: 0, 55 | enableDisable: 0, 56 | deviceState: 0, 57 | isUtc: true, 58 | lowLimit: 0, 59 | highLimit: 0, 60 | device: 'b289851b.dec6f8', 61 | server: '1528f96c.56d047', 62 | x: 300, 63 | y: 320, 64 | wires: [ 65 | [] 66 | ] 67 | }, 68 | { 69 | id: 'b289851b.dec6f8', 70 | type: 'BACnet-Device', 71 | z: '', 72 | name: 'Windows VM', 73 | deviceAddress: '192.168.1.94' 74 | }, 75 | { 76 | id: '1528f96c.56d047', 77 | type: 'BACnet-Client', 78 | z: '', 79 | name: '', 80 | adpuTimeout: '', 81 | port: '', 82 | interface: '', 83 | broadcastAddress: '' 84 | } 85 | ], testCredentials, function () { 86 | var bacnetCommand = helper.getNode('8f8d8daf.248e') 87 | bacnetCommand.should.have.property('name', 'bacnetCommand') 88 | 89 | done() 90 | }, function () { 91 | helper.log('function callback') 92 | }) 93 | }) 94 | }) 95 | 96 | describe('post', function () { 97 | it('should fail for invalid node', function (done) { 98 | helper.request().post('/BACnet-command/invalid').expect(404).end(done) 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/bacnet-read-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Original Work Copyright 2014 IBM Corp. 3 | * node-red 4 | * 5 | * Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 6 | * All rights reserved. 7 | * node-red-contrib-bacnet - The MIT License 8 | * 9 | **/ 10 | 11 | 'use strict' 12 | 13 | var readNode = require('../src/bacnet-read.js') 14 | var deviceNode = require('../src/bacnet-device.js') 15 | var clientNode = require('../src/bacnet-client.js') 16 | var instanceNode = require('../src/bacnet-instance.js') 17 | 18 | var helper = require('node-red-node-test-helper') 19 | helper.init(require.resolve('node-red')) 20 | 21 | describe('Read node Testing', function () { 22 | beforeEach(function (done) { 23 | helper.startServer(function () { 24 | done() 25 | }) 26 | }) 27 | 28 | afterEach(function (done) { 29 | helper.unload().then(function () { 30 | helper.stopServer(function () { 31 | done() 32 | }) 33 | }).catch(function () { 34 | helper.stopServer(function () { 35 | done() 36 | }) 37 | }) 38 | }) 39 | 40 | describe('Node', function () { 41 | it('simple read node should be loaded', function (done) { 42 | helper.load([deviceNode, clientNode, instanceNode, readNode], [ 43 | { 44 | id: 'fa0424dc.f9bd', 45 | type: 'BACnet-Read', 46 | z: 'ad26e8b.6b24498', 47 | name: 'bacnetRead', 48 | objectType: '8', 49 | instance: 'cf0dca49.2a9ac', 50 | propertyId: '28', 51 | device: 'b289851b.dec6f8', 52 | server: '1528f96c.56d047', 53 | multipleRead: false, 54 | wires: [ 55 | [] 56 | ] 57 | }, 58 | { 59 | id: 'cf0dca49.2a9ac', 60 | type: 'BACnet-Instance', 61 | z: '', 62 | name: 'Room Simulator YABE', 63 | instanceAddress: '3342490' 64 | }, 65 | { 66 | id: 'b289851b.dec6f8', 67 | type: 'BACnet-Device', 68 | z: '', 69 | name: 'Windows VM', 70 | deviceAddress: '192.168.1.94' 71 | }, 72 | { 73 | id: '1528f96c.56d047', 74 | type: 'BACnet-Client', 75 | z: '', 76 | name: '', 77 | adpuTimeout: '', 78 | port: '', 79 | interface: '', 80 | broadcastAddress: '' 81 | } 82 | ], function () { 83 | var bacnetRead = helper.getNode('fa0424dc.f9bd') 84 | bacnetRead.should.have.property('name', 'bacnetRead') 85 | 86 | done() 87 | }, function () { 88 | helper.log('function callback') 89 | }) 90 | }) 91 | }) 92 | 93 | describe('post', function () { 94 | it('should fail for invalid node', function (done) { 95 | helper.request().post('/BACnet-read/invalid').expect(404).end(done) 96 | }) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/bacnet-write-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Original Work Copyright 2014 IBM Corp. 3 | * node-red 4 | * 5 | * Copyright (c) 2017,2018,2019,2020,2021,2022,2023,2024 Klaus Landsdorf (http://plus4nodered.com/) 6 | * All rights reserved. 7 | * node-red-contrib-bacnet - The MIT License 8 | * 9 | **/ 10 | 11 | 'use strict' 12 | 13 | var writeNode = require('../src/bacnet-write.js') 14 | var deviceNode = require('../src/bacnet-device.js') 15 | var clientNode = require('../src/bacnet-client.js') 16 | var instanceNode = require('../src/bacnet-instance.js') 17 | 18 | var helper = require('node-red-node-test-helper') 19 | helper.init(require.resolve('node-red')) 20 | 21 | describe('Write node Testing', function () { 22 | beforeEach(function (done) { 23 | helper.startServer(function () { 24 | done() 25 | }) 26 | }) 27 | 28 | afterEach(function (done) { 29 | helper.unload().then(function () { 30 | helper.stopServer(function () { 31 | done() 32 | }) 33 | }).catch(function () { 34 | helper.stopServer(function () { 35 | done() 36 | }) 37 | }) 38 | }) 39 | 40 | describe('Node', function () { 41 | it('simple write node should be loaded', function (done) { 42 | helper.load([deviceNode, clientNode, instanceNode, writeNode], [ 43 | { 44 | id: 'bdc5fbd.9678608', 45 | type: 'BACnet-Write', 46 | z: 'ad26e8b.6b24498', 47 | name: 'bacnetWrite', 48 | objectType: 8, 49 | instance: 'cf0dca49.2a9ac', 50 | valueTag: 9, 51 | valueValue: '', 52 | propertyId: 8, 53 | priority: 14, 54 | device: 'b289851b.dec6f8', 55 | server: '1528f96c.56d047', 56 | multipleWrite: false, 57 | wires: [ 58 | [] 59 | ] 60 | }, 61 | { 62 | id: 'cf0dca49.2a9ac', 63 | type: 'BACnet-Instance', 64 | z: '', 65 | name: 'Room Simulator YABE', 66 | instanceAddress: '3342490' 67 | }, 68 | { 69 | id: 'b289851b.dec6f8', 70 | type: 'BACnet-Device', 71 | z: '', 72 | name: 'Windows VM', 73 | deviceAddress: '192.168.1.94' 74 | }, 75 | { 76 | id: '1528f96c.56d047', 77 | type: 'BACnet-Client', 78 | z: '', 79 | name: '', 80 | adpuTimeout: '', 81 | port: '', 82 | interface: '', 83 | broadcastAddress: '' 84 | } 85 | ], function () { 86 | var bacnetWrite = helper.getNode('bdc5fbd.9678608') 87 | bacnetWrite.should.have.property('name', 'bacnetWrite') 88 | 89 | done() 90 | }, function () { 91 | helper.log('function callback') 92 | }) 93 | }) 94 | }) 95 | 96 | describe('post', function () { 97 | it('should fail for invalid node', function (done) { 98 | helper.request().post('/BACnet-write/invalid').expect(404).end(done) 99 | }) 100 | }) 101 | }) 102 | --------------------------------------------------------------------------------