Initial commit
This commit is contained in:
commit
6b4ec16652
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
.idea
|
||||
tmp
|
||||
admin/i18n/flat.txt
|
||||
admin/i18n/*/flat.txt
|
||||
iob_npm.done
|
||||
package-lock.json
|
||||
/userdata
|
9
.npmignore
Normal file
9
.npmignore
Normal file
@ -0,0 +1,9 @@
|
||||
gulpfile.js
|
||||
tasks
|
||||
tmp
|
||||
test
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
admin/i18n
|
||||
iob_npm.done
|
||||
package-lock.json
|
23
.travis.yml
Normal file
23
.travis.yml
Normal file
@ -0,0 +1,23 @@
|
||||
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
|
||||
- npm install winston@2.3.1
|
||||
- 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production'
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
177
LICENSE
Normal file
177
LICENSE
Normal file
@ -0,0 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
191
README.md
Normal file
191
README.md
Normal file
@ -0,0 +1,191 @@
|
||||
![Logo](admin/node-red.png)
|
||||
# ioBroker node-red Adapter
|
||||
==============
|
||||
[![NPM version](http://img.shields.io/npm/v/iobroker.node-red.svg)](https://www.npmjs.com/package/iobroker.node-red)
|
||||
[![Downloads](https://img.shields.io/npm/dm/iobroker.node-red.svg)](https://www.npmjs.com/package/iobroker.node-red)
|
||||
[![Tests](https://travis-ci.org/ioBroker/ioBroker.node-red.svg?branch=master)](https://travis-ci.org/ioBroker/ioBroker.node-red)
|
||||
|
||||
[![NPM](https://nodei.co/npm/iobroker.node-red.png?downloads=true)](https://nodei.co/npm/iobroker.node-red/)
|
||||
|
||||
# Starts node-red instance and communicates with it.
|
||||
|
||||
***This adapter needs at least nodejs 4.x to work***
|
||||
|
||||
This adapter uses the node-red server from https://github.com/node-red/node-red
|
||||
|
||||
**Note:** If in select ID dialog of the ioBroker node you cannot find some variable, restart node-red instance. By restarting the new list of objects will be created.
|
||||
|
||||
## Changelog
|
||||
### 1.7.1 (2017-09-24)
|
||||
* (bluefox) use newer version of node-red 0.19.4
|
||||
* (bluefox) Basic authentication was added
|
||||
|
||||
### 1.7.0 (2017-08-23)
|
||||
* (bluefox) use newer version of node-red 0.19.1
|
||||
|
||||
### 1.6.0 (2017-08-06)
|
||||
* (bluefox) use newer version of node-red 0.18.7
|
||||
* (bluefox) Admin3 dialog implemented
|
||||
* (bluefox) RAM settings were added
|
||||
* (bluefox) add credentialSecret option
|
||||
|
||||
### 1.5.1 (2017-02-16)
|
||||
* (Apollon77) queue set state requests till ioBroker connection has been initialized
|
||||
|
||||
### 1.5.0 (2018-02-14)
|
||||
* (Apollon77) use newer version of node-red 0.18.2
|
||||
|
||||
### 1.4.1 (2017-10-03)
|
||||
* (twonky4) fix blank topic support
|
||||
|
||||
### 1.4.0 (2017-08-06)
|
||||
* (bluefox) use newer version of node-red 0.17.5
|
||||
|
||||
### 1.3.0 (2017-04-13)
|
||||
* (bluefox) Update the select ID dialog
|
||||
* (bluefox) Add node-red-contrib-polymer
|
||||
|
||||
### 1.2.0 (2017-02-14)
|
||||
* (bluefox) use newer version of node-red 0.16.2
|
||||
|
||||
### 1.1.6 (2017-01-24)
|
||||
* (bluefox) use newer version of node-red 0.16.2
|
||||
|
||||
### 1.1.5 (2017-01-03)
|
||||
* (Erhard Weinell) support concurrent access to GetNode
|
||||
|
||||
### 1.1.4 (2016-11-04)
|
||||
* (bluefox) use newer version of node-red 0.15.2
|
||||
|
||||
### 1.1.2 (2016-07-23)
|
||||
* (nobodyMO) use newer version of node-red 0.14.6
|
||||
* (nobodyMO) change topic name processing
|
||||
|
||||
### 1.1.1 (2016-07-08)
|
||||
* (nobodyMO) use newer version of node-red 0.14.4
|
||||
|
||||
### 1.1.0 (2016-05-22)
|
||||
* (ploebb) configurable: convert values to string
|
||||
* (nobodyMO) use newer version of node-red 0.14.3
|
||||
|
||||
### 1.0.1 (2016-05-22)
|
||||
* (bluefox) on some systems node-red was available under wrong URL http://ip:1881/undefined. Fixed
|
||||
|
||||
### 1.0.0 (2016-04-29)
|
||||
* (bluefox) support of npm 2/3
|
||||
|
||||
### 0.4.4 (2016-04-29)
|
||||
* (bluefox) install with flag unsafePerm
|
||||
|
||||
### 0.4.3 (2016-04-23)
|
||||
* (bluefox) use node-red 0.13.4
|
||||
|
||||
### 0.4.2 (2016-01-21)
|
||||
* (nobodyMO) Add httpRoot setting
|
||||
* (nobodyMO) add filter settings to nodes
|
||||
|
||||
### 0.4.1 (2016-01-14)
|
||||
* (nobodyMO) Add --max-old-space-size=128 to support systems with low memory.
|
||||
* (nobodyMO) Add version 0.12.5 for node-red because it works.
|
||||
* (nobodyMO) Add ioBroker get node.
|
||||
* (nobodyMO) Set _maxListeners = 100 to suppress warnings in the log.
|
||||
|
||||
### 0.3.5 (2015-08-23)
|
||||
* (bluefox) fix error if many additional npm packets
|
||||
|
||||
### 0.3.4 (2015-08-10)
|
||||
* (bluefox) do not include node-red packages into global context
|
||||
|
||||
### 0.3.3 (2015-07-24)
|
||||
* (bluefox) enable node-red 0.11.x
|
||||
|
||||
### 0.3.2 (2015-06-29)
|
||||
* (bluefox) fix error with ioBroker nodes
|
||||
|
||||
### 0.3.1 (2015-06-28)
|
||||
* (bluefox) change link in admin to node-red web server
|
||||
|
||||
### 0.3.0 (2015-05-18)
|
||||
* (bluefox) add flag "stopBeforeUpdate"
|
||||
* (bluefox) store data in iobroker-data directory
|
||||
|
||||
### 0.2.2 (2015-05-17)
|
||||
* (bluefox) fix error with invalid additional npm package
|
||||
|
||||
### 0.2.1 (2015-05-17)
|
||||
* (bluefox) fix readme link
|
||||
|
||||
### 0.2.0 (2015-05-16)
|
||||
* (bluefox) allow install of additional npm and node-red packets
|
||||
|
||||
### 0.1.9 (2015-03-26)
|
||||
* (bluefox) fix first start
|
||||
|
||||
### 0.1.7 (2015-03-25)
|
||||
* (bluefox) remove warnings
|
||||
|
||||
### 0.1.6 (2015-03-18)
|
||||
* (bluefox) make node-red compatible with ioBroker again
|
||||
|
||||
### 0.1.5 (2015-02-12)
|
||||
* (bluefox) update node-red to 0.10.1
|
||||
* (bluefox) update select ID dialog
|
||||
|
||||
### 0.1.4 (2015-01-07)
|
||||
* (bluefox) create variables without need to be extra called with "__create__"
|
||||
|
||||
### 0.1.3 (2015-01-06)
|
||||
* (bluefox) make possible creation of variables
|
||||
|
||||
### 0.1.2 (2015-01-04)
|
||||
* (bluefox) print debug message by saving
|
||||
|
||||
### 0.1.1 (2015-01-03)
|
||||
* (bluefox) fix errors with utils.js
|
||||
|
||||
### 0.1.0 (2015-01-02)
|
||||
* (bluefox) enable npm install
|
||||
|
||||
### 0.0.8 (2014-12-20)
|
||||
* (bluefox) support signal stopInstance
|
||||
|
||||
### 0.0.7 (2014-12-14)
|
||||
* (bluefox) support of select ID dialogs
|
||||
|
||||
### 0.0.6 (2014-11-26)
|
||||
* (bluefox) use names like in mqtt: "adapter/instance/device/channel/state"
|
||||
* (bluefox) suport of "value" or "object" for input node
|
||||
|
||||
### 0.0.5 (2014-11-22)
|
||||
* (bluefox) support of new naming concept
|
||||
|
||||
### 0.0.4 (2014-11-05)
|
||||
* (bluefox) fix some errors
|
||||
|
||||
### 0.0.2 (2014-11-04)
|
||||
* (bluefox) use adapter.js to communicate with ioBroker
|
||||
|
||||
### 0.0.1 (2014-11-03)
|
||||
* (bluefox) initial commit
|
||||
|
||||
## Install
|
||||
|
||||
```node iobroker.js add node-red```
|
||||
|
||||
## Configuration
|
||||
|
||||
## License
|
||||
|
||||
Copyright 2014-2018 bluefox <dogafox@gmail.com>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
132
admin/index.html
Normal file
132
admin/index.html
Normal file
@ -0,0 +1,132 @@
|
||||
<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>
|
||||
|
||||
<style>
|
||||
.table_header {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
||||
.ip {
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<!-- you have to define 2 functions in the global scope: -->
|
||||
<script type="text/javascript">
|
||||
var devices = [];
|
||||
|
||||
function setValue(id, value, onChange) {
|
||||
var $value = $('#' + id + '.value');
|
||||
if ($value.attr('type') === 'checkbox') {
|
||||
$value.prop('checked', value).change(function() {
|
||||
onChange();
|
||||
});
|
||||
} else {
|
||||
$value.val(value).on('change', function() {
|
||||
onChange();
|
||||
}).on('keyup', function() {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// the function loadSettings has to exist ...
|
||||
function load(settings, onChange) {
|
||||
if (!settings) return;
|
||||
|
||||
devices = settings.devices || [];
|
||||
|
||||
if (settings.maxMemory === undefined) settings.maxMemory = 128;
|
||||
|
||||
for (var key in settings) {
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
setValue(key, settings[key], onChange);
|
||||
}
|
||||
}
|
||||
$('#update').button().click(function () {
|
||||
sendTo(null, 'update', null, function (error) {
|
||||
if (!error) {
|
||||
showMessage(_('Successfully updated'))
|
||||
} else {
|
||||
showMessage(_('Cannot update:') + _(error))
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var $libs = $('#libraries');
|
||||
|
||||
if (common.npmLibs){
|
||||
$libs.val(common.npmLibs.join(', '));
|
||||
}
|
||||
|
||||
$libs.on('change', function () {
|
||||
onChange();
|
||||
}).on('keyup', function () {
|
||||
$(this).trigger('change');
|
||||
});
|
||||
|
||||
getIsAdapterAlive(function (isAlive) {
|
||||
if (!isAlive) $('#update').button('disable');
|
||||
});
|
||||
onChange(false);
|
||||
}
|
||||
|
||||
function save(callback) {
|
||||
var obj = {};
|
||||
$('.value').each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.attr('type') === 'checkbox') {
|
||||
obj[$this.attr('id')] = $this.prop('checked');
|
||||
} else {
|
||||
obj[$this.attr('id')] = $this.val();
|
||||
}
|
||||
});
|
||||
|
||||
var libs = $('#libraries').val().split(',');
|
||||
var common = {npmLibs: []};
|
||||
for (var l = 0; l < libs.length; l++) {
|
||||
common.npmLibs.push(libs[l].trim());
|
||||
}
|
||||
|
||||
callback(obj, {localLink: 'http://%ip%:' + obj.port, npmLibs: common.npmLibs});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- you have to put your config page in a div with id adapter-container -->
|
||||
<div id="adapter-container">
|
||||
<table><tr>
|
||||
<td><img src="node-red.png" width="64" height="64"></td>
|
||||
<td style="padding-top: 20px;padding-left: 10px"><h3 class="translate">node-red adapter settings</h3></td>
|
||||
</tr></table>
|
||||
|
||||
<h4 class="translate">node-red settings</h4>
|
||||
<table>
|
||||
<tr><td><label class="translate" for="port">Web server port:</label></td><td colspan="2"><input class="value" id="port" type="number" min="1" max="65565"/></td></tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="libraries">Additional npm modules:</label></td><td><input id="libraries" class="value" style="width: 100%"/></td><td class="translate">Divided by comma</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="httpRoot">http root directory:</label></td><td><input id="httpRoot" class="value" style="width: 100%"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="valueConvert">Convert values to string:</label></td><td> <input class="value" id="valueConvert" type="checkbox" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label class="translate" for="maxMemory">Max allocated RAM:</label></td><td> <input class="value" id="maxMemory" type="number" min="32"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
<h4 class="translate">node-red update select dialog</h4>
|
||||
<button id="update" class="translateB">Update select dialog</button>
|
||||
</div>
|
||||
</html>
|
271
admin/index_m.html
Normal file
271
admin/index_m.html
Normal file
@ -0,0 +1,271 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Materialze style -->
|
||||
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
|
||||
|
||||
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
|
||||
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
|
||||
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||
<script type="text/javascript" src="words.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
// https://stackoverflow.com/questions/14733374/how-to-generate-md5-file-hash-on-javascript
|
||||
var MD5 = function (d) {
|
||||
var result = MM(V(Y(X(d), 8 * d.length)));
|
||||
return result.toLowerCase()
|
||||
};
|
||||
|
||||
function MM(d) {
|
||||
var f = '';
|
||||
for (var _, m = '0123456789ABCDEF', r = 0; r < d.length; r++) {
|
||||
_ = d.charCodeAt(r);
|
||||
f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
function X(d) {
|
||||
var _;
|
||||
for (_ = Array(d.length >> 2), m = 0; m < _.length; m++) _[m] = 0;
|
||||
for (m = 0; m < 8 * d.length; m += 8) _[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32;
|
||||
return _;
|
||||
}
|
||||
|
||||
function V(d) {
|
||||
var _;
|
||||
for (_ = '', m = 0; m < 32 * d.length; m += 8) {
|
||||
_ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255);
|
||||
}
|
||||
return _;
|
||||
}
|
||||
|
||||
function Y(d, _) {
|
||||
d[_ >> 5] |= 128 << _ % 32, d[14 + (_ + 64 >>> 9 << 4)] = _;
|
||||
for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) {
|
||||
var h = m, t = f, g = r, e = i;
|
||||
f = md5_ii(f = md5_ii(f = md5_ii(f = md5_ii(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_ff(f = md5_ff(f = md5_ff(f = md5_ff(f, r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = safe_add(m, h), f = safe_add(f, t), r = safe_add(r, g), i = safe_add(i, e);
|
||||
}
|
||||
return Array(m, f, r, i)
|
||||
}
|
||||
|
||||
function md5_cmn(d, _, m, f, r, i) {
|
||||
return safe_add(bit_rol(safe_add(safe_add(_, d), safe_add(f, i)), r), m)
|
||||
}
|
||||
|
||||
function md5_ff(d, _, m, f, r, i, n) {
|
||||
return md5_cmn(_ & m | ~_ & f, d, _, r, i, n)
|
||||
}
|
||||
|
||||
function md5_gg(d, _, m, f, r, i, n) {
|
||||
return md5_cmn(_ & f | m & ~f, d, _, r, i, n)
|
||||
}
|
||||
|
||||
function md5_hh(d, _, m, f, r, i, n) {
|
||||
return md5_cmn(_ ^ m ^ f, d, _, r, i, n)
|
||||
}
|
||||
|
||||
function md5_ii(d, _, m, f, r, i, n) {
|
||||
return md5_cmn(m ^ (_ | ~f), d, _, r, i, n)
|
||||
}
|
||||
|
||||
function safe_add(d, _) {
|
||||
var m = (65535 & d) + (65535 & _);
|
||||
return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m
|
||||
}
|
||||
|
||||
function bit_rol(d, _) {
|
||||
return d << _ | d >>> 32 - _
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<!-- you have to define 2 functions in the global scope: -->
|
||||
<script type="text/javascript">
|
||||
var oldPass = '';
|
||||
function setValue(id, value, onChange) {
|
||||
var $value = $('#' + id + '.value');
|
||||
if ($value.attr('type') === 'checkbox') {
|
||||
$value.prop('checked', value).change(function() {
|
||||
onChange();
|
||||
});
|
||||
} else {
|
||||
$value.val(value).on('change', function() {
|
||||
onChange();
|
||||
}).on('keyup', function() {
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
function chips2list(selector) {
|
||||
var data = $(selector).chips('getData');
|
||||
var text = [];
|
||||
for (var lib = 0; lib < data.length; lib++) {
|
||||
text.push(data[lib].tag);
|
||||
}
|
||||
return text.join(' ');
|
||||
}
|
||||
function list2chips(selector, list, onChange) {
|
||||
var chips = list.split(/[,;\s]+/);
|
||||
var data = [];
|
||||
for (var c = 0; c < chips.length; c++) {
|
||||
if (chips[c] && chips[c].trim()) {
|
||||
data.push({tag: chips[c].trim()});
|
||||
}
|
||||
}
|
||||
$(selector).chips({
|
||||
data: data,
|
||||
placeholder: _('Module names'),
|
||||
secondaryPlaceholder: _('Add module'),
|
||||
onChipAdd: onChange,
|
||||
onChipDelete: onChange
|
||||
});
|
||||
}
|
||||
|
||||
// the function loadSettings has to exist ...
|
||||
function load(settings, onChange) {
|
||||
if (!settings) return;
|
||||
|
||||
if (settings.user === undefined) {
|
||||
settings.user = '';
|
||||
}
|
||||
if (settings.pass === undefined) {
|
||||
settings.pass = '';
|
||||
}
|
||||
if (settings.bind === undefined) {
|
||||
settings.bind = '0.0.0.0';
|
||||
}
|
||||
if (settings.maxMemory === undefined) {
|
||||
settings.maxMemory = 128;
|
||||
}
|
||||
oldPass = settings.pass;
|
||||
if (settings.pass) {
|
||||
settings.pass = '__pass__';
|
||||
}
|
||||
|
||||
for (var key in settings) {
|
||||
if (settings.hasOwnProperty(key)) {
|
||||
setValue(key, settings[key], onChange);
|
||||
}
|
||||
}
|
||||
$('#update').click(function () {
|
||||
sendTo(null, 'update', null, function (error) {
|
||||
if (!error) {
|
||||
showMessage(_('Successfully updated'))
|
||||
} else {
|
||||
showMessage(_('Cannot update:') + _(error))
|
||||
}
|
||||
});
|
||||
});
|
||||
getIPs(function (ips) {
|
||||
for (var i = 0; i < ips.length; i++) {
|
||||
$('#bind').append('<option value="' + ips[i].address + '">' + ips[i].name + '</option>');
|
||||
}
|
||||
$('#bind.value').val(settings.bind).select();
|
||||
});
|
||||
|
||||
if (typeof common.npmLibs === 'object') {
|
||||
common.npmLibs = common.npmLibs.join(';');
|
||||
}
|
||||
|
||||
list2chips('.libraries', common.npmLibs || '', onChange);
|
||||
|
||||
getIsAdapterAlive(function (isAlive) {
|
||||
if (!isAlive) {
|
||||
$('#update').addClass('disabled');
|
||||
}
|
||||
});
|
||||
onChange(false);
|
||||
}
|
||||
|
||||
function save(callback) {
|
||||
var obj = {};
|
||||
$('.value').each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.attr('type') === 'checkbox') {
|
||||
obj[$this.attr('id')] = $this.prop('checked');
|
||||
} else {
|
||||
obj[$this.attr('id')] = $this.val();
|
||||
}
|
||||
});
|
||||
|
||||
if (obj.pass) {
|
||||
if (obj.pass !== '__pass__') {
|
||||
obj.pass = MD5(obj.pass);
|
||||
} else {
|
||||
obj.pass = oldPass;
|
||||
}
|
||||
}
|
||||
|
||||
common.npmLibs = chips2list('.libraries');
|
||||
|
||||
callback(obj, {localLink: 'http://%ip%:' + obj.port, npmLibs: common.npmLibs});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="m adapter-container">
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<div class="row">
|
||||
<div class="col s6">
|
||||
<img src="node-red.png" class="logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12 m8 l6">
|
||||
<label class="translate">Additional npm modules:</label>
|
||||
<div class="chips libraries"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12 m8 l4">
|
||||
<select class="value" id="bind"></select>
|
||||
<label class="translate" for="bind">IP:</label>
|
||||
</div>
|
||||
<div class="input-field col s12 m4 l2">
|
||||
<input class="value" id="port" type="number" min="1" max="65565"/>
|
||||
<label class="translate" for="port">Web server port:</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12 m4 l3">
|
||||
<input id="user" class="value" type="text"/>
|
||||
<label class="translate" for="user">User</label>
|
||||
<span class="translate">Leave blank to disable basic authentication</span>
|
||||
</div>
|
||||
<div class="input-field col s12 m4 l3">
|
||||
<input id="pass" class="value" type="password"/>
|
||||
<label class="translate" for="pass">Password</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12 m4 l3">
|
||||
<input id="httpRoot" class="value" type="text" style="width: 100%"/>
|
||||
<label class="translate" for="httpRoot">http root directory:</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="input-field col s12 m4 l3">
|
||||
<input class="value" id="valueConvert" type="checkbox" />
|
||||
<label class="translate" for="valueConvert">Convert values to string:</label>
|
||||
</div>
|
||||
<div class="input-field col s12 m4 l3">
|
||||
<input class="value" id="maxMemory" type="number" min="32"/>
|
||||
<label class="translate" for="maxMemory">Max allocated RAM:</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<a id="update" class="waves-effect waves-light btn"><i class="material-icons right">cloud</i><span class="translate">Update select dialog</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
admin/node-red.png
Normal file
BIN
admin/node-red.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
20
admin/words.js
Normal file
20
admin/words.js
Normal file
@ -0,0 +1,20 @@
|
||||
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
|
||||
/*global systemDictionary:true */
|
||||
'use strict';
|
||||
|
||||
systemDictionary = {
|
||||
"node-red settings": { "en": "node-red settings", "de": "node-red Einstellungen", "ru": "node-red Настройки", "pt": "configurações de nó vermelho", "nl": "node-red instellingen", "fr": "paramètres de node-red", "it": "impostazioni del node-red", "es": "configuración de node-red", "pl": "ustawienia node-red"},
|
||||
"Web server port:": { "en": "Web server port", "de": "Web-Server-Port", "ru": "Порт веб сервера", "pt": "Porta do servidor da Web", "nl": "Webserverpoort", "fr": "Port du serveur Web", "it": "Porta del server Web", "es": "Puerto del servidor web", "pl": "Port serwera internetowego"},
|
||||
"node-red update select dialog": { "en": "node-red update select dialog", "de": "node-red SelectID Dialog aktualisieren", "ru": "Обновить переменные в диалоге node-red", "pt": "caixa de diálogo de seleção de atualização do nó-vermelho", "nl": "update select dialoogvenster", "fr": "boîte de dialogue de sélection de mise à jour de noeud", "it": "finestra di dialogo di selezione aggiornamento node-red", "es": "diálogo de selección de actualización node-red", "pl": "okno dialogowe aktualizacji uaktualnienia węzła"},
|
||||
"Update select dialog": { "en": "Update select dialog", "de": "Update Dialog um ID zu selektieren", "ru": "Обновить переменные в диалоге выбора объектов", "pt": "Atualizar caixa de diálogo de seleção", "nl": "Update select dialoogvenster", "fr": "Mettre à jour le dialogue", "it": "Aggiorna la finestra di selezione", "es": "Actualizar diálogo de selección", "pl": "Zaktualizuj wybrane okno dialogowe"},
|
||||
"Divided by comma": { "en": "Divided by comma", "de": "Getrennt mit Komma", "ru": "Через запятую", "pt": "Dividido por vírgula", "nl": "Verdeeld door een komma", "fr": "Divisé par une virgule", "it": "Diviso in virgola", "es": "Dividido por coma", "pl": "Podzielone przecinkiem"},
|
||||
"Additional npm modules:": { "en": "Additional npm modules", "de": "Zusätzliche NPM-Module", "ru": "Дополнительные NPM Модули", "pt": "Módulos npm adicionais", "nl": "Extra npm-modules", "fr": "Modules NPM supplémentaires", "it": "Ulteriori moduli npm", "es": "Módulos npm adicionales", "pl": "Dodatkowe moduły npm"},
|
||||
"http root directory:": { "en": "http root directory", "de": "http Stammpfad", "ru": "http root directory", "pt": "diretório raiz http", "nl": "http root directory", "fr": "répertoire racine http", "it": "directory root http", "es": "directorio raíz http", "pl": "główny katalog http"},
|
||||
"Convert values to string:": { "en": "Convert ioBroker values to string", "de": "ioBroker-Werte in String konvertieren:", "ru": "Конвертировать значения из ioBroker в строки", "pt": "Converter valores de ioBroker em string", "nl": "IoBroker-waarden converteren naar tekenreeks", "fr": "Convertir les valeurs de ioBroker en chaîne", "it": "Converti i valori di ioBroker in stringa", "es": "Convierta los valores de ioBroker en una cadena", "pl": "Konwertuj wartości ioBroker na ciąg"},
|
||||
"Add module": { "en": "Add module", "de": "Modul hinzufügen", "ru": "Добавить модуль", "pt": "Adicionar módulo", "nl": "Module toevoegen", "fr": "Ajouter un module", "it": "Aggiungi modulo", "es": "Agregar módulo", "pl": "Dodaj moduł"},
|
||||
"Max allocated RAM:": { "en": "Max allocated RAM", "de": "Max zugewiesener RAM", "ru": "Выделено RAM", "pt": "RAM alocada máxima", "nl": "Max toegewezen RAM", "fr": "Max allouée RAM", "it": "RAM allocata massima", "es": "RAM máxima asignada", "pl": "Maksymalna przydzielona pamięć RAM"},
|
||||
"Module names": { "en": "Module names", "de": "Modulnamen", "ru": "Имена модулей", "pt": "Nomes de módulos", "nl": "Module namen", "fr": "Noms de modules", "it": "Nomi dei moduli", "es": "Nombres de módulos", "pl": "Nazwy modułów"},
|
||||
"User": { "en": "User", "de": "Benutzer", "ru": "Имя пользователя", "pt": "Do utilizador", "nl": "Gebruiker", "fr": "Utilisateur", "it": "Utente", "es": "Usuario", "pl": "Użytkownik"},
|
||||
"Leave blank to disable basic authentication": { "en": "Leave blank to disable basic authentication", "de": "Leer lassen, um die Basic-Authentication zu deaktivieren", "ru": "Оставить пустым, чтобы отключить basic authentication", "pt": "Deixe em branco para desativar a autenticação básica", "nl": "Laat dit leeg om basisverificatie uit te schakelen", "fr": "Laissez vide pour désactiver l'authentification de base", "it": "Lascia vuoto per disabilitare l'autenticazione di base", "es": "Dejar en blanco para deshabilitar la autenticación básica", "pl": "Pozostaw puste, aby wyłączyć podstawowe uwierzytelnianie"},
|
||||
"Password": { "en": "Password", "de": "Passwort", "ru": "Пароль", "pt": "Senha", "nl": "Wachtwoord", "fr": "Mot de passe", "it": "Parola d'ordine", "es": "Contraseña", "pl": "Hasło"},
|
||||
};
|
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'
|
400
gulpfile.js
Normal file
400
gulpfile.js
Normal file
@ -0,0 +1,400 @@
|
||||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const fs = require('fs');
|
||||
const pkg = require('./package.json');
|
||||
const iopackage = require('./io-package.json');
|
||||
const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
|
||||
/*const appName = getAppName();
|
||||
|
||||
function getAppName() {
|
||||
const parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 1].split('.')[0].toLowerCase();
|
||||
}
|
||||
*/
|
||||
const fileName = 'words.js';
|
||||
const languages = {
|
||||
en: {},
|
||||
de: {},
|
||||
ru: {},
|
||||
pt: {},
|
||||
nl: {},
|
||||
fr: {},
|
||||
it: {},
|
||||
es: {},
|
||||
pl: {}
|
||||
};
|
||||
|
||||
function lang2data(lang, isFlat) {
|
||||
let str = isFlat ? '' : '{\n';
|
||||
let count = 0;
|
||||
for (const w in lang) {
|
||||
if (lang.hasOwnProperty(w)) {
|
||||
count++;
|
||||
if (isFlat) {
|
||||
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
|
||||
} else {
|
||||
const key = ' "' + w.replace(/"/g, '\\"') + '": ';
|
||||
str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!count) return isFlat ? '' : '{\n}';
|
||||
if (isFlat) {
|
||||
return str;
|
||||
} else {
|
||||
return str.substring(0, str.length - 2) + '\n}';
|
||||
}
|
||||
}
|
||||
|
||||
function readWordJs(src) {
|
||||
try {
|
||||
let words;
|
||||
if (fs.existsSync(src + 'js/' + fileName)) {
|
||||
words = fs.readFileSync(src + 'js/' + fileName).toString();
|
||||
} else {
|
||||
words = fs.readFileSync(src + fileName).toString();
|
||||
}
|
||||
|
||||
const lines = words.split(/\r\n|\r|\n/g);
|
||||
let i = 0;
|
||||
while (!lines[i].match(/^systemDictionary = {/)) {
|
||||
i++;
|
||||
}
|
||||
lines.splice(0, i);
|
||||
|
||||
// remove last empty lines
|
||||
i = lines.length - 1;
|
||||
while (!lines[i]) {
|
||||
i--;
|
||||
}
|
||||
if (i < lines.length - 1) {
|
||||
lines.splice(i + 1);
|
||||
}
|
||||
|
||||
lines[0] = lines[0].replace('systemDictionary = ', '');
|
||||
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
|
||||
words = lines.join('\n');
|
||||
const resultFunc = new Function('return ' + words + ';');
|
||||
|
||||
return resultFunc();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function padRight(text, totalLength) {
|
||||
return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : '');
|
||||
}
|
||||
function writeWordJs(data, src) {
|
||||
let text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
|
||||
text += '/*global systemDictionary:true */\n';
|
||||
text += '\'use strict\';\n\n';
|
||||
|
||||
text += 'systemDictionary = {\n';
|
||||
for (const word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
|
||||
let line = '';
|
||||
for (const lang in data[word]) {
|
||||
if (data[word].hasOwnProperty(lang)) {
|
||||
line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' ';
|
||||
}
|
||||
}
|
||||
if (line) {
|
||||
line = line.trim();
|
||||
line = line.substring(0, line.length - 1);
|
||||
}
|
||||
text += line + '},\n';
|
||||
}
|
||||
}
|
||||
text += '};';
|
||||
|
||||
if (fs.existsSync(src + 'js/' + fileName)) {
|
||||
fs.writeFileSync(src + 'js/' + fileName, text);
|
||||
} else {
|
||||
fs.writeFileSync(src + '' + fileName, text);
|
||||
}
|
||||
}
|
||||
|
||||
const EMPTY = '';
|
||||
|
||||
function words2languages(src) {
|
||||
const langs = Object.assign({}, languages);
|
||||
const data = readWordJs(src);
|
||||
if (data) {
|
||||
for (const word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
for (const lang in data[word]) {
|
||||
if (data[word].hasOwnProperty(lang)) {
|
||||
langs[lang][word] = data[word][lang];
|
||||
// pre-fill all other languages
|
||||
for (const j in langs) {
|
||||
if (langs.hasOwnProperty(j)) {
|
||||
langs[j][word] = langs[j][word] || EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(src + 'i18n/')) {
|
||||
fs.mkdirSync(src + 'i18n/');
|
||||
}
|
||||
for (const l in langs) {
|
||||
if (!langs.hasOwnProperty(l)) continue;
|
||||
const keys = Object.keys(langs[l]);
|
||||
//keys.sort();
|
||||
const obj = {};
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
obj[keys[k]] = langs[l][keys[k]];
|
||||
}
|
||||
if (!fs.existsSync(src + 'i18n/' + l)) {
|
||||
fs.mkdirSync(src + 'i18n/' + l);
|
||||
}
|
||||
|
||||
fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj));
|
||||
}
|
||||
} else {
|
||||
console.error('Cannot read or parse ' + fileName);
|
||||
}
|
||||
}
|
||||
function words2languagesFlat(src) {
|
||||
const langs = Object.assign({}, languages);
|
||||
const data = readWordJs(src);
|
||||
if (data) {
|
||||
for (const word in data) {
|
||||
if (data.hasOwnProperty(word)) {
|
||||
for (const lang in data[word]) {
|
||||
if (data[word].hasOwnProperty(lang)) {
|
||||
langs[lang][word] = data[word][lang];
|
||||
// pre-fill all other languages
|
||||
for (const j in langs) {
|
||||
if (langs.hasOwnProperty(j)) {
|
||||
langs[j][word] = langs[j][word] || EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const keys = Object.keys(langs.en);
|
||||
//keys.sort();
|
||||
for (const l in langs) {
|
||||
if (!langs.hasOwnProperty(l)) continue;
|
||||
const obj = {};
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
obj[keys[k]] = langs[l][keys[k]];
|
||||
}
|
||||
langs[l] = obj;
|
||||
}
|
||||
if (!fs.existsSync(src + 'i18n/')) {
|
||||
fs.mkdirSync(src + 'i18n/');
|
||||
}
|
||||
for (const ll in langs) {
|
||||
if (!langs.hasOwnProperty(ll)) continue;
|
||||
if (!fs.existsSync(src + 'i18n/' + ll)) {
|
||||
fs.mkdirSync(src + 'i18n/' + ll);
|
||||
}
|
||||
|
||||
fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en));
|
||||
}
|
||||
fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n'));
|
||||
} else {
|
||||
console.error('Cannot read or parse ' + fileName);
|
||||
}
|
||||
}
|
||||
function languagesFlat2words(src) {
|
||||
const dirs = fs.readdirSync(src + 'i18n/');
|
||||
const langs = {};
|
||||
const bigOne = {};
|
||||
const order = Object.keys(languages);
|
||||
dirs.sort(function (a, b) {
|
||||
const posA = order.indexOf(a);
|
||||
const posB = order.indexOf(b);
|
||||
if (posA === -1 && posB === -1) {
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
} else if (posA === -1) {
|
||||
return -1;
|
||||
} else if (posB === -1) {
|
||||
return 1;
|
||||
} else {
|
||||
if (posA > posB) return 1;
|
||||
if (posA < posB) return -1;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
|
||||
|
||||
for (let l = 0; l < dirs.length; l++) {
|
||||
if (dirs[l] === 'flat.txt') continue;
|
||||
const lang = dirs[l];
|
||||
const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
|
||||
langs[lang] = {};
|
||||
keys.forEach(function (word, i) {
|
||||
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
|
||||
});
|
||||
|
||||
const words = langs[lang];
|
||||
for (const word in words) {
|
||||
if (words.hasOwnProperty(word)) {
|
||||
bigOne[word] = bigOne[word] || {};
|
||||
if (words[word] !== EMPTY) {
|
||||
bigOne[word][lang] = words[word];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// read actual words.js
|
||||
const aWords = readWordJs();
|
||||
|
||||
const temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
|
||||
if (aWords) {
|
||||
// Merge words together
|
||||
for (const w in aWords) {
|
||||
if (aWords.hasOwnProperty(w)) {
|
||||
if (!bigOne[w]) {
|
||||
console.warn('Take from actual words.js: ' + w);
|
||||
bigOne[w] = aWords[w]
|
||||
}
|
||||
dirs.forEach(function (lang) {
|
||||
if (temporaryIgnore.indexOf(lang) !== -1) return;
|
||||
if (!bigOne[w][lang]) {
|
||||
console.warn('Missing "' + lang + '": ' + w);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
writeWordJs(bigOne, src);
|
||||
}
|
||||
function languages2words(src) {
|
||||
const dirs = fs.readdirSync(src + 'i18n/');
|
||||
const langs = {};
|
||||
const bigOne = {};
|
||||
const order = Object.keys(languages);
|
||||
dirs.sort(function (a, b) {
|
||||
const posA = order.indexOf(a);
|
||||
const posB = order.indexOf(b);
|
||||
if (posA === -1 && posB === -1) {
|
||||
if (a > b) return 1;
|
||||
if (a < b) return -1;
|
||||
return 0;
|
||||
} else if (posA === -1) {
|
||||
return -1;
|
||||
} else if (posB === -1) {
|
||||
return 1;
|
||||
} else {
|
||||
if (posA > posB) return 1;
|
||||
if (posA < posB) return -1;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
for (let l = 0; l < dirs.length; l++) {
|
||||
if (dirs[l] === 'flat.txt') continue;
|
||||
const lang = dirs[l];
|
||||
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
|
||||
langs[lang] = JSON.parse(langs[lang]);
|
||||
const words = langs[lang];
|
||||
for (const word in words) {
|
||||
if (words.hasOwnProperty(word)) {
|
||||
bigOne[word] = bigOne[word] || {};
|
||||
if (words[word] !== EMPTY) {
|
||||
bigOne[word][lang] = words[word];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// read actual words.js
|
||||
const aWords = readWordJs();
|
||||
|
||||
const temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
|
||||
if (aWords) {
|
||||
// Merge words together
|
||||
for (const w in aWords) {
|
||||
if (aWords.hasOwnProperty(w)) {
|
||||
if (!bigOne[w]) {
|
||||
console.warn('Take from actual words.js: ' + w);
|
||||
bigOne[w] = aWords[w]
|
||||
}
|
||||
dirs.forEach(function (lang) {
|
||||
if (temporaryIgnore.indexOf(lang) !== -1) return;
|
||||
if (!bigOne[w][lang]) {
|
||||
console.warn('Missing "' + lang + '": ' + w);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
writeWordJs(bigOne, src);
|
||||
}
|
||||
|
||||
gulp.task('adminWords2languages', function (done) {
|
||||
words2languages('./admin/');
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('adminWords2languagesFlat', function (done) {
|
||||
words2languagesFlat('./admin/');
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('adminLanguagesFlat2words', function (done) {
|
||||
languagesFlat2words('./admin/');
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('adminLanguages2words', function (done) {
|
||||
languages2words('./admin/');
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
gulp.task('updatePackages', function (done) {
|
||||
iopackage.common.version = pkg.version;
|
||||
iopackage.common.news = iopackage.common.news || {};
|
||||
if (!iopackage.common.news[pkg.version]) {
|
||||
const news = iopackage.common.news;
|
||||
const newNews = {};
|
||||
|
||||
newNews[pkg.version] = {
|
||||
en: 'news',
|
||||
de: 'neues',
|
||||
ru: 'новое'
|
||||
};
|
||||
iopackage.common.news = Object.assign(newNews, news);
|
||||
}
|
||||
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('updateReadme', function (done) {
|
||||
const readme = fs.readFileSync('README.md').toString();
|
||||
const pos = readme.indexOf('## Changelog\n');
|
||||
if (pos !== -1) {
|
||||
const readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
|
||||
const readmeEnd = readme.substring(pos + '## Changelog\n'.length);
|
||||
|
||||
if (readme.indexOf(version) === -1) {
|
||||
const timestamp = new Date();
|
||||
const date = timestamp.getFullYear() + '-' +
|
||||
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
|
||||
('0' + (timestamp.getDate()).toString(10)).slice(-2);
|
||||
|
||||
let news = '';
|
||||
if (iopackage.common.news && iopackage.common.news[pkg.version]) {
|
||||
news += '* ' + iopackage.common.news[pkg.version].en;
|
||||
}
|
||||
|
||||
fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task('default', ['updatePackages', 'updateReadme']);
|
138
io-package.json
Normal file
138
io-package.json
Normal file
@ -0,0 +1,138 @@
|
||||
{
|
||||
"common": {
|
||||
"name": "node-red",
|
||||
"version": "1.7.1",
|
||||
"title": "node-red",
|
||||
"news": {
|
||||
"1.7.1": {
|
||||
"en": "Used newer version of node-red 0.19.4\nBasic authentication was added",
|
||||
"de": "Verwendete neuere Version von node-red 0.19.4\nStandardauthentifizierung wurde hinzugefügt",
|
||||
"ru": "Используется новая версия node-red 0.19.4\nДобавлена базовая аутентификация",
|
||||
"pt": "Usado versão mais recente do nó vermelho 0.19.4\nAutenticação básica foi adicionada",
|
||||
"nl": "Gebruikte nieuwere versie van knoop-rood 0.19.4\nBasisverificatie is toegevoegd",
|
||||
"fr": "Version plus récente de node-red 0.19.4\nL'authentification de base a été ajoutée",
|
||||
"it": "Utilizzata la versione più recente di node-red 0.19.4\nÈ stata aggiunta l'autenticazione di base",
|
||||
"es": "Versión más nueva usada de node-red 0.19.4\nAutenticación básica fue agregada",
|
||||
"pl": "Używana nowsza wersja węzła-czerwonego 0.19.4\nDodano uwierzytelnianie podstawowe"
|
||||
},
|
||||
"1.7.0": {
|
||||
"en": "Used newer version of node-red 0.19.1",
|
||||
"de": "Benutzte neuere Version von node-red 0.19.1",
|
||||
"ru": "Используется новая версия node-red 0.19.1",
|
||||
"pt": "Usado versão mais recente do nó vermelho 0.19.1",
|
||||
"nl": "Gebruikte nieuwere versie van knoop-rood 0.19.1",
|
||||
"fr": "Version plus récente de node-red 0.19.1",
|
||||
"it": "Utilizzata la versione più recente di node-red 0.19.1",
|
||||
"es": "Versión más nueva usada de node-red 0.19.1",
|
||||
"pl": "Używana nowsza wersja węzła-czerwonego 0.19.1"
|
||||
},
|
||||
"1.6.0": {
|
||||
"en": "newer version of node-red 0.18.7 used\nAdmin3 dialog implemented\nRAM settings were added",
|
||||
"de": "neuere Version von node-red 0.18.7 verwendet\nAdmin3-Dialog implementiert\nRAM-Einstellungen wurden hinzugefügt",
|
||||
"ru": "более новая версия node-red 0.18.7 используется\nДиалог Admin3 реализован\nДобавлены настройки RAM",
|
||||
"pt": "versão mais recente do node-red 0.18.7 usado\nDiálogo Admin3 implementado\nConfigurações de RAM foram adicionadas",
|
||||
"nl": "nieuwere versie van knooppunt-rood 0.18.7 gebruikt\nAdmin3-dialoogvenster geïmplementeerd\nRAM-instellingen zijn toegevoegd",
|
||||
"fr": "version plus récente de node-red 0.18.7 d'occasion\nBoîte de dialogue Admin3 implémentée\nParamètres RAM ont été ajoutés",
|
||||
"it": "versione più recente di node-red 0.18.7 usata\nFinestra di dialogo Admin3 implementata\nSono state aggiunte le impostazioni della RAM",
|
||||
"es": "nueva versión de node-red 0.18.7 usada\nDiálogo Admin3 implementado\nSe agregaron configuraciones de RAM",
|
||||
"pl": "zastosowano nowszą wersję węzła-czerwonego 0.18.7\nZaimplementowano okno Admin3\nDodano ustawienia pamięci RAM"
|
||||
},
|
||||
"1.5.1": {
|
||||
"en": "queue set state requests till ioBroker connection has been initialized",
|
||||
"de": "Verzögern von ioBroker-Schreibaktionen bis Verbindung zu ioBroker initialisiert wurde",
|
||||
"ru": "queue set state requests till ioBroker connection has been initialized"
|
||||
},
|
||||
"1.5.0": {
|
||||
"en": "use newer version of node-red 0.18.2",
|
||||
"de": "Neue Version von node-red 0.18.2",
|
||||
"ru": "Новая версия node-red 0.18.2"
|
||||
},
|
||||
"1.4.1": {
|
||||
"en": "fix blank topic support",
|
||||
"de": "Korrigiere lehre Topics",
|
||||
"ru": "Поправлены пустые топики"
|
||||
},
|
||||
"1.4.0": {
|
||||
"en": "use newer version of node-red 0.17.5",
|
||||
"de": "Neue Version von node-red 0.17.5",
|
||||
"ru": "Новая версия node-red 0.17.5"
|
||||
},
|
||||
"1.3.0": {
|
||||
"en": "Update the select ID dialog",
|
||||
"de": "Update ID Auswahl",
|
||||
"ru": "Обновлён диалог выбора объектов"
|
||||
},
|
||||
"1.2.0": {
|
||||
"en": "add node-red-contrib-os, node-red-dashboard, node-red-contrib-aggregator by default",
|
||||
"de": "Hinzugefügt: node-red-contrib-os, node-red-dashboard, node-red-contrib-aggregator",
|
||||
"ru": "Добавлены: node-red-contrib-os, node-red-dashboard, node-red-contrib-aggregator"
|
||||
},
|
||||
"1.1.6": {
|
||||
"en": "use newer version of node-red 0.16.2",
|
||||
"de": "Neue Version von node-red 0.16.2",
|
||||
"ru": "Новая версия node-red 0.16.2"
|
||||
}
|
||||
},
|
||||
"desc": {
|
||||
"en": "This adapter uses node-red as a service. No additional node-red instance required.",
|
||||
"de": "Adapter benutzt node-red als Servcie. Kein zusätzliches node-red Programm nötig.",
|
||||
"ru": "Драйвер создает node-red сервер и позволяет общаться с ним.",
|
||||
"pt": "Este adaptador usa node-red como um serviço. Nenhuma instância node-red adicional é necessária.",
|
||||
"nl": "Deze adapter gebruikt node-red als een service. Geen extra node-red exemplaar vereist.",
|
||||
"fr": "Cet adaptateur utilise node-red en tant que service. Aucune instance node-red supplémentaire requise.",
|
||||
"it": "Questo adattatore utilizza node-red come servizio. Nessuna istanza aggiuntiva node-red richiesta.",
|
||||
"es": "Este adaptador usa node-red como un servicio. No se requiere ninguna instancia adicional de node-red.",
|
||||
"pl": "Ten adapter używa node-red jako usługi. Żadna dodatkowa instancja node-red nie jest wymagana."
|
||||
},
|
||||
"authors": [
|
||||
"bluefox <dogafox@gmail.com>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"platform": "Javascript/Node.js",
|
||||
"mode": "daemon",
|
||||
"messagebox": true,
|
||||
"loglevel": "info",
|
||||
"icon": "node-red.png",
|
||||
"keywords": [
|
||||
"node-red",
|
||||
"logic",
|
||||
"script"
|
||||
],
|
||||
"extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.node-red/master/admin/node-red.png",
|
||||
"localLink": "http://%ip%:%port%%httpRoot%",
|
||||
"enabled": true,
|
||||
"singletonHost": true,
|
||||
"supportStopInstance": true,
|
||||
"unsafePerm": true,
|
||||
"materialize": true,
|
||||
"type": "logic",
|
||||
"readme": "https://github.com/ioBroker/ioBroker.node-red/blob/master/README.md",
|
||||
"stopBeforeUpdate": true,
|
||||
"adminTab": {
|
||||
"link": "http://%ip%:%port%%httpRoot%",
|
||||
"fa-icon": "settings_input_composite"
|
||||
}
|
||||
},
|
||||
"native": {
|
||||
"bind": "0.0.0.0",
|
||||
"port": 1880,
|
||||
"httpRoot": "/",
|
||||
"npmLibs": "",
|
||||
"valueConvert": true,
|
||||
"maxMemory": 128,
|
||||
"user": "",
|
||||
"pass": ""
|
||||
},
|
||||
"objects": [],
|
||||
"instanceObjects": [
|
||||
{
|
||||
"_id": "",
|
||||
"type": "channel",
|
||||
"common": {
|
||||
"name": "States created by node-red.%INSTANCE%",
|
||||
"role": "info"
|
||||
},
|
||||
"native": {}
|
||||
}
|
||||
]
|
||||
}
|
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;
|
443
main.js
Normal file
443
main.js
Normal file
@ -0,0 +1,443 @@
|
||||
/**
|
||||
*
|
||||
* ioBroker node-red Adapter
|
||||
*
|
||||
* (c) 2014 bluefox<bluefox@ccu.io>
|
||||
*
|
||||
* Apache 2.0 License
|
||||
*
|
||||
*/
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||
const adapter = utils.Adapter({
|
||||
name: 'node-red',
|
||||
systemConfig: true, // get the system configuration as systemConfig parameter of adapter
|
||||
unload: unloadRed
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const Notify = require('fs.notify');
|
||||
const attempts = {};
|
||||
const additional = [];
|
||||
let secret;
|
||||
|
||||
let userdataDir = __dirname + '/userdata/';
|
||||
|
||||
adapter.on('message', function (obj) {
|
||||
if (obj) processMessage(obj);
|
||||
processMessages();
|
||||
});
|
||||
|
||||
adapter.on('ready', function () {
|
||||
installLibraries(main);
|
||||
});
|
||||
|
||||
function installNpm(npmLib, callback) {
|
||||
const path = __dirname;
|
||||
if (typeof npmLib === 'function') {
|
||||
callback = npmLib;
|
||||
npmLib = undefined;
|
||||
}
|
||||
|
||||
const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '" --save';
|
||||
adapter.log.info(cmd + ' (System call)');
|
||||
// Install node modules as system call
|
||||
|
||||
// System call used for update of js-controller itself,
|
||||
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
|
||||
const exec = require('child_process').exec;
|
||||
const child = exec(cmd);
|
||||
child.stdout.on('data', function(buf) {
|
||||
adapter.log.info(buf.toString('utf8'));
|
||||
});
|
||||
child.stderr.on('data', function(buf) {
|
||||
adapter.log.error(buf.toString('utf8'));
|
||||
});
|
||||
|
||||
child.on('exit', function (code, signal) {
|
||||
if (code) {
|
||||
adapter.log.error('Cannot install ' + npmLib + ': ' + code);
|
||||
}
|
||||
// command succeeded
|
||||
if (callback) callback(npmLib);
|
||||
});
|
||||
}
|
||||
|
||||
function installLibraries(callback) {
|
||||
let allInstalled = true;
|
||||
if (typeof adapter.common.npmLibs === 'string') {
|
||||
adapter.common.npmLibs = adapter.common.npmLibs.split(/[,;\s]+/);
|
||||
}
|
||||
|
||||
if (adapter.common && adapter.common.npmLibs) {
|
||||
for (let lib = 0; lib < adapter.common.npmLibs.length; lib++) {
|
||||
if (adapter.common.npmLibs[lib] && adapter.common.npmLibs[lib].trim()) {
|
||||
adapter.common.npmLibs[lib] = adapter.common.npmLibs[lib].trim();
|
||||
if (!fs.existsSync(__dirname + '/node_modules/' + adapter.common.npmLibs[lib] + '/package.json')) {
|
||||
|
||||
if (!attempts[adapter.common.npmLibs[lib]]) {
|
||||
attempts[adapter.common.npmLibs[lib]] = 1;
|
||||
} else {
|
||||
attempts[adapter.common.npmLibs[lib]]++;
|
||||
}
|
||||
if (attempts[adapter.common.npmLibs[lib]] > 3) {
|
||||
adapter.log.error('Cannot install npm packet: ' + adapter.common.npmLibs[lib]);
|
||||
continue;
|
||||
}
|
||||
|
||||
installNpm(adapter.common.npmLibs[lib], function () {
|
||||
installLibraries(callback);
|
||||
});
|
||||
allInstalled = false;
|
||||
break;
|
||||
} else {
|
||||
if (additional.indexOf(adapter.common.npmLibs[lib]) === -1) additional.push(adapter.common.npmLibs[lib]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allInstalled) callback();
|
||||
}
|
||||
|
||||
// is called if a subscribed state changes
|
||||
//adapter.on('stateChange', function (id, state) {
|
||||
//});
|
||||
function unloadRed (callback) {
|
||||
// Stop node-red
|
||||
stopping = true;
|
||||
if (redProcess) {
|
||||
adapter.log.info("kill node-red task");
|
||||
redProcess.kill();
|
||||
redProcess = null;
|
||||
}
|
||||
if (notificationsCreds) notificationsCreds.close();
|
||||
if (notificationsFlows) notificationsFlows.close();
|
||||
|
||||
if (callback) callback();
|
||||
}
|
||||
|
||||
function processMessage(obj) {
|
||||
if (!obj || !obj.command) return;
|
||||
switch (obj.command) {
|
||||
case 'update':
|
||||
writeStateList(error => {
|
||||
if (typeof obj.callback === 'function') adapter.sendTo(obj.from, obj.command, error, obj.callback);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'stopInstance':
|
||||
unloadRed();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function processMessages() {
|
||||
adapter.getMessage((err, obj) => {
|
||||
if (obj) {
|
||||
processMessage(obj.command, obj.message);
|
||||
processMessages();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeRedPath() {
|
||||
let nodeRed = __dirname + '/node_modules/node-red';
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
nodeRed = path.normalize(__dirname + '/../node_modules/node-red');
|
||||
if (!fs.existsSync(nodeRed)) {
|
||||
adapter && adapter.log && adapter.log.error('Cannot find node-red packet!');
|
||||
throw new Error('Cannot find node-red packet!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nodeRed;
|
||||
}
|
||||
|
||||
let redProcess;
|
||||
let stopping;
|
||||
let notificationsFlows;
|
||||
let notificationsCreds;
|
||||
let saveTimer;
|
||||
const nodePath = getNodeRedPath();
|
||||
|
||||
function startNodeRed() {
|
||||
adapter.config.maxMemory = parseInt(adapter.config.maxMemory, 10) || 128;
|
||||
const args = ['--max-old-space-size=' + adapter.config.maxMemory, nodePath + '/red.js', '-v', '--settings', userdataDir + 'settings.js'];
|
||||
adapter.log.info('Starting node-red: ' + args.join(' '));
|
||||
|
||||
redProcess = spawn('node', args);
|
||||
|
||||
redProcess.on('error', function (err) {
|
||||
adapter.log.error('catched exception from node-red:' + JSON.stringify(err));
|
||||
});
|
||||
|
||||
redProcess.stdout.on('data', function (data) {
|
||||
if (!data) return;
|
||||
data = data.toString();
|
||||
if (data[data.length - 2] === '\r' && data[data.length - 1] === '\n') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 2] === '\n' && data[data.length - 1] === '\r') data = data.substring(0, data.length - 2);
|
||||
if (data[data.length - 1] === '\r') data = data.substring(0, data.length - 1);
|
||||
|
||||
if (data.indexOf('[err') !== -1) {
|
||||
adapter.log.error(data);
|
||||
} else if (data.indexOf('[warn]') !== -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.debug(data);
|
||||
}
|
||||
});
|
||||
redProcess.stderr.on('data', function (data) {
|
||||
if (!data) return;
|
||||
if (data[0]) {
|
||||
let text = '';
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
text += String.fromCharCode(data[i]);
|
||||
}
|
||||
data = text;
|
||||
}
|
||||
if (data.indexOf && data.indexOf('[warn]') === -1) {
|
||||
adapter.log.warn(data);
|
||||
} else {
|
||||
adapter.log.error(JSON.stringify(data));
|
||||
}
|
||||
});
|
||||
|
||||
redProcess.on('exit', function (exitCode) {
|
||||
adapter.log.info('node-red exited with ' + exitCode);
|
||||
redProcess = null;
|
||||
if (!stopping) {
|
||||
setTimeout(startNodeRed, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setOption(line, option, value) {
|
||||
const toFind = "'%%" + option + "%%'";
|
||||
const pos = line.indexOf(toFind);
|
||||
if (pos !== -1) {
|
||||
return line.substring(0, pos) + ((value !== undefined) ? value : (adapter.config[option] === null || adapter.config[option] === undefined) ? '' : adapter.config[option]) + line.substring(pos + toFind.length);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function writeSettings() {
|
||||
const config = JSON.stringify(adapter.systemConfig);
|
||||
const text = fs.readFileSync(__dirname + '/settings.js').toString();
|
||||
const lines = text.split('\n');
|
||||
let npms = '\r\n';
|
||||
const dir = __dirname.replace(/\\/g, '/') + '/node_modules/';
|
||||
const nodesDir = '"' + __dirname.replace(/\\/g, '/') + '/nodes/"';
|
||||
|
||||
const bind = '"' + (adapter.config.bind || '0.0.0.0') + '"';
|
||||
const auth = adapter.config.user && adapter.config.pass ? JSON.stringify({user: adapter.config.user, pass: adapter.config.pass}) : '""';
|
||||
const pass = '"' + adapter.config.pass + '"';
|
||||
|
||||
for (let a = 0; a < additional.length; a++) {
|
||||
if (additional[a].match(/^node-red-/)) continue;
|
||||
npms += ' "' + additional[a] + '": require("' + dir + additional[a] + '")';
|
||||
if (a !== additional.length - 1) {
|
||||
npms += ', \r\n';
|
||||
}
|
||||
}
|
||||
|
||||
// update from 1.0.1 (new convert-option)
|
||||
if (adapter.config.valueConvert === null ||
|
||||
adapter.config.valueConvert === undefined ||
|
||||
adapter.config.valueConvert === '' ||
|
||||
adapter.config.valueConvert === 'true' ||
|
||||
adapter.config.valueConvert === '1' ||
|
||||
adapter.config.valueConvert === 1) {
|
||||
adapter.config.valueConvert = true;
|
||||
}
|
||||
if (adapter.config.valueConvert === 0 ||
|
||||
adapter.config.valueConvert === '0' ||
|
||||
adapter.config.valueConvert === 'false') {
|
||||
adapter.config.valueConvert = false;
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'auth', auth);
|
||||
lines[i] = setOption(lines[i], 'pass', pass);
|
||||
lines[i] = setOption(lines[i], 'bind', bind);
|
||||
lines[i] = setOption(lines[i], 'port');
|
||||
lines[i] = setOption(lines[i], 'instance', adapter.instance);
|
||||
lines[i] = setOption(lines[i], 'config', config);
|
||||
lines[i] = setOption(lines[i], 'functionGlobalContext', npms);
|
||||
lines[i] = setOption(lines[i], 'nodesdir', nodesDir);
|
||||
lines[i] = setOption(lines[i], 'httpRoot');
|
||||
lines[i] = setOption(lines[i], 'credentialSecret', secret);
|
||||
lines[i] = setOption(lines[i], 'valueConvert');
|
||||
}
|
||||
fs.writeFileSync(userdataDir + 'settings.js', lines.join('\n'));
|
||||
}
|
||||
|
||||
function writeStateList(callback) {
|
||||
adapter.getForeignObjects('*', 'state', ['rooms', 'functions'], function (err, obj) {
|
||||
// remove native information
|
||||
for (const i in obj) {
|
||||
if (obj.hasOwnProperty(i) && obj[i].native) {
|
||||
delete obj[i].native;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(nodePath + '/public/iobroker.json', JSON.stringify(obj));
|
||||
if (callback) callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function saveObjects() {
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
let cred = undefined;
|
||||
let flows = undefined;
|
||||
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
cred = JSON.parse(fs.readFileSync(userdataDir + 'flows_cred.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows_cred.json');
|
||||
}
|
||||
try {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
flows = JSON.parse(fs.readFileSync(userdataDir + 'flows.json'));
|
||||
}
|
||||
} catch(e) {
|
||||
adapter.log.error('Cannot save ' + userdataDir + 'flows.json');
|
||||
}
|
||||
//upload it to config
|
||||
adapter.setObject('flows', {
|
||||
common: {
|
||||
name: 'Flows for node-red'
|
||||
},
|
||||
native: {
|
||||
cred: cred,
|
||||
flows: flows
|
||||
},
|
||||
type: 'config'
|
||||
}, function () {
|
||||
adapter.log.info('Save ' + userdataDir + 'flows.json');
|
||||
});
|
||||
}
|
||||
|
||||
function syncPublic(path) {
|
||||
if (!path) path = '/public';
|
||||
|
||||
const dir = fs.readdirSync(__dirname + path);
|
||||
|
||||
if (!fs.existsSync(nodePath + path)) {
|
||||
fs.mkdirSync(nodePath + path);
|
||||
}
|
||||
|
||||
for (let i = 0; i < dir.length; i++) {
|
||||
const stat = fs.statSync(__dirname + path + '/' + dir[i]);
|
||||
if (stat.isDirectory()) {
|
||||
syncPublic(path + '/' + dir[i]);
|
||||
} else {
|
||||
if (!fs.existsSync(nodePath + path + '/' + dir[i])) {
|
||||
fs.createReadStream(__dirname + path + '/' + dir[i]).pipe(fs.createWriteStream(nodePath + path + '/' + dir[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierFlows(isFirst) {
|
||||
if (!notificationsFlows) {
|
||||
if (fs.existsSync(userdataDir + 'flows.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsFlows = new Notify([userdataDir + 'flows.json']);
|
||||
notificationsFlows.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierFlows();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function installNotifierCreds(isFirst) {
|
||||
if (!notificationsCreds) {
|
||||
if (fs.existsSync(userdataDir + 'flows_cred.json')) {
|
||||
if (!isFirst) saveObjects();
|
||||
// monitor project file
|
||||
notificationsCreds = new Notify([userdataDir + 'flows_cred.json']);
|
||||
notificationsCreds.on('change', function () {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(saveObjects, 500);
|
||||
});
|
||||
} else {
|
||||
// Try to install notifier every 10 seconds till the file will be created
|
||||
setTimeout(function () {
|
||||
installNotifierCreds();
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
// Find userdata directory
|
||||
|
||||
// normally /opt/iobroker/node_modules/iobroker.js-controller
|
||||
// but can be /example/ioBroker.js-controller
|
||||
const controllerDir = utils.controllerDir;
|
||||
const parts = controllerDir.split('/');
|
||||
if (parts.length > 1 && parts[parts.length - 2] === 'node_modules') {
|
||||
parts.splice(parts.length - 2, 2);
|
||||
userdataDir = parts.join('/');
|
||||
userdataDir += '/iobroker-data/node-red/';
|
||||
}
|
||||
|
||||
// create userdata directory
|
||||
if (!fs.existsSync(userdataDir)) {
|
||||
fs.mkdirSync(userdataDir);
|
||||
}
|
||||
|
||||
syncPublic();
|
||||
|
||||
// Read configuration
|
||||
adapter.getObject('flows', function (err, obj) {
|
||||
if (obj && obj.native && obj.native.cred) {
|
||||
const c = JSON.stringify(obj.native.cred);
|
||||
// If really not empty
|
||||
if (c !== '{}' && c !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows_cred.json', JSON.stringify(obj.native.cred));
|
||||
}
|
||||
}
|
||||
if (obj && obj.native && obj.native.flows) {
|
||||
const f = JSON.stringify(obj.native.flows);
|
||||
// If really not empty
|
||||
if (f !== '{}' && f !== '[]') {
|
||||
fs.writeFileSync(userdataDir + 'flows.json', JSON.stringify(obj.native.flows));
|
||||
}
|
||||
}
|
||||
|
||||
installNotifierFlows(true);
|
||||
installNotifierCreds(true);
|
||||
|
||||
adapter.getForeignObject('system.config', (err, obj) => {
|
||||
if (obj && obj.native && obj.native.secret) {
|
||||
//noinspection JSUnresolvedVariable
|
||||
secret = obj.native.secret;
|
||||
}
|
||||
// Create settings for node-red
|
||||
writeSettings();
|
||||
writeStateList(() => startNodeRed());
|
||||
});
|
||||
});
|
||||
}
|
BIN
nodes/icons/iobroker.png
Normal file
BIN
nodes/icons/iobroker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 268 B |
330
nodes/ioBroker.html
Normal file
330
nodes/ioBroker.html
Normal file
@ -0,0 +1,330 @@
|
||||
<!--
|
||||
Copyright 2013-2017 bluefox<dogafox@gmail.com>.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="ioBroker in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic" style="width:60%"><input type="button" id="node-input-topic-button" style="display:inline-block;width:10%" value="."/>
|
||||
</div>
|
||||
<div id="dialog-select-member" style="display:none"></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
|
||||
<select id="node-input-payloadType" style="width:125px !important">
|
||||
<option value="value">value</option>
|
||||
<option value="object">object</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label> </label>
|
||||
<input type="checkbox" id="node-input-onlyack" style="display: inline-block; width: auto; vertical-align: top;">
|
||||
<label for="node-input-onlyack" style="width: 70%;">Send only on then ack==true</label>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-func"><i class="fa fa-wrench"></i> Mode</label>
|
||||
<select type="text" id="node-input-func" style="width:74%;">
|
||||
<option value="all">Send all events</option>
|
||||
<option value="rbe">block unless value changes</option>
|
||||
<option value="deadband">block unless changes by more than</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" id="node-bandgap">
|
||||
<label for="node-input-gap"> </label>
|
||||
<input type="text" id="node-input-gap" placeholder="e.g. 10 or 5%" style="width:71%;">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="ioBroker in">
|
||||
<p>ioBroker input node. Connects to a ioBroker and subscribes to the specified topic. The topic may contain redis wildcards (*).</p>
|
||||
<p>Outputs an object called <b>msg</b> containing <b>msg.topic, msg.payload, msg.timestamp, msg.lastchange and msg.acknowledged.</p>
|
||||
<p>The checkbox determines whether only States with ack == true or all events are forwarded.</p>
|
||||
<p>The select box Mode offers further filtering options. The options are the same as for the RBE node.</p>
|
||||
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" href="vendor/ui.fancytree.min.css"/>
|
||||
<style type="text/css">
|
||||
#dialog-select-member select{
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="vendor/jquery.fancytree-all.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="selectID.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
RED.nodes.registerType('ioBroker in',{
|
||||
category: 'input',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:"*",required:true},
|
||||
payloadType: {value:'value'},
|
||||
onlyack: {value:""},
|
||||
func: {value:"all"},
|
||||
gap: {value:"",validate:RED.validators.regex(/^(\d*[.]*\d*|)(%|)$/)}
|
||||
|
||||
},
|
||||
color:"#a8bfd8",
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "ioBroker.png",
|
||||
label: function() {
|
||||
return this.name||this.topic||"ioBroker";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (!window.__iobroker) {
|
||||
$.getJSON("iobroker.json", function(data) {
|
||||
window.__iobroker = data;
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
}
|
||||
|
||||
$('#node-input-topic-button').button({
|
||||
icons: {primary: 'ui-icon-folder-open'},
|
||||
text: false
|
||||
}).click(function () {
|
||||
$('#dialog-select-member').selectId('show', $('#node-input-topic').val(), undefined, function (newId, oldId) {
|
||||
var oldObj = $('#dialog-select-member').selectId('getInfo', oldId)
|
||||
var newObj = $('#dialog-select-member').selectId('getInfo', newId)
|
||||
$('#node-input-topic').val(newId);
|
||||
var oldName = $('#node-input-name').val();
|
||||
if (!oldName || !oldObj || (oldObj.common && oldName == oldObj.common.name && newObj.common)) {
|
||||
$('#node-input-name').val(newObj.common.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#node-input-func").on("change",function() {
|
||||
if ($("#node-input-func").val() == null) $("#node-input-func").val ("all");
|
||||
if ($("#node-input-func").val() === "deadband") {
|
||||
$("#node-bandgap").show();
|
||||
} else {
|
||||
$("#node-bandgap").hide();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="ioBroker out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic" style="width:60%"><input type="button" id="node-input-topic-button" style="display:inline-block;width:10%" value="."/>
|
||||
</div>
|
||||
<div id="dialog-select-member" style="display:none"></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-ack"><i class="fa fa-arrow-up"></i> Type</label>
|
||||
<select id="node-input-ack" style="width:73% !important">
|
||||
<option value="true">value</option>
|
||||
<option value="false">command</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-autoCreate"><i class="fa fa-arrow-up"></i> Auto create</label>
|
||||
<select id="node-input-autoCreate" style="width:73% !important">
|
||||
<option value="true">Create states if not exist</option>
|
||||
<option value="false">Ignore messages for non existing states</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-tips">Tip: Leave topic blank if you want to set them via msg properties.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="ioBroker out">
|
||||
<p>Connects to a ioBroker and publishes <b>msg.payload</b> either to the <b>msg.topic</b> or to the topic specified in the edit window. The value in the edit window has precedence.</p>
|
||||
<p>If <b>msg.payload</b> contains an object it will be stringified before being sent.</p>
|
||||
<p>If <b>msg.payload</b> contains "__create__", the object will be only created, but no value will be written.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('ioBroker out',{
|
||||
category: 'output',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
ack: {value:"false"},
|
||||
autoCreate: {value: "false"}
|
||||
},
|
||||
color:"#a8bfd8",
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "ioBroker.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||this.topic||"ioBroker";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (!window.__iobroker) {
|
||||
$.getJSON("iobroker.json", function(data) {
|
||||
window.__iobroker = data;
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
}
|
||||
|
||||
$('#node-input-topic-button').button({
|
||||
icons: {primary: 'ui-icon-folder-open'},
|
||||
text: false
|
||||
}).click(function () {
|
||||
$('#dialog-select-member').selectId('show', $('#node-input-topic').val(), undefined, function (newId, oldId) {
|
||||
var oldObj = $('#dialog-select-member').selectId('getInfo', oldId);
|
||||
var newObj = $('#dialog-select-member').selectId('getInfo', newId);
|
||||
$('#node-input-topic').val(newId);
|
||||
|
||||
var oldName = $('#node-input-name').val();
|
||||
if (!oldName || !oldObj || (oldObj.common && oldName == oldObj.common.name && newObj.common)) {
|
||||
$('#node-input-name').val(newObj.common.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="ioBroker get">
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic" style="width:60%"><input type="button" id="node-input-topic-button" style="display:inline-block;width:10%" value="."/>
|
||||
</div>
|
||||
<div id="dialog-select-member" style="display:none"></div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-payloadType"><i class="fa fa-envelope"></i> Payload</label>
|
||||
<select id="node-input-payloadType" style="width:125px !important">
|
||||
<option value="value">value</option>
|
||||
<option value="object">object</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-attrname"><i class="fa fa-tag"></i> Attribute</label>
|
||||
<input type="text" id="node-input-attrname" placeholder="attrname">
|
||||
</div>
|
||||
<div class="form-tips">Tip: Leave topic blank if you want to set them via msg properties.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="ioBroker get">
|
||||
<p>Connects to a ioBroker and returns the requested value or the object in the massage attribute in the properties dialog, e.g. <b>msg.payload</b>. The object could be identified either by the <b>msg.topic</b> or specified in the poperties dialog. The value in the poperties dialog has precedence.</p>
|
||||
<p>The msg object also contains the attributs <b>msg.timestamp</b>, <b>msg.lastchange</b> and <b>msg.acknowledged</b>. All other attributes ob the input msg object will be passed to the output msg object.</p>
|
||||
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('ioBroker get',{
|
||||
category: 'function',
|
||||
defaults: {
|
||||
name: {value:""},
|
||||
topic: {value:""},
|
||||
attrname: {value:"payload"},
|
||||
payloadType: {value:'value'}
|
||||
},
|
||||
color:"#a8bfd8",
|
||||
inputs:1,
|
||||
outputs:1,
|
||||
icon: "ioBroker.png",
|
||||
label: function() {
|
||||
return this.name||this.topic||"ioBroker get";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
},
|
||||
oneditprepare: function() {
|
||||
if (!window.__iobroker) {
|
||||
$.getJSON("iobroker.json", function(data) {
|
||||
window.__iobroker = data;
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$('#dialog-select-member').selectId('init', {
|
||||
objects: window.__iobroker,
|
||||
noMultiselect: true,
|
||||
columns: ['name', 'role', 'enum', 'room'],
|
||||
states: null,
|
||||
noImg: true
|
||||
});
|
||||
}
|
||||
|
||||
$('#node-input-topic-button').button({
|
||||
icons: {primary: 'ui-icon-folder-open'},
|
||||
text: false
|
||||
}).click(function () {
|
||||
$('#dialog-select-member').selectId('show', $('#node-input-topic').val(), undefined, function (newId, oldId) {
|
||||
var oldObj = $('#dialog-select-member').selectId('getInfo', oldId);
|
||||
var newObj = $('#dialog-select-member').selectId('getInfo', newId);
|
||||
$('#node-input-topic').val(newId);
|
||||
|
||||
var oldName = $('#node-input-name').val();
|
||||
if (!oldName || !oldObj || (oldObj.common && oldName == oldObj.common.name && newObj.common)) {
|
||||
$('#node-input-name').val(newObj.common.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
|
389
nodes/ioBroker.js
Normal file
389
nodes/ioBroker.js
Normal file
@ -0,0 +1,389 @@
|
||||
/**
|
||||
* Copyright 2013,2014 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = function(RED) {
|
||||
'use strict';
|
||||
require('events').EventEmitter.prototype._maxListeners = 100;
|
||||
var util = require('util');
|
||||
var utils = require(__dirname + '/../lib/utils');
|
||||
//var redis = require("redis");
|
||||
//var hashFieldRE = /^([^=]+)=(.*)$/;
|
||||
// Get the redis address
|
||||
|
||||
var settings = require(process.env.NODE_RED_HOME + '/red/red').settings;
|
||||
var instance = settings.get('iobrokerInstance') || 0;
|
||||
var config = settings.get('iobrokerConfig');
|
||||
var valueConvert = settings.get('valueConvert');
|
||||
if (typeof config == 'string') {
|
||||
config = JSON.parse(config);
|
||||
}
|
||||
var adapter;
|
||||
|
||||
try {
|
||||
adapter = utils.Adapter({name: 'node-red', instance: instance, config: config});
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
}
|
||||
var nodes = [];
|
||||
var nodeSets = [];
|
||||
var ready = false;
|
||||
var log = adapter && adapter.log && adapter.log.warn ? adapter.log.warn : console.log;
|
||||
|
||||
adapter.on('ready', function () {
|
||||
ready = true;
|
||||
adapter.subscribeForeignStates('*');
|
||||
while (nodes.length) {
|
||||
var node = nodes.pop();
|
||||
if (node instanceof IOBrokerInNode) {
|
||||
adapter.on('stateChange', node.stateChange);
|
||||
}
|
||||
node.status({fill: 'green', shape: 'dot', text: 'connected'});
|
||||
}
|
||||
var count = 0;
|
||||
while (nodeSets.length) {
|
||||
var nodeSetData = nodeSets.pop();
|
||||
nodeSetData.node.emit('input', nodeSetData.msg);
|
||||
count++;
|
||||
}
|
||||
if (count > 0) log(count + ' queued state values set in ioBroker');
|
||||
|
||||
});
|
||||
|
||||
// name is like system.state, pattern is like "*.state" or "*" or "*system*"
|
||||
function getRegex(pattern) {
|
||||
if (!pattern || pattern === '*') return null;
|
||||
if (pattern.indexOf('*') === -1) return null;
|
||||
if (pattern[pattern.length - 1] !== '*') pattern = pattern + '$';
|
||||
if (pattern[0] !== '*') pattern = '^' + pattern;
|
||||
pattern = pattern.replace(/\*/g, '[a-zA-Z0-9.\s]');
|
||||
pattern = pattern.replace(/\./g, '\\.');
|
||||
return new RegExp(pattern);
|
||||
}
|
||||
|
||||
function checkState(node, id, val, callback) {
|
||||
if (node.idChecked) {
|
||||
return callback && callback();
|
||||
}
|
||||
if (node.topic) {
|
||||
node.idChecked = true;
|
||||
}
|
||||
|
||||
adapter.getObject(id, function (err, obj) {
|
||||
if (!obj) {
|
||||
adapter.getForeignObject(id, function (err, obj) {
|
||||
if (!obj) {
|
||||
log('State "' + id + '" was created in the ioBroker as ' + adapter._fixId(id));
|
||||
// Create object
|
||||
adapter.setObject(id, {
|
||||
common: {
|
||||
name: id,
|
||||
role: 'info',
|
||||
type: 'state',
|
||||
desc: 'Created by Node-Red'
|
||||
},
|
||||
native: {},
|
||||
type: 'state'
|
||||
}, function (err) {
|
||||
if (val !== undefined && val !== null && val !== '__create__') {
|
||||
adapter.setState(id, val, function () {
|
||||
callback && callback();
|
||||
});
|
||||
} else {
|
||||
adapter.setState(id, undefined, function () {
|
||||
callback && callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
node._id = obj._id;
|
||||
if (val !== undefined && val !== null && val !== '__create__') {
|
||||
adapter.setForeignState(obj._id, val, function () {
|
||||
callback && callback();
|
||||
});
|
||||
} else {
|
||||
callback && callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (val !== undefined && val !== null && val !== '__create__') {
|
||||
adapter.setForeignState(obj._id, val, function () {
|
||||
callback && callback();
|
||||
});
|
||||
} else {
|
||||
callback && callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function IOBrokerInNode(n) {
|
||||
var node = this;
|
||||
RED.nodes.createNode(node,n);
|
||||
node.topic = (n.topic || '*').replace(/\//g, '.');
|
||||
node.regex = new RegExp('^node-red\\.' + instance + '\\.');
|
||||
|
||||
// If no adapter prefix, add own adapter prefix
|
||||
if (node.topic && node.topic.indexOf('.') === -1) {
|
||||
node.topic = adapter.namespace + '.' + node.topic;
|
||||
}
|
||||
|
||||
node.regexTopic = getRegex(this.topic);
|
||||
node.payloadType = n.payloadType;
|
||||
node.onlyack = (n.onlyack == true || false);
|
||||
node.func = n.func || 'all';
|
||||
node.gap = n.gap || '0';
|
||||
node.pc = false;
|
||||
|
||||
if (node.gap.substr(-1) === '%') {
|
||||
node.pc = true;
|
||||
node.gap = parseFloat(node.gap);
|
||||
}
|
||||
node.g = node.gap;
|
||||
|
||||
node.previous = {};
|
||||
|
||||
if (node.topic) {
|
||||
var id = node.topic;
|
||||
// If no wildchars and belongs to this adapter
|
||||
if (id.indexOf('*') === -1 && (node.regex.test(id) || id.indexOf('.') !== -1)) {
|
||||
checkState(node, id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ready) {
|
||||
node.status({fill: 'green', shape: 'dot', text: 'connected'});
|
||||
} else {
|
||||
node.status({fill: 'red', shape: 'ring', text: 'disconnected'}, true);
|
||||
}
|
||||
|
||||
node.stateChange = function(topic, obj) {
|
||||
if (node.regexTopic) {
|
||||
if (!node.regexTopic.test(topic)) return;
|
||||
} else if (node.topic !== '*' && node.topic !== topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.onlyack && obj.ack != true) return;
|
||||
|
||||
var t = topic.replace(/\./g, '/') || '_no_topic';
|
||||
//node.log ("Function: " + node.func);
|
||||
|
||||
if (node.func === 'rbe') {
|
||||
if (obj.val === node.previous[t]) {
|
||||
return;
|
||||
}
|
||||
} else if (node.func === 'deadband') {
|
||||
var n = parseFloat(obj.val.toString());
|
||||
if (!isNaN(n)) {
|
||||
//node.log('Old Value: ' + node.previous[t] + ' New Value: ' + n);
|
||||
if (node.pc) { node.gap = (node.previous[t] * node.g / 100) || 0; }
|
||||
if (!node.previous.hasOwnProperty(t)) {
|
||||
node.previous[t] = n - node.gap;
|
||||
}
|
||||
if (!Math.abs(n - node.previous[t]) >= node.gap) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
node.warn('no number found in value');
|
||||
return;
|
||||
}
|
||||
}
|
||||
node.previous[t] = obj.val;
|
||||
|
||||
node.send({
|
||||
topic: t,
|
||||
payload: (node.payloadType === 'object') ? obj : ((obj.val === null || obj.val === undefined) ? '' : (valueConvert ? obj.val.toString() : obj.val)),
|
||||
acknowledged:obj.ack,
|
||||
timestamp: obj.ts,
|
||||
lastchange: obj.lc,
|
||||
from: obj.from
|
||||
});
|
||||
|
||||
node.status({fill: 'green', shape: 'dot', text: (node.payloadType === 'object') ? JSON.stringify(obj) : ((obj.val === null || obj.val === undefined) ? '' : obj.val.toString() ) });
|
||||
};
|
||||
|
||||
node.on('close', function() {
|
||||
adapter.removeListener('stateChange', node.stateChange);
|
||||
});
|
||||
|
||||
if (ready) {
|
||||
adapter.on('stateChange', node.stateChange);
|
||||
} else {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
RED.nodes.registerType('ioBroker in', IOBrokerInNode);
|
||||
|
||||
function IOBrokerOutNode(n) {
|
||||
var node = this;
|
||||
RED.nodes.createNode(node,n);
|
||||
node.topic = n.topic;
|
||||
|
||||
node.ack = (n.ack === 'true' || n.ack === true);
|
||||
node.autoCreate = (n.autoCreate === 'true' || n.autoCreate === true);
|
||||
node.regex = new RegExp('^node-red\\.' + instance + '\\.');
|
||||
|
||||
if (ready) {
|
||||
node.status({fill: 'green', shape: 'dot', text: 'connected'});
|
||||
} else {
|
||||
node.status({fill: 'red', shape: 'ring', text: 'disconnected'}, true);
|
||||
}
|
||||
|
||||
function setState(id, val, ack) {
|
||||
if (node.idChecked) {
|
||||
if (val !== '__create__') {
|
||||
adapter.setState(id, {val: val, ack: ack});
|
||||
}
|
||||
} else {
|
||||
checkState(node, id, {val: val, ack: ack});
|
||||
}
|
||||
}
|
||||
|
||||
node.on('input', function(msg) {
|
||||
var id = node.topic || msg.topic;
|
||||
if (!ready) {
|
||||
nodeSets.push({'node': node, 'msg': msg});
|
||||
//log('Message for "' + id + '" queued because ioBroker connection not initialized');
|
||||
return;
|
||||
}
|
||||
if (id) {
|
||||
id = id.replace(/\//g, '.');
|
||||
|
||||
// Create variable if not exists
|
||||
if (node.autoCreate && !node.idChecked) {
|
||||
id = id.replace(/\//g, '.');
|
||||
// If no wildchars and belongs to this adapter
|
||||
if (id.indexOf('*') === -1 && (node.regex.test(id) || id.indexOf('.') !== -1)) {
|
||||
checkState(node, id);
|
||||
}
|
||||
}
|
||||
|
||||
// If not this adapter state
|
||||
if (!node.regex.test(id) && id.indexOf('.') !== -1) {
|
||||
// Check if state exists
|
||||
adapter.getForeignState(id, function (err, state) {
|
||||
if (!err && state) {
|
||||
adapter.setForeignState(id, {val: msg.payload, ack: node.ack});
|
||||
node.status({fill: 'green', shape: 'dot', text: msg.payload.toString() });
|
||||
} else {
|
||||
log('State "' + id + '" does not exist in the ioBroker');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (id.indexOf('*') !== -1) {
|
||||
log('Invalid topic name "' + id + '" for ioBroker');
|
||||
} else {
|
||||
setState(id, msg.payload, node.ack);
|
||||
node.status({fill: 'green', shape: 'dot', text: msg.payload.toString() });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.warn('No key or topic set');
|
||||
}
|
||||
});
|
||||
|
||||
if (!ready) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
//node.on("close", function() {
|
||||
//
|
||||
// });
|
||||
|
||||
}
|
||||
RED.nodes.registerType('ioBroker out', IOBrokerOutNode);
|
||||
|
||||
function IOBrokerGetNode(n) {
|
||||
var node = this;
|
||||
RED.nodes.createNode(node,n);
|
||||
node.topic = (typeof n.topic=== 'string' && n.topic.length > 0 ? n.topic.replace(/\//g, '.') : null) ;
|
||||
|
||||
// If no adapter prefix, add own adapter prefix
|
||||
if (node.topic && node.topic.indexOf('.') === -1) {
|
||||
node.topic = adapter.namespace + '.' + node.topic;
|
||||
}
|
||||
|
||||
node.regex = new RegExp('^node-red\\.' + instance + '\\.');
|
||||
//node.regex = getRegex(this.topic);
|
||||
node.payloadType = n.payloadType;
|
||||
node.attrname = n.attrname;
|
||||
|
||||
if (node.topic) {
|
||||
var id = node.topic;
|
||||
// If no wildchars and belongs to this adapter
|
||||
if (id.indexOf('*') === -1 && (node.regex.test(id) || id.indexOf('.') !== -1)) {
|
||||
checkState(node, id);
|
||||
}
|
||||
}
|
||||
|
||||
if (ready) {
|
||||
node.status({fill: 'green', shape: 'dot', text: 'connected'});
|
||||
} else {
|
||||
node.status({fill: 'red', shape: 'ring', text: 'disconnected'}, true);
|
||||
}
|
||||
|
||||
node.getStateValue = function (msg) {
|
||||
return function (err, state) {
|
||||
if (!err && state) {
|
||||
msg[node.attrname] = (node.payloadType === 'object') ? state : ((state.val === null || state.val === undefined) ? '' : (valueConvert ? state.val.toString() : state.val));
|
||||
msg.acknowledged = state.ack;
|
||||
msg.timestamp = state.ts;
|
||||
msg.lastchange = state.lc;
|
||||
node.status({
|
||||
fill: 'green',
|
||||
shape: 'dot',
|
||||
text: (node.payloadType === 'object') ? JSON.stringify(state) : ((state.val === null || state.val === undefined) ? '' : state.val.toString())
|
||||
});
|
||||
node.send(msg);
|
||||
} else {
|
||||
log('State "' + id + '" does not exist in the ioBroker');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
node.on('input', function(msg) {
|
||||
var id = node.topic || msg.topic;
|
||||
if (!ready) {
|
||||
nodeSets.push({'node': node, 'msg': msg});
|
||||
//log('Message for "' + id + '" queued because ioBroker connection not initialized');
|
||||
return;
|
||||
}
|
||||
if (id) {
|
||||
id = id.replace(/\//g, '.');
|
||||
// If not this adapter state
|
||||
if (!node.regex.test(id) && id.indexOf('.') !== -1) {
|
||||
// Check if state exists
|
||||
adapter.getForeignState(id, node.getStateValue(msg));
|
||||
} else {
|
||||
if (id.indexOf('*') !== -1) {
|
||||
log('Invalid topic name "' + id + '" for ioBroker');
|
||||
} else {
|
||||
adapter.getState(id, node.getStateValue(msg));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.warn('No key or topic set');
|
||||
}
|
||||
});
|
||||
|
||||
if (!ready) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
}
|
||||
RED.nodes.registerType('ioBroker get', IOBrokerGetNode);
|
||||
};
|
53
package.json
Normal file
53
package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "iobroker.node-red",
|
||||
"description": "This adapter uses node-red as a service of ioBroker. No additional node-red instance required.",
|
||||
"version": "1.7.1",
|
||||
"author": {
|
||||
"name": "bluefox",
|
||||
"email": "dogafox@gmail.com"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "bluefox",
|
||||
"email": "dogafox@gmail.com"
|
||||
}
|
||||
],
|
||||
"homepage": "https://github.com/ioBroker/ioBroker.node-red",
|
||||
"license": "Apache-2.0",
|
||||
"keywords": [
|
||||
"ioBroker",
|
||||
"node-red",
|
||||
"home automation"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ioBroker/ioBroker.node-red"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"js2xmlparser": "^3.0.0",
|
||||
"fs.notify": "^0.0.4",
|
||||
"feedparser": "^2.2.9",
|
||||
"mongodb": "^3.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-red": "^0.19.4",
|
||||
"node-red-contrib-os": "^0.1.7",
|
||||
"node-red-dashboard": "^2.9.8",
|
||||
"node-red-contrib-aggregator": "^1.3.0",
|
||||
"node-red-contrib-polymer": "^0.0.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"request": "^2.88.0",
|
||||
"mocha": "^5.2.0",
|
||||
"chai": "^4.1.2"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/ioBroker/ioBroker.node-red/issues"
|
||||
},
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "node node_modules/mocha/bin/mocha --exit"
|
||||
},
|
||||
"readmeFilename": "README.md"
|
||||
}
|
176
public/schedule/LICENSE.txt
Normal file
176
public/schedule/LICENSE.txt
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
68
public/schedule/index.html
Normal file
68
public/schedule/index.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<!--
|
||||
Copyright 2014 Nicholas J Humfrey
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>Node-RED Schedule</title>
|
||||
|
||||
<link rel="shortcut icon" href="../favicon.ico" />
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<script src="../jquery/js/jquery-1.11.1.min.js"></script>
|
||||
<script src="../bootstrap/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../bootstrap/css/bootstrap.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
<script src="./schedule.js"></script>
|
||||
</head>
|
||||
|
||||
|
||||
<body spellcheck="false">
|
||||
|
||||
<div class="navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container-fluid">
|
||||
<a class="brand" href="#"><img src="../node-red.png"> <span class="red">Node-RED Schedule</span> </a>
|
||||
<div class="btn-group pull-right">
|
||||
<a class="btn btn-primary" href="../" id="btn-back"><i class="icon-home icon-white"></i> Back to Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<table id="schedule" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Hour</th>
|
||||
<th>Min</th>
|
||||
<th>Day</th>
|
||||
<th>Month</th>
|
||||
<th>Day of Week</th>
|
||||
<th>Topic</th>
|
||||
<th>Payload</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
28
public/schedule/style.css
Normal file
28
public/schedule/style.css
Normal file
@ -0,0 +1,28 @@
|
||||
body {
|
||||
font: 14px "Helvetica" !important;
|
||||
}
|
||||
|
||||
a.brand {
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
a.brand span {
|
||||
vertical-align: middle;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
a.brand:hover span.red {
|
||||
color: rgb(197, 112, 112) !important;
|
||||
}
|
||||
|
||||
a.brand:hover {
|
||||
color: #bbb !important;
|
||||
}
|
||||
|
||||
a.brand img {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#schedule {
|
||||
margin-top: 20px;
|
||||
}
|
2465
public/selectID.js
Normal file
2465
public/selectID.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/vendor/icons.gif
vendored
Normal file
BIN
public/vendor/icons.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
27
public/vendor/jquery.fancytree-all.min.js
vendored
Normal file
27
public/vendor/jquery.fancytree-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
8
public/vendor/ui.fancytree.min.css
vendored
Normal file
8
public/vendor/ui.fancytree.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
144
settings.js
Normal file
144
settings.js
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright 2013 IBM Corp.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
// The `https` setting requires the `fs` module. Uncomment the following
|
||||
// to make it available:
|
||||
//var fs = require("fs");
|
||||
|
||||
module.exports = {
|
||||
// the tcp port that the Node-RED web server is listening on
|
||||
uiPort: '%%port%%',
|
||||
uiHost: '%%bind%%',
|
||||
|
||||
// By default, the Node-RED UI accepts connections on all IPv4 interfaces.
|
||||
// The following property can be used to listen on a specific interface. For
|
||||
// example, the following would only allow connections from the local machine.
|
||||
//uiHost: "127.0.0.1",
|
||||
iobrokerInstance: '%%instance%%',
|
||||
iobrokerConfig: '%%config%%',
|
||||
|
||||
// Retry time in milliseconds for MQTT connections
|
||||
mqttReconnectTime: 15000,
|
||||
|
||||
// Retry time in milliseconds for Serial port connections
|
||||
serialReconnectTime: 15000,
|
||||
|
||||
// Retry time in milliseconds for TCP socket connections
|
||||
//socketReconnectTime: 10000,
|
||||
|
||||
// Timeout in milliseconds for TCP server socket connections
|
||||
// defaults to no timeout
|
||||
//socketTimeout: 120000,
|
||||
|
||||
// Maximum number of lines in debug window before pruning
|
||||
debugMaxLength: 1000,
|
||||
|
||||
// The file containing the flows. If not set, it defaults to flows_<hostname>.json
|
||||
flowFile: 'flows.json',
|
||||
|
||||
// To enabled pretty-printing of the flow within the flow file, set the following
|
||||
// property to true:
|
||||
flowFilePretty: true,
|
||||
|
||||
// By default, all user data is stored in the Node-RED install directory. To
|
||||
// use a different location, the following property can be used
|
||||
userDir: __dirname + '/',
|
||||
|
||||
// Node-RED scans the `nodes` directory in the install directory to find nodes.
|
||||
// The following property can be used to specify an additional directory to scan.
|
||||
nodesDir: '%%nodesdir%%',
|
||||
|
||||
// By default, the Node-RED UI is available at http://localhost:1880/
|
||||
// The following property can be used to specify a different root path.
|
||||
// If set to false, this is disabled.
|
||||
//httpAdminRoot: '/admin',
|
||||
|
||||
// You can protect the user interface with a userid and password by using the following property.
|
||||
// The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password')
|
||||
httpAdminAuth: '%%auth%%',
|
||||
|
||||
// Some nodes, such as HTTP In, can be used to listen for incoming http requests.
|
||||
// By default, these are served relative to '/'. The following property
|
||||
// can be used to specifiy a different root path. If set to false, this is
|
||||
// disabled.
|
||||
//httpNodeRoot: '/nodes',
|
||||
|
||||
// To password protect the node-defined HTTP endpoints, the following property
|
||||
// can be used.
|
||||
// The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password')
|
||||
//httpNodeAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"},
|
||||
|
||||
// When httpAdminRoot is used to move the UI to a different root path, the
|
||||
// following property can be used to identify a directory of static content
|
||||
// that should be served at http://localhost:1880/.
|
||||
//httpStatic: '/home/nol/node-red-dashboard/',
|
||||
|
||||
// To password protect the static content, the following property can be used.
|
||||
// The password must be an md5 hash eg.. 5f4dcc3b5aa765d61d8327deb882cf99 ('password')
|
||||
//httpStaticAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"},
|
||||
|
||||
// The following property can be used in place of 'httpAdminRoot' and 'httpNodeRoot',
|
||||
// to apply the same root to both parts.
|
||||
httpRoot: "'%%httpRoot%%'",
|
||||
|
||||
// The following property can be used in place of 'httpAdminAuth' and 'httpNodeAuth',
|
||||
// to apply the same authentication to both parts.
|
||||
//httpAuth: {user:"user",pass:"5f4dcc3b5aa765d61d8327deb882cf99"},
|
||||
|
||||
// The following property can be used to disable the editor. The admin API
|
||||
// is not affected by this option. To disable both the editor and the admin
|
||||
// API, use either the httpRoot or httpAdminRoot properties
|
||||
//disableEditor: false,
|
||||
|
||||
// The following property can be used to enable HTTPS
|
||||
// See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener
|
||||
// for details on its contents.
|
||||
// See the comment at the top of this file on how to load the `fs` module used by
|
||||
// this setting.
|
||||
//
|
||||
//https: {
|
||||
// key: fs.readFileSync('privatekey.pem'),
|
||||
// cert: fs.readFileSync('certificate.pem')
|
||||
//},
|
||||
|
||||
// The following property can be used to configure cross-origin resource sharing
|
||||
// in the HTTP nodes.
|
||||
// See https://github.com/troygoode/node-cors#configuration-options for
|
||||
// details on its contents. The following is a basic permissive set of options:
|
||||
//httpNodeCors: {
|
||||
// origin: "*",
|
||||
// methods: "GET,PUT,POST,DELETE"
|
||||
//},
|
||||
|
||||
// Anything in this hash is globally available to all functions.
|
||||
// It is accessed as context.global.
|
||||
// eg:
|
||||
// functionGlobalContext: { os:require('os') }
|
||||
// can be accessed in a function block as:
|
||||
// context.global.os
|
||||
|
||||
valueConvert: '%%valueConvert%%',
|
||||
|
||||
credentialSecret: "'%%credentialSecret%%'",
|
||||
|
||||
functionGlobalContext: {
|
||||
//'%%functionGlobalContext%%'
|
||||
// os:require('os'),
|
||||
// bonescript:require('bonescript'),
|
||||
// arduino:require('duino')
|
||||
}
|
||||
|
||||
};
|
17
tasks/jscs.js
Normal file
17
tasks/jscs.js
Normal file
@ -0,0 +1,17 @@
|
||||
var srcDir = __dirname + "/../";
|
||||
|
||||
module.exports = {
|
||||
all: {
|
||||
src: [
|
||||
srcDir + "*.js",
|
||||
srcDir + "lib/*.js",
|
||||
srcDir + "adapter/example/*.js",
|
||||
srcDir + "tasks/**/*.js",
|
||||
srcDir + "www/**/*.js",
|
||||
'!' + srcDir + "www/lib/**/*.js",
|
||||
'!' + srcDir + 'node_modules/**/*.js',
|
||||
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
|
||||
],
|
||||
options: require('./jscsRules.js')
|
||||
}
|
||||
};
|
36
tasks/jscsRules.js
Normal file
36
tasks/jscsRules.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
force: true,
|
||||
"requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], /*"if",*/
|
||||
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireParenthesesAroundIIFE": true,
|
||||
"disallowSpacesInFunctionDeclaration": {"beforeOpeningRoundBrace": true},
|
||||
"disallowSpacesInNamedFunctionExpression": {"beforeOpeningRoundBrace": true},
|
||||
"requireSpacesInFunctionExpression": {"beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInAnonymousFunctionExpression": {"beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInNamedFunctionExpression": {"beforeOpeningCurlyBrace": true},
|
||||
"requireSpacesInFunctionDeclaration": {"beforeOpeningCurlyBrace": true},
|
||||
"disallowMultipleVarDecl": true,
|
||||
"requireBlocksOnNewline": true,
|
||||
"disallowEmptyBlocks": true,
|
||||
"disallowSpacesInsideObjectBrackets": true,
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpaceAfterObjectKeys": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"requireCommaBeforeLineBreak": true,
|
||||
//"requireAlignedObjectValues": "all",
|
||||
"requireOperatorBeforeLineBreak": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
// "disallowLeftStickedOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
// "requireRightStickedOperators": ["!"],
|
||||
// "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
//"disallowSpaceAfterBinaryOperators": [","],
|
||||
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
|
||||
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
|
||||
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
|
||||
"requireSpaceAfterBinaryOperators": ["?", ">", ",", ">=", "<=", "<", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
|
||||
//"validateIndentation": 4,
|
||||
//"validateQuoteMarks": { "mark": "\"", "escape": true },
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowKeywordsOnNewLine": ["else", "catch"]
|
||||
|
||||
};
|
17
tasks/jshint.js
Normal file
17
tasks/jshint.js
Normal file
@ -0,0 +1,17 @@
|
||||
var srcDir = __dirname + "/../";
|
||||
|
||||
module.exports = {
|
||||
options: {
|
||||
force: true
|
||||
},
|
||||
all: [
|
||||
srcDir + "*.js",
|
||||
srcDir + "lib/*.js",
|
||||
srcDir + "adapter/example/*.js",
|
||||
srcDir + "tasks/**/*.js",
|
||||
srcDir + "www/**/*.js",
|
||||
'!' + srcDir + "www/lib/**/*.js",
|
||||
'!' + srcDir + 'node_modules/**/*.js',
|
||||
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
|
||||
]
|
||||
};
|
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;
|
||||
}
|
105
test/testFunctions.js
Normal file
105
test/testFunctions.js
Normal file
@ -0,0 +1,105 @@
|
||||
var expect = require('chai').expect;
|
||||
var setup = require(__dirname + '/lib/setup');
|
||||
var request = require('request');
|
||||
|
||||
var objects = null;
|
||||
var states = null;
|
||||
var onStateChanged = null;
|
||||
var onObjectChanged = null;
|
||||
var port = 18888;
|
||||
|
||||
function checkConnectionOfAdapter(cb, counter) {
|
||||
counter = counter || 0;
|
||||
if (counter > 20) {
|
||||
cb && cb('Cannot check connection');
|
||||
return;
|
||||
}
|
||||
|
||||
states.getState('system.adapter.node-red.0.alive', function (err, state) {
|
||||
if (err) console.error(err);
|
||||
if (state && state.val) {
|
||||
cb && cb();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
checkConnectionOfAdapter(cb, counter + 1);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkValueOfState(id, value, cb, counter) {
|
||||
counter = counter || 0;
|
||||
if (counter > 20) {
|
||||
cb && cb('Cannot check value Of State ' + id);
|
||||
return;
|
||||
}
|
||||
|
||||
states.getState(id, function (err, state) {
|
||||
if (err) console.error(err);
|
||||
if (value === null && !state) {
|
||||
cb && cb();
|
||||
} else
|
||||
if (state && (value === undefined || state.val === value)) {
|
||||
cb && cb();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
checkValueOfState(id, value, cb, counter + 1);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('Test node-red', function() {
|
||||
before('Test node-red: 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.port = port;
|
||||
|
||||
setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController(true, function (id, obj) {
|
||||
if (onObjectChanged) onObjectChanged(id, obj);
|
||||
}, function (id, state) {
|
||||
if (onStateChanged) onStateChanged(id, state);
|
||||
},
|
||||
function (_objects, _states) {
|
||||
objects = _objects;
|
||||
states = _states;
|
||||
states.subscribe('*');
|
||||
_done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Test node-red: Check if adapter started', function (done) {
|
||||
this.timeout(5000);
|
||||
checkConnectionOfAdapter(done);
|
||||
});
|
||||
|
||||
it('Test node-red: check creation of state', function (done) {
|
||||
this.timeout(20000);
|
||||
// check if node-red is running
|
||||
|
||||
setTimeout(function () {
|
||||
request('http://localhost:' + port, function (error, response, body) {
|
||||
expect(error).to.be.not.ok;
|
||||
expect(body).to.be.ok;
|
||||
done();
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
after('Test node-red: Stop js-controller', function (done) {
|
||||
this.timeout(6000);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user