Initial commit
This commit is contained in:
commit
e3af2bd74a
28
.codeclimate.yml
Normal file
28
.codeclimate.yml
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
engines:
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
- javascript
|
||||
exclude_fingerprints:
|
||||
- 43e0e1e6524762f837c44bbffd1e763c
|
||||
- 96c5a1fe99411cfd260780387b4f2345
|
||||
eslint:
|
||||
enabled: true
|
||||
checks:
|
||||
complexity:
|
||||
enabled: false
|
||||
exclude_fingerprints:
|
||||
- c4c98b5bf6e9d6534149e4038e291b1a
|
||||
- 055faf39892c98b5e4b310702bd90ade
|
||||
- 04f09dc503b35020ec99b1779c671a68
|
||||
fixme:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- "**.js"
|
||||
exclude_paths:
|
||||
- test/
|
||||
- lib/utils.js
|
||||
- Gruntfile.js
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
**/*{.,-}min.js
|
213
.eslintrc
Normal file
213
.eslintrc
Normal file
@ -0,0 +1,213 @@
|
||||
ecmaFeatures:
|
||||
modules: true
|
||||
jsx: true
|
||||
|
||||
env:
|
||||
amd: true
|
||||
browser: true
|
||||
es6: true
|
||||
jquery: true
|
||||
node: true
|
||||
|
||||
# http://eslint.org/docs/rules/
|
||||
rules:
|
||||
# Possible Errors
|
||||
comma-dangle: [2, never]
|
||||
no-cond-assign: 2
|
||||
no-console: 0
|
||||
no-constant-condition: 2
|
||||
no-control-regex: 2
|
||||
no-debugger: 2
|
||||
no-dupe-args: 2
|
||||
no-dupe-keys: 2
|
||||
no-duplicate-case: 2
|
||||
no-empty: 2
|
||||
no-empty-character-class: 2
|
||||
no-ex-assign: 2
|
||||
no-extra-boolean-cast: 2
|
||||
no-extra-parens: 0
|
||||
no-extra-semi: 2
|
||||
no-func-assign: 2
|
||||
no-inner-declarations: [2, functions]
|
||||
no-invalid-regexp: 2
|
||||
no-irregular-whitespace: 2
|
||||
no-negated-in-lhs: 2
|
||||
no-obj-calls: 2
|
||||
no-regex-spaces: 2
|
||||
no-sparse-arrays: 2
|
||||
no-unexpected-multiline: 2
|
||||
no-unreachable: 2
|
||||
use-isnan: 2
|
||||
valid-jsdoc: 0
|
||||
valid-typeof: 2
|
||||
|
||||
# Best Practices
|
||||
accessor-pairs: 2
|
||||
block-scoped-var: 0
|
||||
complexity: [2, 6]
|
||||
consistent-return: 0
|
||||
curly: 0
|
||||
default-case: 0
|
||||
dot-location: 0
|
||||
dot-notation: 0
|
||||
eqeqeq: 2
|
||||
guard-for-in: 2
|
||||
no-alert: 2
|
||||
no-caller: 2
|
||||
no-case-declarations: 2
|
||||
no-div-regex: 2
|
||||
no-else-return: 0
|
||||
no-empty-label: 2
|
||||
no-empty-pattern: 2
|
||||
no-eq-null: 2
|
||||
no-eval: 2
|
||||
no-extend-native: 2
|
||||
no-extra-bind: 2
|
||||
no-fallthrough: 2
|
||||
no-floating-decimal: 0
|
||||
no-implicit-coercion: 0
|
||||
no-implied-eval: 2
|
||||
no-invalid-this: 0
|
||||
no-iterator: 2
|
||||
no-labels: 0
|
||||
no-lone-blocks: 2
|
||||
no-loop-func: 2
|
||||
no-magic-number: 0
|
||||
no-multi-spaces: 0
|
||||
no-multi-str: 0
|
||||
no-native-reassign: 2
|
||||
no-new-func: 2
|
||||
no-new-wrappers: 2
|
||||
no-new: 2
|
||||
no-octal-escape: 2
|
||||
no-octal: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 2
|
||||
no-return-assign: 2
|
||||
no-script-url: 2
|
||||
no-self-compare: 2
|
||||
no-sequences: 0
|
||||
no-throw-literal: 0
|
||||
no-unused-expressions: 2
|
||||
no-useless-call: 2
|
||||
no-useless-concat: 2
|
||||
no-void: 2
|
||||
no-warning-comments: 0
|
||||
no-with: 2
|
||||
radix: 2
|
||||
vars-on-top: 0
|
||||
wrap-iife: 2
|
||||
yoda: 0
|
||||
|
||||
# Strict
|
||||
strict: 0
|
||||
|
||||
# Variables
|
||||
init-declarations: 0
|
||||
no-catch-shadow: 2
|
||||
no-delete-var: 2
|
||||
no-label-var: 2
|
||||
no-shadow-restricted-names: 2
|
||||
no-shadow: 0
|
||||
no-undef-init: 2
|
||||
no-undef: 0
|
||||
no-undefined: 0
|
||||
no-unused-vars: 0
|
||||
no-use-before-define: 0
|
||||
|
||||
# Node.js and CommonJS
|
||||
callback-return: 2
|
||||
global-require: 2
|
||||
handle-callback-err: 2
|
||||
no-mixed-requires: 0
|
||||
no-new-require: 0
|
||||
no-path-concat: 2
|
||||
no-process-exit: 2
|
||||
no-restricted-modules: 0
|
||||
no-sync: 0
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing: 0
|
||||
block-spacing: 0
|
||||
brace-style: 0
|
||||
camelcase: 0
|
||||
comma-spacing: 0
|
||||
comma-style: 0
|
||||
computed-property-spacing: 0
|
||||
consistent-this: 0
|
||||
eol-last: 0
|
||||
func-names: 0
|
||||
func-style: 0
|
||||
id-length: 0
|
||||
id-match: 0
|
||||
indent: 0
|
||||
jsx-quotes: 0
|
||||
key-spacing: 0
|
||||
linebreak-style: 0
|
||||
lines-around-comment: 0
|
||||
max-depth: 0
|
||||
max-len: 0
|
||||
max-nested-callbacks: 0
|
||||
max-params: 0
|
||||
max-statements: [2, 30]
|
||||
new-cap: 0
|
||||
new-parens: 0
|
||||
newline-after-var: 0
|
||||
no-array-constructor: 0
|
||||
no-bitwise: 0
|
||||
no-continue: 0
|
||||
no-inline-comments: 0
|
||||
no-lonely-if: 0
|
||||
no-mixed-spaces-and-tabs: 0
|
||||
no-multiple-empty-lines: 0
|
||||
no-negated-condition: 0
|
||||
no-nested-ternary: 0
|
||||
no-new-object: 0
|
||||
no-plusplus: 0
|
||||
no-restricted-syntax: 0
|
||||
no-spaced-func: 0
|
||||
no-ternary: 0
|
||||
no-trailing-spaces: 0
|
||||
no-underscore-dangle: 0
|
||||
no-unneeded-ternary: 0
|
||||
object-curly-spacing: 0
|
||||
one-var: 0
|
||||
operator-assignment: 0
|
||||
operator-linebreak: 0
|
||||
padded-blocks: 0
|
||||
quote-props: 0
|
||||
quotes: 0
|
||||
require-jsdoc: 0
|
||||
semi-spacing: 0
|
||||
semi: 0
|
||||
sort-vars: 0
|
||||
space-after-keywords: 0
|
||||
space-before-blocks: 0
|
||||
space-before-function-paren: 0
|
||||
space-before-keywords: 0
|
||||
space-in-parens: 0
|
||||
space-infix-ops: 0
|
||||
space-return-throw-case: 0
|
||||
space-unary-ops: 0
|
||||
spaced-comment: 0
|
||||
wrap-regex: 0
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: 0
|
||||
arrow-parens: 0
|
||||
arrow-spacing: 0
|
||||
constructor-super: 0
|
||||
generator-star-spacing: 0
|
||||
no-arrow-condition: 0
|
||||
no-class-assign: 0
|
||||
no-const-assign: 0
|
||||
no-dupe-class-members: 0
|
||||
no-this-before-super: 0
|
||||
no-var: 0
|
||||
object-shorthand: 0
|
||||
prefer-arrow-callback: 0
|
||||
prefer-const: 0
|
||||
prefer-reflect: 0
|
||||
prefer-spread: 0
|
||||
prefer-template: 0
|
||||
require-yield: 0
|
15
.npmignore
Normal file
15
.npmignore
Normal file
@ -0,0 +1,15 @@
|
||||
Gruntfile.js
|
||||
tasks
|
||||
node_modules
|
||||
.idea
|
||||
.gitignore
|
||||
.git
|
||||
.DS_Store
|
||||
test/lib
|
||||
test/testAdapter.js
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
.codeclimate.yml
|
||||
.eslinkignore
|
||||
.eslintrc
|
||||
.vscode
|
29
.travis.yml
Normal file
29
.travis.yml
Normal file
@ -0,0 +1,29 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
language: node_js
|
||||
node_js:
|
||||
- '4'
|
||||
- '6'
|
||||
- '8'
|
||||
- '10'
|
||||
before_script:
|
||||
- export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
|
||||
- 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi'
|
||||
- npm -v
|
||||
- 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter; fi'
|
||||
- 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then chmod +x ./cc-test-reporter; fi'
|
||||
- npm install winston@2.3.1
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
- 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./cc-test-reporter before-build; fi'
|
||||
after_success:
|
||||
- sed -i "s|tmp/node_modules/iobroker.nut/||" ./coverage/lcov.info
|
||||
- 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT -r 3623f3070bd9cae772a6757bdb0bcac4f8c2ebfbdebbe8e577bea9275810950d; fi'
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Programm starten",
|
||||
"program": "${workspaceRoot}/nut.js",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"args": [
|
||||
"--debug", "--force", "--trace-warnings"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "An den Prozess anfügen",
|
||||
"port": 58585,
|
||||
"address": "cubietruck4",
|
||||
"restart": false,
|
||||
"sourceMaps": false,
|
||||
"localRoot": "${workspaceRoot}",
|
||||
"remoteRoot": null
|
||||
}
|
||||
]
|
||||
}
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2017 Ingo Fischer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
132
README.md
Normal file
132
README.md
Normal file
@ -0,0 +1,132 @@
|
||||
![Logo](admin/nut.png)
|
||||
# ioBroker.nut
|
||||
|
||||
[![Greenkeeper badge](https://badges.greenkeeper.io/Apollon77/ioBroker.nut.svg)](https://greenkeeper.io/)
|
||||
|
||||
[![NPM version](http://img.shields.io/npm/v/iobroker.nut.svg)](https://www.npmjs.com/package/iobroker.nut)
|
||||
[![Downloads](https://img.shields.io/npm/dm/iobroker.nut.svg)](https://www.npmjs.com/package/iobroker.nut)
|
||||
[![Dependency Status](https://gemnasium.com/badges/github.com/Apollon77/ioBroker.nut.svg)](https://gemnasium.com/github.com/Apollon77/ioBroker.nut)
|
||||
[![Code Climate](https://codeclimate.com/github/Apollon77/ioBroker.nut/badges/gpa.svg)](https://codeclimate.com/github/Apollon77/ioBroker.nut)
|
||||
|
||||
**Tests:** Linux/Mac: [![Travis-CI](http://img.shields.io/travis/Apollon77/ioBroker.nut/master.svg)](https://travis-ci.org/Apollon77/ioBroker.nut)
|
||||
Windows: [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/Apollon77/ioBroker.nut?branch=master&svg=true)](https://ci.appveyor.com/project/Apollon77/ioBroker-nut/)
|
||||
|
||||
[![NPM](https://nodei.co/npm/iobroker.nut.png?downloads=true)](https://nodei.co/npm/iobroker.nut/)
|
||||
|
||||
This adapter for ioBroker connects to a defined NUT server to provide the status and details of a connected UPS/USV as ioBroker states, so that it can be used there.
|
||||
|
||||
## Description of parameters
|
||||
### host_ip
|
||||
IP address of the NUT server. NUT needs to run in server mode and needs to be accessible by the computer the
|
||||
iobroker NUT adapter runs on. So check firewall settings if you have problems and allow the access. If the UPS
|
||||
is connected locally you can also use 127.0.0.1 or localhost.
|
||||
|
||||
### host_port
|
||||
Port of NUT. The default port is <b>3493</b>
|
||||
|
||||
### ups_name
|
||||
Name of the UPS as defined in the NUT configuration of the NUT server.</p>
|
||||
Hint: If you want to connect to an UPS connected to a Synology diskstation the name is simply "ups".
|
||||
|
||||
### update_interval
|
||||
Interval in Seconds to update the data. Default is 300s
|
||||
|
||||
## UPS-Monitor Notifies
|
||||
Included is a small linux shell-script at scripts/nut-notify.sh which can be configured in upsmon.
|
||||
|
||||
The script needs execute rights (chmod +x nut-notify.sh).
|
||||
|
||||
It should be added to /etc/nut/upsmon.conf like:
|
||||
|
||||
```
|
||||
NOTIFYCMD "cd /opt/iobroker/;./nut-notify.sh"
|
||||
```
|
||||
|
||||
Additionally configure all relevant notify messages like:
|
||||
|
||||
```
|
||||
NOTIFYFLAG ONLINE SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG FSD SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG COMMOK SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG COMMBAD SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG SHUTDOWN SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG REPLBATT SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG NOCOMM SYSLOG+WALL+EXEC
|
||||
NOTIFYFLAG NOPARENT SYSLOG+WALL+EXEC
|
||||
```
|
||||
Important is the added "EXEC" flag.
|
||||
|
||||
One simple example for a nut-notify.sh script is:
|
||||
```
|
||||
#! /bin/sh
|
||||
# NUT adapter notify script.
|
||||
|
||||
logger -t nut-notify "Notify iobroker $UPSNAME -> $NOTIFYTYPE"
|
||||
/opt/iobroker/iobroker message nut notify "{\"upsname\":\"$UPSNAME\",\"notifytype\":\"$NOTIFYTYPE\"}"
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
If you have problems and the adapter do not deliver the data you can use the two scripts in directory "test"
|
||||
of the adapter installation (so normally in node_modules/iobroker.nut/test relative to your iobroker installation
|
||||
directory) to try it out on the commandline. Call the scripts using "node filename.js" to see the awaited parameters.</p>
|
||||
* **test_upslist.js**: Connects to the NUT server and returns a list of available UPS names
|
||||
* **test_upsvars.js**: Connects to the NUT server for a defined UPS and returns a list of available UPS variables
|
||||
|
||||
## Todo
|
||||
* docs for webpage
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.1.3 (2018-04-13)
|
||||
* Fix Admin
|
||||
|
||||
### 1.1.2 (2018-03-28)
|
||||
* Fix status parsing
|
||||
|
||||
### 1.1.1
|
||||
* Enhance error handling
|
||||
|
||||
### 1.1.0
|
||||
* Add possibility to call commands on the UPS
|
||||
|
||||
### 1.0.0
|
||||
* change mode from schedule to deamon
|
||||
* implement message support to receive messages from upsmon
|
||||
* add status.severity to get one status about the USV with values idle, operating, operating_critical, action_needed, unknown
|
||||
|
||||
### 0.3.0
|
||||
* add better usable status states under "status" channel
|
||||
|
||||
### 0.2.1
|
||||
* finalizied initial version
|
||||
|
||||
### 0.1.0
|
||||
* initial release for testing
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2018 Apollon77 <ingo@fischer-ka.de>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
64
admin/index.html
Normal file
64
admin/index.html
Normal file
@ -0,0 +1,64 @@
|
||||
<html>
|
||||
|
||||
<!-- these 4 files always have to be included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
|
||||
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
|
||||
|
||||
<!-- these two file always have to be included -->
|
||||
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||
|
||||
<script type="text/javascript" src="words.js"></script>
|
||||
|
||||
<!-- you have to define 2 functions in the global scope: -->
|
||||
<script type="text/javascript">
|
||||
function load(settings, onChange) {
|
||||
$('#host_ip').val(settings['host_ip']);
|
||||
$('#host_port').val(settings['host_port']);
|
||||
$('#ups_name').val(settings['ups_name']);
|
||||
$('#update_interval').val(settings['update_interval']);
|
||||
$('#username').val(settings['username']);
|
||||
$('#password').val(settings['password']);
|
||||
}
|
||||
|
||||
function save(callback) {
|
||||
callback({host_ip: $('#host_ip').val().trim(), host_port: $('#host_port').val().trim(), ups_name: $('#ups_name').val().trim(), update_interval: $('#update_interval').val().trim(), username: $('#username').val().trim(), password: $('#password').val().trim()});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- you have to put your config page in a div with id adapter-container -->
|
||||
<div id="adapter-container">
|
||||
|
||||
<table><tr>
|
||||
<td><img src="nut.png"/></td>
|
||||
<td><h3 class="translate">page_title</h3></td>
|
||||
</tr></table>
|
||||
<p style="padding-left: 12px">
|
||||
<label for="host_ip" class="translate">host_ip</label> <input class="value" type="text" id="host_ip" size="15"/><br/>
|
||||
<label for="host_port" class="translate">host_port</label> <input class="value" type="text" id="host_port" size="6"/><br/>
|
||||
<label for="ups_name" class="translate">ups_name</label> <input class="value" type="text" id="ups_name" size="20"/><br/>
|
||||
<label for="update_interval" class="translate">update_interval</label> <input class="value" type="text" id="update_interval" size="20"/>s<br/>
|
||||
<label for="username" class="translate">username</label> <input class="value" type="text" id="username" size="20"/><br/>
|
||||
<label for="password" class="translate">password</label> <input class="value" type="password" id="password" size="20"/><br/>
|
||||
</p>
|
||||
<h3 class="translate">host_ip</h3>
|
||||
<p class="translate">ip_info</p>
|
||||
|
||||
<h3 class="translate">host_port</h3>
|
||||
<p class="translate">port_info</p>
|
||||
|
||||
<h3 class="translate">ups_name</h3>
|
||||
<p class="translate">name_info</p>
|
||||
|
||||
<h3 class="translate">update_interval</h3>
|
||||
<p class="translate">update_interval_info</p>
|
||||
|
||||
<h3>Troubleshooting</h3>
|
||||
<p class="translate">trouble_info</p>
|
||||
|
||||
</div>
|
||||
|
||||
</html>
|
BIN
admin/nut.png
Normal file
BIN
admin/nut.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
34
admin/words.js
Normal file
34
admin/words.js
Normal file
@ -0,0 +1,34 @@
|
||||
systemDictionary = {
|
||||
"page_title": {"en": "NUT Adapter Settings", "de": "NUT Adapter Einstellungen", "ru": "NUT Adapter Settings"},
|
||||
"host_ip": {"en": "NUT Server IP", "de": "IP des NUT Servers", "ru": "IP NUT сервера"},
|
||||
"host_port": {"en": "NUT Server port", "de": "Port des NUT Servers", "ru": "Порт NUT сервера"},
|
||||
"ups_name": {"en": "NUT Name of the UPS", "de": "NUT Name der UPS", "ru": "NUT имя UPS"},
|
||||
"username": {"en": "Username of the UPS", "de": "Username der UPS", "ru": "Username of the UPS"},
|
||||
"password": {"en": "Password of the UPS", "de": "Passwort der UPS", "ru": "Password of the UPS"},
|
||||
"update_interval": {"en": "Update Interval", "de": "Aktualisierungsintervall", "ru": "Update Interval"},
|
||||
"ip_info": {
|
||||
"en":"IP address of the NUT server. NUT needs to run in server mode and needs to be accessible by the computer the iobroker NUT adapter runs on. So check firewall settings if you have problems and allow the access. If the UPS is connected locally you can also use 127.0.0.1 or localhost.",
|
||||
"de":"IP Adresse des NUT Servers. NUT muss im Server-Mode konfiguriert sein und von dem Rechner auf dem der iobroker Adapter installiert ist erreichbar sein. Bei Problemen sollten die Firewall-Einstellungen geprüft werden, dass der Zugriff gestattet ist. Wenn die USV am lokalen rechner angeschlossen ist kann 127.0.0.1 oder localhost genutzt werden.",
|
||||
"ru":"IP address of the NUT server. NUT needs to run in server mode and needs to be accessible by the computer the iobroker NUT adapter runs on. So check firewall settings if you have problems and allow the access. If the UPS is connected locally you can also use 127.0.0.1 or localhost."
|
||||
},
|
||||
"port_info": {
|
||||
"en":"Port of NUT. The default port is 3493.",
|
||||
"de":"Port des NUT Servers. Der Standardport ist 3493.",
|
||||
"ru":"Port of NUT. The default port is 3493."
|
||||
},
|
||||
"name_info": {
|
||||
"en":"Name of the UPS as defined in the NUT configuration of the NUT server.</p>Hint: If you want to connect to an UPS connected to a Synology diskstation the name is simply 'ups'.",
|
||||
"de":"Name der USV, wie in den NUT EInstellungen definiert.</p>Hinweis: Für eine USV, die an eine Synology Diskstation angeschlossen ist, lautet der Name 'ups'.",
|
||||
"ru":"Name of the UPS as defined in the NUT configuration of the NUT server.</p>Hint: If you want to connect to an UPS connected to a Synology diskstation the name is simply 'ups'."
|
||||
},
|
||||
"update_interval_info": {
|
||||
"en":"Interval in Seconds to update the data.",
|
||||
"de":"Intervall in Sekunden in dem die Daten aktualisiert werden.",
|
||||
"ru":"Interval in Seconds to update the data."
|
||||
},
|
||||
"trouble_info": {
|
||||
"en":"When you turn the adapter into debug then you can see all created states and their data in the logfile. If you have problems and the adapter do not deliver the data you can use the two scripts in directory 'test' of the adapter installation (so normally in node_modules/iobroker.nut/test relative to your iobroker installation directory) to try it out on the commandline. Call the scripts using 'node filename.js' to see the awaited parameters.</p><ul><li><b>test_upslist.js</b>: Connects to the NUT server and returns a list of available UPS names</li><li><b>test_upsvars.js</b>: Connects to the NUT server for a defined UPS and returns a list of available UPS variables</li></ul>",
|
||||
"de":"Wenn der Adapter im Debug Modus gestartet wird, werden im Logfile alle erzeugten States und deren Daten aufgelistet. Wenn der Adapter keine Daten liefert können für direklte Tests auch die beiden Skripte im Verzeichnis 'test' der Adapter-Installation (normalerweise unter node_modules/iobroker.nut/test relativ zur iobroker-Installation) an der Kommandozeile aufgerufen werden. Die Skripte können mit 'node filename.js' aufgerufen werden um die benötigten Parameter zu sehen.<ul><li><b>test_upslist.js</b>: Verbindet sich zu einem NUT Server und gibt die Namen der verbundenen USVs aus</li><li><b>test_upsvars.js</b>: Verbindet sich zu einem NUT Server für eine definierte USV und gibt die verfügbaren UPS Varialen aus</li></ul>",
|
||||
"ru":"When you turn the adapter into debug then you can see all created states and their data in the logfile. If you have problems and the adapter do not deliver the data you can use the two scripts in directory 'test' of the adapter installation (so normally in node_modules/iobroker.nut/test relative to your iobroker installation directory) to try it out on the commandline. Call the scripts using 'node filename.js' to see the awaited parameters.</p><ul><li><b>test_upslist.js</b>: Connects to the NUT server and returns a list of available UPS names</li><li><b>test_upsvars.js</b>: Connects to the NUT server for a defined UPS and returns a list of available UPS variables</li></ul>"
|
||||
}
|
||||
};
|
25
appveyor.yml
Normal file
25
appveyor.yml
Normal file
@ -0,0 +1,25 @@
|
||||
version: 'test-{build}'
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: '4'
|
||||
- nodejs_version: '6'
|
||||
- nodejs_version: '8'
|
||||
- nodejs_version: '10'
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%'
|
||||
install:
|
||||
- ps: 'Install-Product node $env:nodejs_version $env:platform'
|
||||
- ps: '$NpmVersion = (npm -v).Substring(0,1)'
|
||||
- ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }'
|
||||
- ps: npm --version
|
||||
- npm install
|
||||
- npm install winston@2.3.1
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
test_script:
|
||||
- echo %cd%
|
||||
- node --version
|
||||
- npm --version
|
||||
- npm test
|
||||
build: 'off'
|
74
io-package.json
Normal file
74
io-package.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"common": {
|
||||
"name": "nut",
|
||||
"version": "1.1.3",
|
||||
"news": {
|
||||
"1.1.3": {
|
||||
"en": "Fix Admin",
|
||||
"de": "Fixe Admin",
|
||||
"ru": "Fix Admin",
|
||||
"pt": "Corrigir Admin",
|
||||
"nl": "Admin oplossen",
|
||||
"fr": "Fix Admin",
|
||||
"it": "Correggi Admin",
|
||||
"es": "Fix Admin",
|
||||
"pl": "Napraw administratora"
|
||||
},
|
||||
"1.1.2": {
|
||||
"en": "fix status parsing",
|
||||
"de": "Statuserkennung verbessert",
|
||||
"ru": "fix status parsing"
|
||||
},
|
||||
"1.1.1": {
|
||||
"en": "enhance error handling",
|
||||
"de": "Fehlerbehandlung verbessert",
|
||||
"ru": "enhance error handling"
|
||||
},
|
||||
"1.1.0": {
|
||||
"en": "Add possibility to call commands on the UPS",
|
||||
"de": "Möglichkeit hinzugefügt Kommandos auf der USV auszuführen",
|
||||
"ru": "Add possibility to call commands on the UPS"
|
||||
},
|
||||
"1.0.0": {
|
||||
"en": "change mode from schedule to deamon, implement message support to receive messages from upsmon, add status.severity",
|
||||
"de": "Adapter arbeitet nun als Deamon, Notify-Support per upsmon hinzugefügt, status.severity hinzugefügt",
|
||||
"ru": "change mode from schedule to deamon, implement message support to receive messages from upsmon, add status.severity"
|
||||
},
|
||||
"0.3.1": {
|
||||
"en": "add better usable status states under 'status' channel",
|
||||
"de": "Besser nutzbare Stati States unter 'status' eingefügt",
|
||||
"ru": "add better usable status states under 'status' channel"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
"Apollon77 <ingo@fischer-ka.de>"
|
||||
],
|
||||
"title": "Network UPS Adapter",
|
||||
"desc": "Read all data from your UPS/USV via nut protocol",
|
||||
"platform": "Javascript/Node.js",
|
||||
"mode": "daemon",
|
||||
"messagebox": true,
|
||||
"subscribe": "messagebox",
|
||||
"stopBeforeUpdate": true,
|
||||
"icon": "nut.png",
|
||||
"extIcon": "https://raw.githubusercontent.com/Apollon77/ioBroker.nut/master/admin/nut.png",
|
||||
"readme": "https://github.com/Apollon77/ioBroker.nut/blob/master/README.md",
|
||||
"license": "MIT",
|
||||
"npmLibs": [],
|
||||
"type": "hardware",
|
||||
"keywords": ["iobroker", "nut", "ups", "usv"],
|
||||
"loglevel": "info",
|
||||
"enabled": false
|
||||
|
||||
},
|
||||
"native": {
|
||||
"host_ip": "127.0.0.1",
|
||||
"host_port": "3493",
|
||||
"ups_name": "nutName",
|
||||
"update_interval": 300,
|
||||
"username": "",
|
||||
"password": ""
|
||||
},
|
||||
"objects": [],
|
||||
"instanceObjects": []
|
||||
}
|
83
lib/utils.js
Normal file
83
lib/utils.js
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let controllerDir;
|
||||
let appName;
|
||||
|
||||
/**
|
||||
* returns application name
|
||||
*
|
||||
* The name of the application can be different and this function finds it out.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getAppName() {
|
||||
const parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 2].split('.')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* looks for js-controller home folder
|
||||
*
|
||||
* @param {boolean} isInstall
|
||||
* @returns {string}
|
||||
*/
|
||||
function getControllerDir(isInstall) {
|
||||
// Find the js-controller location
|
||||
const possibilities = [
|
||||
'iobroker.js-controller',
|
||||
'ioBroker.js-controller',
|
||||
];
|
||||
/** @type {string} */
|
||||
let controllerPath;
|
||||
for (const pkg of possibilities) {
|
||||
try {
|
||||
const possiblePath = require.resolve(pkg);
|
||||
if (fs.existsSync(possiblePath)) {
|
||||
controllerPath = possiblePath;
|
||||
break;
|
||||
}
|
||||
} catch (e) { /* not found */ }
|
||||
}
|
||||
if (controllerPath == null) {
|
||||
if (!isInstall) {
|
||||
console.log('Cannot find js-controller');
|
||||
process.exit(10);
|
||||
} else {
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
// we found the controller
|
||||
return path.dirname(controllerPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* reads controller base settings
|
||||
*
|
||||
* @alias getConfig
|
||||
* @returns {object}
|
||||
*/
|
||||
function getConfig() {
|
||||
let configPath;
|
||||
if (fs.existsSync(
|
||||
configPath = path.join(controllerDir, 'conf', appName + '.json')
|
||||
)) {
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} else if (fs.existsSync(
|
||||
configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json')
|
||||
)) {
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
} else {
|
||||
throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
|
||||
}
|
||||
}
|
||||
appName = getAppName();
|
||||
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);
|
||||
const adapter = require(path.join(controllerDir, 'lib/adapter.js'));
|
||||
|
||||
exports.controllerDir = controllerDir;
|
||||
exports.getConfig = getConfig;
|
||||
exports.Adapter = adapter;
|
||||
exports.appName = appName;
|
378
nut.js
Normal file
378
nut.js
Normal file
@ -0,0 +1,378 @@
|
||||
/**
|
||||
*
|
||||
* NUT adapter
|
||||
*
|
||||
* Adapter loading NUT data from an UPS
|
||||
*
|
||||
*/
|
||||
/* jshint -W097 */
|
||||
// jshint strict:true
|
||||
/*jslint node: true */
|
||||
/*jslint esversion: 6 */
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var utils = require(path.join(__dirname,'lib','utils')); // Get common adapter utils
|
||||
var Nut = require('node-nut');
|
||||
|
||||
var nutTimeout;
|
||||
|
||||
var nutCommands = null;
|
||||
|
||||
var adapter = utils.Adapter('nut');
|
||||
|
||||
adapter.on('ready', function (obj) {
|
||||
main();
|
||||
});
|
||||
|
||||
adapter.on('message', function (msg) {
|
||||
processMessage(msg);
|
||||
});
|
||||
|
||||
adapter.on('stateChange', function (id, state) {
|
||||
adapter.log.debug('stateChange ' + id + ' ' + JSON.stringify(state));
|
||||
var realNamespace = adapter.namespace + '.commands.';
|
||||
var stateId = id.substring(realNamespace.length);
|
||||
if (!state || state.ack || id.indexOf(realNamespace) !== 0) return;
|
||||
|
||||
var command = stateId.replace(/-/g,'.');
|
||||
initNutConnection(function(oNut) {
|
||||
if (adapter.config.username && adapter.config.password) {
|
||||
adapter.log.info('send username for command ' + command);
|
||||
oNut.SetUsername(adapter.config.username, function (err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while sending username: '+ err);
|
||||
}
|
||||
else {
|
||||
adapter.log.info('send password for command ' + command);
|
||||
oNut.SetPassword(adapter.config.password, function (err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while sending password: '+ err);
|
||||
}
|
||||
else {
|
||||
adapter.log.info('send command ' + command);
|
||||
oNut.RunUPSCommand(adapter.config.ups_name, command, function (err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while sending command ' + command + ': '+ err);
|
||||
}
|
||||
getCurrentNutValues(oNut, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
adapter.log.info('send command ' + command + ' without username and password');
|
||||
oNut.RunUPSCommand(adapter.config.ups_name, command, function (err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while sending command ' + command + ': '+ err);
|
||||
}
|
||||
getCurrentNutValues(oNut, true);
|
||||
});
|
||||
}
|
||||
|
||||
adapter.setState(id, {ack: true, val: false});
|
||||
});
|
||||
});
|
||||
|
||||
adapter.on('unload', function (callback) {
|
||||
if (nutTimeout) clearTimeout(nutTimeout);
|
||||
});
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
if (nutTimeout) clearTimeout(nutTimeout);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
if (adapter && adapter.log) {
|
||||
adapter.log.warn('Exception: ' + err);
|
||||
}
|
||||
if (nutTimeout) clearTimeout(nutTimeout);
|
||||
});
|
||||
|
||||
function main() {
|
||||
adapter.getForeignObject('system.adapter.' + adapter.namespace, function (err, obj) {
|
||||
if (!err && obj && (obj.common.mode !== 'daemon')) {
|
||||
obj.common.mode = 'daemon';
|
||||
if (obj.common.schedule) delete(obj.common.schedule);
|
||||
adapter.setForeignObject(obj._id, obj);
|
||||
}
|
||||
});
|
||||
adapter.setObjectNotExists('status.last_notify', {
|
||||
type: 'state',
|
||||
common: {
|
||||
name: 'status.last_notify',
|
||||
type: 'string',
|
||||
read: true,
|
||||
write: false
|
||||
},
|
||||
native: {id: 'status.last_notify'}
|
||||
});
|
||||
adapter.getState('status.last_notify', function (err, state) {
|
||||
if (!err && !state) {
|
||||
adapter.setState('status.last_notify', {ack: true, val: ''});
|
||||
}
|
||||
initNutConnection(function(oNut) {
|
||||
oNut.GetUPSCommands(adapter.config.ups_name, function(cmdlist, err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while getting all commands: '+ err);
|
||||
}
|
||||
else {
|
||||
adapter.log.debug('Got commands, create and subscribe command states');
|
||||
initNutCommands(cmdlist);
|
||||
}
|
||||
|
||||
getCurrentNutValues(oNut, true);
|
||||
|
||||
var update_interval = parseInt(adapter.config.update_interval,10) || 60;
|
||||
nutTimeout = setTimeout(updateNutData, update_interval*1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initNutCommands(cmdlist) {
|
||||
adapter.log.debug('Create Channel commands');
|
||||
adapter.setObjectNotExists('commands', {
|
||||
type: 'channel',
|
||||
common: {name: 'commands'},
|
||||
native: {}
|
||||
});
|
||||
|
||||
if (! cmdlist) return;
|
||||
nutCommands = cmdlist;
|
||||
for (var i = 0; i < cmdlist.length; i++) {
|
||||
var cmdName = cmdlist[i].replace(/\./g,'-');
|
||||
adapter.log.debug('Create State commands.' + cmdName);
|
||||
adapter.setObjectNotExists('commands.' + cmdName, {
|
||||
type: 'state',
|
||||
common: {
|
||||
name: 'commands.' + cmdName,
|
||||
role: 'button',
|
||||
type: 'boolean',
|
||||
read: true,
|
||||
write: true,
|
||||
def: false
|
||||
},
|
||||
native: {id: 'commands.' + cmdName}
|
||||
});
|
||||
adapter.setState('commands.' + cmdName, {ack: true, val: false});
|
||||
}
|
||||
adapter.subscribeStates('commands.*');
|
||||
}
|
||||
|
||||
/*
|
||||
Command Datapoint to be used with "NOIFY EVENTS" and upsmon
|
||||
ONLINE : The UPS is back on line.
|
||||
ONBATT : The UPS is on battery.
|
||||
LOWBATT : The UPS battery is low (as determined by the driver).
|
||||
FSD : The UPS has been commanded into the "forced shutdown" mode.
|
||||
COMMOK : Communication with the UPS has been established.
|
||||
COMMBAD : Communication with the UPS was just lost.
|
||||
SHUTDOWN : The local system is being shut down.
|
||||
REPLBATT : The UPS needs to have its battery replaced.
|
||||
NOCOMM : The UPS can’t be contacted for monitoring.
|
||||
*/
|
||||
function processMessage(message) {
|
||||
if (!message) return;
|
||||
|
||||
adapter.log.info('Message received = ' + JSON.stringify(message));
|
||||
|
||||
var updateNut = false;
|
||||
if (message.command === 'notify' && message.message) {
|
||||
adapter.log.info('got Notify ' + message.message.notifytype + ' for: ' + message.message.upsname);
|
||||
var ownName = adapter.config.ups_name + '@' + adapter.config.host_ip;
|
||||
adapter.log.info('ownName=' + ownName + ' --> ' + (ownName === message.message.upsname));
|
||||
if (ownName === message.message.upsname) {
|
||||
updateNut = true;
|
||||
adapter.setState('status.last_notify', {ack: true, val: message.message.notifytype});
|
||||
if (message.message.notifytype==='COMMBAD' || message.message.notifytype==='NOCOMM') parseAndSetSeverity("OFF");
|
||||
}
|
||||
}
|
||||
else updateNut = true;
|
||||
|
||||
if (updateNut) {
|
||||
if (nutTimeout) clearTimeout(nutTimeout);
|
||||
updateNutData();
|
||||
}
|
||||
}
|
||||
|
||||
function initNutConnection(callback) {
|
||||
var oNut = new Nut(adapter.config.host_port, adapter.config.host_ip);
|
||||
|
||||
oNut.on('error', function(err) {
|
||||
adapter.log.error('Error happend: ' + err);
|
||||
adapter.getState('status.last_notify', function (err, state) {
|
||||
if (!err && !state || (state && state.val!=='COMMBAD' && state.val!=='SHUTDOWN' && state.val!=='NOCOMM')) {
|
||||
adapter.setState('status.last_notify', {ack: true, val: 'ERROR'});
|
||||
}
|
||||
if (!err) parseAndSetSeverity("");
|
||||
});
|
||||
});
|
||||
|
||||
oNut.on('close', function() {
|
||||
adapter.log.debug('NUT Connection closed. Done.');
|
||||
});
|
||||
|
||||
oNut.on('ready', function() {
|
||||
adapter.log.debug('NUT Connection ready');
|
||||
callback(oNut);
|
||||
});
|
||||
|
||||
oNut.start();
|
||||
}
|
||||
|
||||
function updateNutData() {
|
||||
adapter.log.info('Start NUT update');
|
||||
|
||||
initNutConnection(function(oNut) {
|
||||
getCurrentNutValues(oNut, true);
|
||||
});
|
||||
|
||||
var update_interval = parseInt(adapter.config.update_interval,10) || 60;
|
||||
nutTimeout = setTimeout(updateNutData, update_interval*1000);
|
||||
}
|
||||
|
||||
function getCurrentNutValues(oNut, closeConnection) {
|
||||
oNut.GetUPSVars(adapter.config.ups_name, function(varlist, err) {
|
||||
if (err) {
|
||||
adapter.log.error('Err while getting NUT values: '+ err);
|
||||
}
|
||||
else {
|
||||
adapter.log.debug('Got values, start setting them');
|
||||
storeNutData(varlist);
|
||||
}
|
||||
if (closeConnection) oNut.close();
|
||||
});
|
||||
}
|
||||
|
||||
function storeNutData(varlist) {
|
||||
var last='';
|
||||
var current='';
|
||||
var index=0;
|
||||
var stateName='';
|
||||
|
||||
for (var key in varlist) {
|
||||
if (!varlist.hasOwnProperty(key)) continue;
|
||||
|
||||
index=key.indexOf('.');
|
||||
if (index > 0) {
|
||||
current=key.substring(0,index);
|
||||
}
|
||||
else {
|
||||
current='';
|
||||
last='';
|
||||
index=-1;
|
||||
}
|
||||
if (((last==='') || (last!==current)) && (current!=='')) {
|
||||
adapter.log.debug('Create Channel '+current);
|
||||
adapter.setObjectNotExists(current, {
|
||||
type: 'channel',
|
||||
common: {name: current},
|
||||
native: {}
|
||||
});
|
||||
}
|
||||
stateName=current+'.'+key.substring(index+1).replace(/\./g,'-');
|
||||
adapter.log.debug('Create State '+stateName);
|
||||
if (stateName === 'battery.charge') {
|
||||
adapter.setObjectNotExists(stateName, {
|
||||
type: 'state',
|
||||
common: {name: stateName, type: 'number', role: 'value.battery', read: true, write: false},
|
||||
native: {id: stateName}
|
||||
});
|
||||
}
|
||||
else {
|
||||
adapter.setObjectNotExists(stateName, {
|
||||
type: 'state',
|
||||
common: {name: stateName, type: 'string', read: true, write: false},
|
||||
native: {id: stateName}
|
||||
});
|
||||
}
|
||||
adapter.log.debug('Set State '+stateName+' = '+varlist[key]);
|
||||
adapter.setState(stateName, {ack: true, val: varlist[key]});
|
||||
last=current;
|
||||
}
|
||||
|
||||
adapter.log.debug('Create Channel status');
|
||||
adapter.setObjectNotExists('status', {
|
||||
type: 'channel',
|
||||
common: {name: 'status'},
|
||||
native: {}
|
||||
});
|
||||
adapter.setObjectNotExists('status.severity', {
|
||||
type: 'state',
|
||||
common: {
|
||||
name: 'status.severity',
|
||||
role: 'indicator',
|
||||
type: 'number',
|
||||
read: true,
|
||||
write: false,
|
||||
def:4,
|
||||
states: '0:idle;1:operating;2:operating_critical;3:action_needed;4:unknown'
|
||||
},
|
||||
native: {id: 'status.severity'}
|
||||
});
|
||||
if (varlist['ups.status']) {
|
||||
parseAndSetSeverity(varlist['ups.status']);
|
||||
}
|
||||
else parseAndSetSeverity("");
|
||||
|
||||
adapter.log.info('All Nut values set');
|
||||
}
|
||||
|
||||
function parseAndSetSeverity(ups_status) {
|
||||
var statusMap = {
|
||||
'OL':{name:'online',severity:'idle'},
|
||||
'OB':{name:'onbattery',severity:'operating'},
|
||||
'LB':{name:'lowbattery',severity:'operating_critical'},
|
||||
'HB':{name:'highbattery',severity:'operating_critical'},
|
||||
'RB':{name:'replacebattery',severity:'action_needed'},
|
||||
'CHRG':{name:'charging',severity:'idle'},
|
||||
'DISCHRG':{name:'discharging',severity:'operating'},
|
||||
'BYPASS':{name:'bypass',severity:'action_needed'},
|
||||
'CAL':{name:'calibration',severity:'operating'},
|
||||
'OFF':{name:'offline',severity:'action_needed'},
|
||||
'OVER':{name:'overload',severity:'action_needed'},
|
||||
'TRIM':{name:'trimming',severity:'operating'},
|
||||
'BOOST':{name:'boosting',severity:'operating'},
|
||||
'FSD':{name:'shutdown',severity:'operating_critical'}
|
||||
};
|
||||
var severity = {
|
||||
'idle':false,
|
||||
'operating':false,
|
||||
'operating_critical':false,
|
||||
'action_needed':false
|
||||
};
|
||||
if (ups_status.indexOf('FSD') !== -1) {
|
||||
ups_status += ' OB LB';
|
||||
}
|
||||
var checker=' '+ups_status+' ';
|
||||
var stateName="";
|
||||
for (var idx in statusMap) {
|
||||
if (statusMap.hasOwnProperty(idx)) {
|
||||
var found=(checker.indexOf(' ' + idx)>-1);
|
||||
stateName='status.'+statusMap[idx].name;
|
||||
adapter.log.debug('Create State '+stateName);
|
||||
adapter.setObjectNotExists(stateName, {
|
||||
type: 'state',
|
||||
common: {name: stateName, type: 'boolean', read: true, write: false},
|
||||
native: {id: stateName}
|
||||
});
|
||||
adapter.log.debug('Set State '+stateName+' = '+found);
|
||||
adapter.setState(stateName, {ack: true, val: found});
|
||||
if (found) {
|
||||
severity[statusMap[idx].severity]=true;
|
||||
adapter.log.debug('Severity Flag '+statusMap[idx].severity+'=true');
|
||||
}
|
||||
}
|
||||
}
|
||||
var severityVal = 4;
|
||||
if (severity.operating_critical) severityVal=2;
|
||||
else if (severity.action_needed) severityVal=3;
|
||||
else if (severity.operating) severityVal=1;
|
||||
else if (severity.idle) severityVal=0;
|
||||
|
||||
adapter.log.debug('Set State status.severity = '+severityVal);
|
||||
adapter.setState('status.severity', {ack: true, val: severityVal});
|
||||
}
|
41
package.json
Normal file
41
package.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "iobroker.nut",
|
||||
"version": "1.1.3",
|
||||
"description": "Network UPS Adapter",
|
||||
"author": "Ingo Fischer <ingo@fischer-ka.de>",
|
||||
"contributors": [],
|
||||
"homepage": "",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"iobroker",
|
||||
"nut",
|
||||
"ups",
|
||||
"usv"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Apollon77/ioBroker.nut"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-nut": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^5.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"nyc": "*"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Apollon77/ioBroker.nut/issues"
|
||||
},
|
||||
"main": "nut.js",
|
||||
"scripts": {
|
||||
"test": "nyc --reporter=lcov node_modules/mocha/bin/mocha test/testAdapter.js --exit && node node_modules/mocha/bin/mocha test/testPackageFiles.js"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": ["!**/node_modules/"],
|
||||
"include": [
|
||||
"**/tmp/node_modules/iobroker.nut/*.js"
|
||||
],
|
||||
"produce-source-map": true
|
||||
}
|
||||
}
|
5
scripts/nut-notify.sh
Normal file
5
scripts/nut-notify.sh
Normal file
@ -0,0 +1,5 @@
|
||||
#! /bin/sh
|
||||
# NUT adapter notify script.
|
||||
|
||||
logger -t nut-notify "Notify iobroker $UPSNAME -> $NOTIFYTYPE"
|
||||
/opt/iobroker/iobroker message nut notify "{\"upsname\":\"$UPSNAME\",\"notifytype\":\"$NOTIFYTYPE\"}"
|
728
test/lib/setup.js
Normal file
728
test/lib/setup.js
Normal file
@ -0,0 +1,728 @@
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
// check if tmp directory exists
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var child_process = require('child_process');
|
||||
var rootDir = path.normalize(__dirname + '/../../');
|
||||
var pkg = require(rootDir + 'package.json');
|
||||
var debug = typeof v8debug === 'object';
|
||||
pkg.main = pkg.main || 'main.js';
|
||||
|
||||
var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
|
||||
adapterName = adapterName[adapterName.length - 2];
|
||||
var adapterStarted = false;
|
||||
|
||||
function getAppName() {
|
||||
var parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 3].split('.')[0];
|
||||
}
|
||||
|
||||
var appName = getAppName().toLowerCase();
|
||||
|
||||
var objects;
|
||||
var states;
|
||||
|
||||
var pid = null;
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
|
||||
var targetFile = target;
|
||||
|
||||
//if target is a directory a new file with the same name will be created
|
||||
if (fs.existsSync(target)) {
|
||||
if ( fs.lstatSync( target ).isDirectory() ) {
|
||||
targetFile = path.join(target, path.basename(source));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fs.writeFileSync(targetFile, fs.readFileSync(source));
|
||||
}
|
||||
catch (err) {
|
||||
console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)");
|
||||
}
|
||||
}
|
||||
|
||||
function copyFolderRecursiveSync(source, target, ignore) {
|
||||
var files = [];
|
||||
|
||||
var base = path.basename(source);
|
||||
if (base === adapterName) {
|
||||
base = pkg.name;
|
||||
}
|
||||
//check if folder needs to be created or integrated
|
||||
var targetFolder = path.join(target, base);
|
||||
if (!fs.existsSync(targetFolder)) {
|
||||
fs.mkdirSync(targetFolder);
|
||||
}
|
||||
|
||||
//copy
|
||||
if (fs.lstatSync(source).isDirectory()) {
|
||||
files = fs.readdirSync(source);
|
||||
files.forEach(function (file) {
|
||||
if (ignore && ignore.indexOf(file) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var curSource = path.join(source, file);
|
||||
var curTarget = path.join(targetFolder, file);
|
||||
if (fs.lstatSync(curSource).isDirectory()) {
|
||||
// ignore grunt files
|
||||
if (file.indexOf('grunt') !== -1) return;
|
||||
if (file === 'chai') return;
|
||||
if (file === 'mocha') return;
|
||||
copyFolderRecursiveSync(curSource, targetFolder, ignore);
|
||||
} else {
|
||||
copyFileSync(curSource, curTarget);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp')) {
|
||||
fs.mkdirSync(rootDir + 'tmp');
|
||||
}
|
||||
|
||||
function storeOriginalFiles() {
|
||||
console.log('Store original files...');
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
|
||||
objects['system.adapter.admin.0'].common.enabled = false;
|
||||
}
|
||||
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
|
||||
objects['system.adapter.admin.1'].common.enabled = false;
|
||||
}
|
||||
|
||||
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json');
|
||||
fs.writeFileSync(dataDir + 'states.json.original', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json found - ignore');
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOriginalFiles() {
|
||||
console.log('restoreOriginalFiles...');
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var f = fs.readFileSync(dataDir + 'objects.json.original');
|
||||
fs.writeFileSync(dataDir + 'objects.json', f);
|
||||
try {
|
||||
f = fs.readFileSync(dataDir + 'states.json.original');
|
||||
fs.writeFileSync(dataDir + 'states.json', f);
|
||||
}
|
||||
catch (err) {
|
||||
console.log('no states.json.original found - ignore');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkIsAdapterInstalled(cb, counter, customName) {
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
counter = counter || 0;
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
console.log('checkIsAdapterInstalled...');
|
||||
|
||||
try {
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.' + customName + '.0']) {
|
||||
console.log('checkIsAdapterInstalled: ready!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
} else {
|
||||
console.warn('checkIsAdapterInstalled: still not ready');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.error('checkIsAdapterInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsAdapterInstalled: wait...');
|
||||
setTimeout(function() {
|
||||
checkIsAdapterInstalled(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsControllerInstalled(cb, counter) {
|
||||
counter = counter || 0;
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
console.log('checkIsControllerInstalled...');
|
||||
try {
|
||||
var f = fs.readFileSync(dataDir + 'objects.json');
|
||||
var objects = JSON.parse(f.toString());
|
||||
if (objects['system.adapter.admin.0']) {
|
||||
console.log('checkIsControllerInstalled: installed!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
|
||||
if (counter > 20) {
|
||||
console.log('checkIsControllerInstalled: Cannot install!');
|
||||
if (cb) cb('Cannot install');
|
||||
} else {
|
||||
console.log('checkIsControllerInstalled: wait...');
|
||||
setTimeout(function() {
|
||||
checkIsControllerInstalled(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function installAdapter(customName, cb) {
|
||||
if (typeof customName === 'function') {
|
||||
cb = customName;
|
||||
customName = null;
|
||||
}
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
console.log('Install adapter...');
|
||||
var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
|
||||
// make first install
|
||||
if (debug) {
|
||||
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
checkIsAdapterInstalled(function (error) {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
} else {
|
||||
// add controller
|
||||
var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
checkIsAdapterInstalled(function (error) {
|
||||
if (error) console.error(error);
|
||||
console.log('Adapter installed.');
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function waitForEnd(_pid, cb) {
|
||||
if (!_pid) {
|
||||
cb(-1, -1);
|
||||
return;
|
||||
}
|
||||
_pid.on('exit', function (code, signal) {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
_pid.on('close', function (code, signal) {
|
||||
if (_pid) {
|
||||
_pid = null;
|
||||
cb(code, signal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function installJsController(cb) {
|
||||
console.log('installJsController...');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
|
||||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
|
||||
// try to detect appName.js-controller in node_modules/appName.js-controller
|
||||
// travis CI installs js-controller into node_modules
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
|
||||
// copy all
|
||||
// stop controller
|
||||
console.log('Stop controller if running...');
|
||||
var _pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js stop', {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
// copy all files into
|
||||
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
|
||||
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
|
||||
console.log('Copy js-controller...');
|
||||
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
|
||||
}
|
||||
|
||||
console.log('Setup js-controller...');
|
||||
var __pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
waitForEnd(__pid, function () {
|
||||
checkIsControllerInstalled(function () {
|
||||
// change ports for object and state DBs
|
||||
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
console.log('Setup finished.');
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(function () {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// check if port 9000 is free, else admin adapter will be added to running instance
|
||||
var client = new require('net').Socket();
|
||||
client.connect(9000, '127.0.0.1', function() {
|
||||
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
setTimeout(function () {
|
||||
client.destroy();
|
||||
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
|
||||
console.log('installJsController: no js-controller => install from git');
|
||||
|
||||
child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
|
||||
cwd: rootDir + 'tmp/',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
console.log('Setup js-controller...');
|
||||
var __pid;
|
||||
if (debug) {
|
||||
// start controller
|
||||
child_process.exec('node ' + appName + '.js setup first', {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
child_process.fork(appName + '.js', ['setup', 'first'], {
|
||||
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// let npm install admin and run setup
|
||||
checkIsControllerInstalled(function () {
|
||||
var _pid;
|
||||
|
||||
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
|
||||
_pid = child_process.fork(appName + '.js', ['stop'], {
|
||||
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
|
||||
waitForEnd(_pid, function () {
|
||||
// change ports for object and state DBs
|
||||
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(function () {
|
||||
storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
console.log('installJsController: js-controller installed');
|
||||
if (cb) cb(false);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function copyAdapterToController() {
|
||||
console.log('Copy adapter...');
|
||||
// Copy adapter to tmp/node_modules/appName.adapter
|
||||
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
|
||||
console.log('Adapter copied.');
|
||||
}
|
||||
|
||||
function clearControllerLog() {
|
||||
var dirPath = rootDir + 'tmp/log';
|
||||
var files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear controller log...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Controller log cleared');
|
||||
} catch (err) {
|
||||
console.error('cannot clear log: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearDB() {
|
||||
var dirPath = rootDir + 'tmp/iobroker-data/sqlite';
|
||||
var files;
|
||||
try {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log('Clear sqlite DB...');
|
||||
files = fs.readdirSync(dirPath);
|
||||
} else {
|
||||
console.log('Create controller log directory...');
|
||||
files = [];
|
||||
fs.mkdirSync(dirPath);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Cannot read "' + dirPath + '"');
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
try {
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Clear sqlite DB');
|
||||
} catch (err) {
|
||||
console.error('cannot clear DB: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupController(cb) {
|
||||
installJsController(function (isInited) {
|
||||
clearControllerLog();
|
||||
clearDB();
|
||||
|
||||
if (!isInited) {
|
||||
restoreOriginalFiles();
|
||||
copyAdapterToController();
|
||||
}
|
||||
// read system.config object
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
var objs;
|
||||
try {
|
||||
objs = fs.readFileSync(dataDir + 'objects.json');
|
||||
objs = JSON.parse(objs);
|
||||
}
|
||||
catch (e) {
|
||||
console.log('ERROR reading/parsing system configuration. Ignore');
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
if (!objs || !objs['system.config']) {
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
|
||||
if (cb) cb(objs['system.config']);
|
||||
});
|
||||
}
|
||||
|
||||
function startAdapter(objects, states, callback) {
|
||||
if (adapterStarted) {
|
||||
console.log('Adapter already started ...');
|
||||
if (callback) callback(objects, states);
|
||||
return;
|
||||
}
|
||||
adapterStarted = true;
|
||||
console.log('startAdapter...');
|
||||
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
|
||||
try {
|
||||
if (debug) {
|
||||
// start controller
|
||||
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
// start controller
|
||||
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
|
||||
cwd: rootDir + 'tmp',
|
||||
stdio: [0, 1, 2, 'ipc']
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify(error));
|
||||
}
|
||||
} else {
|
||||
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
|
||||
}
|
||||
if (callback) callback(objects, states);
|
||||
}
|
||||
|
||||
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
|
||||
if (typeof isStartAdapter === 'function') {
|
||||
callback = onStateChange;
|
||||
onStateChange = onObjectChange;
|
||||
onObjectChange = isStartAdapter;
|
||||
isStartAdapter = true;
|
||||
}
|
||||
|
||||
if (onStateChange === undefined) {
|
||||
callback = onObjectChange;
|
||||
onObjectChange = undefined;
|
||||
}
|
||||
|
||||
if (pid) {
|
||||
console.error('Controller is already started!');
|
||||
} else {
|
||||
console.log('startController...');
|
||||
adapterStarted = false;
|
||||
var isObjectConnected;
|
||||
var isStatesConnected;
|
||||
|
||||
var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
|
||||
objects = new Objects({
|
||||
connection: {
|
||||
"type" : "file",
|
||||
"host" : "127.0.0.1",
|
||||
"port" : 19001,
|
||||
"user" : "",
|
||||
"pass" : "",
|
||||
"noFileCache": false,
|
||||
"connectTimeout": 2000
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.warn(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.error(msg);
|
||||
}
|
||||
},
|
||||
connected: function () {
|
||||
isObjectConnected = true;
|
||||
if (isStatesConnected) {
|
||||
console.log('startController: started!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onObjectChange
|
||||
});
|
||||
|
||||
// Just open in memory DB itself
|
||||
var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
|
||||
states = new States({
|
||||
connection: {
|
||||
type: 'file',
|
||||
host: '127.0.0.1',
|
||||
port: 19000,
|
||||
options: {
|
||||
auth_pass: null,
|
||||
retry_max_delay: 15000
|
||||
}
|
||||
},
|
||||
logger: {
|
||||
silly: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
debug: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
info: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
warn: function (msg) {
|
||||
console.log(msg);
|
||||
},
|
||||
error: function (msg) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
connected: function () {
|
||||
isStatesConnected = true;
|
||||
if (isObjectConnected) {
|
||||
console.log('startController: started!!');
|
||||
if (isStartAdapter) {
|
||||
startAdapter(objects, states, callback);
|
||||
} else {
|
||||
if (callback) {
|
||||
callback(objects, states);
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
change: onStateChange
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stopAdapter(cb) {
|
||||
if (!pid) {
|
||||
console.error('Controller is not running!');
|
||||
if (cb) {
|
||||
setTimeout(function () {
|
||||
cb(false);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
adapterStarted = false;
|
||||
pid.on('exit', function (code, signal) {
|
||||
if (pid) {
|
||||
console.log('child process terminated due to receipt of signal ' + signal);
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.on('close', function (code, signal) {
|
||||
if (pid) {
|
||||
if (cb) cb();
|
||||
pid = null;
|
||||
}
|
||||
});
|
||||
|
||||
pid.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
|
||||
function _stopController() {
|
||||
if (objects) {
|
||||
objects.destroy();
|
||||
objects = null;
|
||||
}
|
||||
if (states) {
|
||||
states.destroy();
|
||||
states = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stopController(cb) {
|
||||
var timeout;
|
||||
if (objects) {
|
||||
console.log('Set system.adapter.' + pkg.name + '.0');
|
||||
objects.setObject('system.adapter.' + pkg.name + '.0', {
|
||||
common:{
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopAdapter(function () {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(true);
|
||||
cb = null;
|
||||
}
|
||||
});
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
timeout = null;
|
||||
console.log('child process NOT terminated');
|
||||
|
||||
_stopController();
|
||||
|
||||
if (cb) {
|
||||
cb(false);
|
||||
cb = null;
|
||||
}
|
||||
pid = null;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Setup the adapter
|
||||
function setAdapterConfig(common, native, instance) {
|
||||
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
if (common) objects[id].common = common;
|
||||
if (native) objects[id].native = native;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
|
||||
}
|
||||
|
||||
// Read config of the adapter
|
||||
function getAdapterConfig(instance) {
|
||||
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
return objects[id];
|
||||
}
|
||||
|
||||
if (typeof module !== undefined && module.parent) {
|
||||
module.exports.getAdapterConfig = getAdapterConfig;
|
||||
module.exports.setAdapterConfig = setAdapterConfig;
|
||||
module.exports.startController = startController;
|
||||
module.exports.stopController = stopController;
|
||||
module.exports.setupController = setupController;
|
||||
module.exports.stopAdapter = stopAdapter;
|
||||
module.exports.startAdapter = startAdapter;
|
||||
module.exports.installAdapter = installAdapter;
|
||||
module.exports.appName = appName;
|
||||
module.exports.adapterName = adapterName;
|
||||
module.exports.adapterStarted = adapterStarted;
|
||||
}
|
195
test/testAdapter.js
Normal file
195
test/testAdapter.js
Normal file
@ -0,0 +1,195 @@
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
/*jshint expr: true*/
|
||||
var expect = require('chai').expect;
|
||||
var setup = require(__dirname + '/lib/setup');
|
||||
|
||||
var objects = null;
|
||||
var states = null;
|
||||
var onStateChanged = null;
|
||||
var onObjectChanged = null;
|
||||
var sendToID = 1;
|
||||
|
||||
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1);
|
||||
|
||||
function checkConnectionOfAdapter(cb, counter) {
|
||||
counter = counter || 0;
|
||||
console.log('Try check #' + counter);
|
||||
if (counter > 30) {
|
||||
if (cb) cb('Cannot check connection');
|
||||
return;
|
||||
}
|
||||
|
||||
states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
if (state && state.val) {
|
||||
if (cb) cb();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
checkConnectionOfAdapter(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkValueOfState(id, value, cb, counter) {
|
||||
counter = counter || 0;
|
||||
if (counter > 20) {
|
||||
if (cb) cb('Cannot check value Of State ' + id);
|
||||
return;
|
||||
}
|
||||
|
||||
states.getState(id, function (err, state) {
|
||||
if (err) console.error(err);
|
||||
if (value === null && !state) {
|
||||
if (cb) cb();
|
||||
} else
|
||||
if (state && (value === undefined || state.val === value)) {
|
||||
if (cb) cb();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
checkValueOfState(id, value, cb, counter + 1);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendTo(target, command, message, callback) {
|
||||
onStateChanged = function (id, state) {
|
||||
if (id === 'messagebox.system.adapter.test.0') {
|
||||
callback(state.message);
|
||||
}
|
||||
};
|
||||
|
||||
states.pushMessage('system.adapter.' + target, {
|
||||
command: command,
|
||||
message: message,
|
||||
from: 'system.adapter.test.0',
|
||||
callback: {
|
||||
message: message,
|
||||
id: sendToID++,
|
||||
ack: false,
|
||||
time: (new Date()).getTime()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Test ' + adapterShortName + ' adapter', function() {
|
||||
before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) {
|
||||
this.timeout(600000); // because of first install from npm
|
||||
|
||||
setup.setupController(function () {
|
||||
var config = setup.getAdapterConfig();
|
||||
// enable adapter
|
||||
config.common.enabled = true;
|
||||
config.common.loglevel = 'debug';
|
||||
|
||||
//config.native.dbtype = 'sqlite';
|
||||
|
||||
setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController(true, function(id, obj) {}, function (id, state) {
|
||||
if (onStateChanged) onStateChanged(id, state);
|
||||
},
|
||||
function (_objects, _states) {
|
||||
objects = _objects;
|
||||
states = _states;
|
||||
_done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) {
|
||||
this.timeout(60000);
|
||||
checkConnectionOfAdapter(function (res) {
|
||||
if (res) console.log(res);
|
||||
expect(res).not.to.be.equal('Cannot check connection');
|
||||
objects.setObject('system.adapter.test.0', {
|
||||
common: {
|
||||
|
||||
},
|
||||
type: 'instance'
|
||||
},
|
||||
function () {
|
||||
states.subscribeMessage('system.adapter.test.0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// We expect ERROR as last Notify necause no nut is running there
|
||||
it('Test ' + adapterShortName + ' adapter: test initial state as ERROR', function (done) {
|
||||
this.timeout(25000);
|
||||
|
||||
setTimeout(function() {
|
||||
states.getState('nut.0.status.last_notify', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
expect(state).to.exist;
|
||||
if (!state) {
|
||||
console.error('state "status.last_notify" not set');
|
||||
}
|
||||
else {
|
||||
console.log('check status.last_notify ... ' + state.val);
|
||||
expect(state.val).to.exist;
|
||||
expect(state.val).to.be.equal('ERROR');
|
||||
}
|
||||
states.getState('nut.0.status.severity', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
expect(state).to.exist;
|
||||
if (!state) {
|
||||
console.error('state "status.severity" not set');
|
||||
}
|
||||
else {
|
||||
console.log('check status.severity ... ' + state.val);
|
||||
}
|
||||
expect(state.val).to.exist;
|
||||
expect(state.val).to.be.equal(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
it('Test ' + adapterShortName + ' adapter: send notify Message and receive answer', function (done) {
|
||||
this.timeout(25000);
|
||||
var now = new Date().getTime();
|
||||
|
||||
console.log('send notify with "COMMBAD" to adapter ...');
|
||||
sendTo('nut.0', 'notify', {notifytype: 'COMMBAD', upsname: 'nutName@127.0.0.1'});
|
||||
setTimeout(function() {
|
||||
states.getState('nut.0.status.last_notify', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
expect(state).to.exist;
|
||||
if (!state) {
|
||||
console.error('state "status.last_notify" not set');
|
||||
}
|
||||
else {
|
||||
console.log('check status.last_notify ... ' + state.val);
|
||||
}
|
||||
expect(state.val).to.be.equal('COMMBAD');
|
||||
states.getState('nut.0.status.severity', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
expect(state).to.exist;
|
||||
if (!state) {
|
||||
console.error('state "status.severity" not set');
|
||||
}
|
||||
else {
|
||||
console.log('check status.severity ... ' + state.val);
|
||||
}
|
||||
expect(state.val).to.exist;
|
||||
expect(state.val).to.be.equal(4);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
setup.stopController(function (normalTerminated) {
|
||||
console.log('Adapter normal terminated: ' + normalTerminated);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
91
test/testPackageFiles.js
Normal file
91
test/testPackageFiles.js
Normal file
@ -0,0 +1,91 @@
|
||||
/* jshint -W097 */
|
||||
/* jshint strict:false */
|
||||
/* jslint node: true */
|
||||
/* jshint expr: true */
|
||||
var expect = require('chai').expect;
|
||||
var fs = require('fs');
|
||||
|
||||
describe('Test package.json and io-package.json', function() {
|
||||
it('Test package files', function (done) {
|
||||
console.log();
|
||||
|
||||
var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
|
||||
var ioPackage = JSON.parse(fileContentIOPackage);
|
||||
|
||||
var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
|
||||
var npmPackage = JSON.parse(fileContentNPMPackage);
|
||||
|
||||
expect(ioPackage).to.be.an('object');
|
||||
expect(npmPackage).to.be.an('object');
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
|
||||
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
|
||||
|
||||
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
|
||||
|
||||
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
|
||||
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
|
||||
console.log();
|
||||
}
|
||||
|
||||
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
|
||||
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
|
||||
|
||||
if (ioPackage.common.name.indexOf('template') !== 0) {
|
||||
if (Array.isArray(ioPackage.common.authors)) {
|
||||
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
|
||||
if (ioPackage.common.authors.length === 1) {
|
||||
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
|
||||
console.log();
|
||||
}
|
||||
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
|
||||
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
|
||||
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
|
||||
console.log();
|
||||
}
|
||||
if (
|
||||
ioPackage.common.title.indexOf('iobroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('ioBroker') !== -1 ||
|
||||
ioPackage.common.title.indexOf('adapter') !== -1 ||
|
||||
ioPackage.common.title.indexOf('Adapter') !== -1
|
||||
) {
|
||||
console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.');
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (ioPackage.common.name.indexOf('vis-') !== 0) {
|
||||
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
|
||||
console.log('WARNING: Admin3 support is missing! Please add it');
|
||||
console.log();
|
||||
}
|
||||
if (ioPackage.common.materialize) {
|
||||
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
|
||||
}
|
||||
}
|
||||
|
||||
var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
|
||||
var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
|
||||
if (fileContentReadme.indexOf('## Changelog') === -1) {
|
||||
console.log('Warning: The README.md should have a section ## Changelog');
|
||||
console.log();
|
||||
}
|
||||
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
|
||||
if (!licenseFileExists) {
|
||||
console.log('Warning: The License should also exist as LICENSE file');
|
||||
console.log();
|
||||
}
|
||||
if (fileContentReadme.indexOf('## License') === -1) {
|
||||
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
|
||||
console.log();
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
27
test/test_upslist.js
Normal file
27
test/test_upslist.js
Normal file
@ -0,0 +1,27 @@
|
||||
if (process.argv.length<3) {
|
||||
console.log('Call: test_upsvars.js <ip> <port> <ups-name>');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
var Nut = require('node-nut');
|
||||
|
||||
//oNut = new Nut(3493, 'localhost');
|
||||
oNut = new Nut(process.argv[3], process.argv[2]);
|
||||
|
||||
oNut.on('error', function(err) {
|
||||
console.log('There was an error: ' + err);
|
||||
});
|
||||
|
||||
oNut.on('close', function() {
|
||||
console.log('Connection closed.');
|
||||
});
|
||||
|
||||
oNut.on('ready', function() {
|
||||
self = this;
|
||||
this.GetUPSList(function(upslist) {
|
||||
console.log(upslist);
|
||||
self.close();
|
||||
});
|
||||
});
|
||||
|
||||
oNut.start();
|
27
test/test_upsvars.js
Normal file
27
test/test_upsvars.js
Normal file
@ -0,0 +1,27 @@
|
||||
if (process.argv.length<3) {
|
||||
console.log('Call: test_upsvars.js <ip> <port> <ups-name>');
|
||||
process.exit();
|
||||
}
|
||||
|
||||
var Nut = require('node-nut');
|
||||
|
||||
//oNut = new Nut(3493, 'localhost');
|
||||
oNut = new Nut(process.argv[3], process.argv[2]);
|
||||
|
||||
oNut.on('error', function(err) {
|
||||
console.log('There was an error: ' + err);
|
||||
});
|
||||
|
||||
oNut.on('close', function() {
|
||||
console.log('Connection closed.');
|
||||
});
|
||||
|
||||
oNut.on('ready', function() {
|
||||
self = this;
|
||||
this.GetUPSVars(process.argv[4],function(varlist) {
|
||||
console.log(varlist);
|
||||
self.close();
|
||||
});
|
||||
});
|
||||
|
||||
oNut.start();
|
Loading…
Reference in New Issue
Block a user