Initial commit
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
.idea
|
||||||
|
node_modules
|
||||||
|
nbproject
|
||||||
|
admin/i18n/flat.txt
|
||||||
|
admin/i18n/*/flat.txt
|
10
.npmignore
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
gulpfile.js
|
||||||
|
admin/i18n
|
||||||
|
tasks
|
||||||
|
node_modules
|
||||||
|
.idea
|
||||||
|
.git
|
||||||
|
/node_modules
|
||||||
|
test
|
||||||
|
.travis.yml
|
||||||
|
appveyor.yml
|
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
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 @@Author@@ <@@email@@>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
95
README.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
![Logo](admin/template.png)
|
||||||
|
# ioBroker.template
|
||||||
|
=================
|
||||||
|
|
||||||
|
This adapter is a template for the creation of an ioBroker adapter. You do not need it at least that you plan developing your own adapter.
|
||||||
|
|
||||||
|
It includes both code running within iobroker and as vis widget. If you only plan to create a vis widget then you should use the [iobroker.vis-template](https://github.com/ioBroker/ioBroker.vis-template) instead.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
1. download and unpack this packet from github ```https://github.com/ioBroker/ioBroker.template/archive/master.zip```
|
||||||
|
or clone git repository ```git clone --depth=1 https://github.com/ioBroker/ioBroker.template.git```
|
||||||
|
|
||||||
|
2. download required npm packets. Write in ioBroker.template directory:
|
||||||
|
|
||||||
|
```npm install```
|
||||||
|
|
||||||
|
3. set name of this template. Call
|
||||||
|
|
||||||
|
```gulp rename --name mynewname --email email@mail.com --author "Author Name"```
|
||||||
|
|
||||||
|
*mynewname* must be **lower** case and with no spaces.
|
||||||
|
|
||||||
|
If gulp is not available, install gulp globally:
|
||||||
|
|
||||||
|
```npm install -g gulp-cli```
|
||||||
|
|
||||||
|
4. rename directory from *ioBroker.template* (can be *ioBroker.template-master*) to *iobroker.mynewname*
|
||||||
|
|
||||||
|
5. to use this template you should copy it into *.../iobroker/node_modules* directory and then create an instance for it with iobroker.admin
|
||||||
|
|
||||||
|
6. create your adapter:
|
||||||
|
|
||||||
|
* you might want to start with main.js (code running within iobroker) and admin/index.html (the adapter settings page).
|
||||||
|
|
||||||
|
* [Adapter-Development-Documentation](https://github.com/ioBroker/ioBroker/wiki/Adapter-Development-Documentation),
|
||||||
|
|
||||||
|
* [Installation, setup and first steps with an ioBroker Development Environment](https://github.com/ioBroker/ioBroker/wiki/Installation,-setup-and-first-steps-with-an-ioBroker-Development-Environment)
|
||||||
|
|
||||||
|
* [Write and debug vis widgets](https://github.com/ioBroker/ioBroker/wiki/How-to-debug-vis-and-to-write-own-widget-set)
|
||||||
|
|
||||||
|
* files under the www folders are made available under http://<iobrokerIP>:8082/<adapter-name>/
|
||||||
|
* for this to work the iobroker.vis adapter has to be installed
|
||||||
|
* delete this folder if you do not plan to export any files this way
|
||||||
|
* call ```iobroker upload <adapter-name>``` after you change files in the www folder to get the new files uploaded to vis
|
||||||
|
* the widget folder contains an example of a vis widget
|
||||||
|
* you might want to start with *widget/<adapter-name>.html* and *widget/js/<adapter-name>.js*
|
||||||
|
* call ```iobroker visdebug <adapter-name>``` to enable debugging and upload widget to "vis". (This works only from V0.7.15 of js-controller)
|
||||||
|
* If you do not plan to export any widget then delete the whole widget folder and remove the ```"restartAdapters": ["vis"]``` statement from *io-package.json*
|
||||||
|
* After admin/index.html is changed you must execute ```iobroker upload mynewname``` to see changes in admin console. The same is valid for any files in *admin* and *www* directory
|
||||||
|
|
||||||
|
7. change version: edit package.json and then call ```grunt p``` in your adapter directory.
|
||||||
|
|
||||||
|
8. share it with the community
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* your github repository must have name "ioBroker.<adaptername>". **B** is capital in "ioBroker", but in the package.json the *name* must be low case, because npm does not allow upper case letters.
|
||||||
|
* *title* in io-package.json (common) is simple short name of adapter in english. *titleLang* is object that consist short names in many languages. *Lang* ist not german Länge, but english LANGuages.
|
||||||
|
* Do not use in the title the words "ioBroker" or "Adapter". It is clear anyway, that it is adapter for ioBroker.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 0.6.0 (2017.01.02)
|
||||||
|
* (bluefox) Support of admin3
|
||||||
|
|
||||||
|
### 0.5.0
|
||||||
|
* (vegetto) include vis widget
|
||||||
|
|
||||||
|
### 0.4.0
|
||||||
|
* (bluefox) fix errors with grunt
|
||||||
|
|
||||||
|
### 0.2.0
|
||||||
|
* (bluefox) initial release
|
||||||
|
|
||||||
|
## License
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 @@Author@@ <@@email@@>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
9
admin/i18n/de/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"My select": "Mein Auswahl",
|
||||||
|
"on save adapter restarts with new config immediately": "Beim Speichern von Einstellungen der Adapter wird sofort neu gestartet.",
|
||||||
|
"template adapter settings": "Beispiel",
|
||||||
|
"test1": "Test 1",
|
||||||
|
"test2": "Test 2"
|
||||||
|
}
|
9
admin/i18n/en/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"My select": "My select",
|
||||||
|
"on save adapter restarts with new config immediately": "on save adapter restarts with new config immediately",
|
||||||
|
"template adapter settings": "template adapter settings",
|
||||||
|
"test1": "Test 1",
|
||||||
|
"test2": "Test 2"
|
||||||
|
}
|
9
admin/i18n/es/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"My select": "Mi seleccion",
|
||||||
|
"on save adapter restarts with new config immediately": "en el adaptador de guardar se reinicia con nueva configuración de inmediato",
|
||||||
|
"template adapter settings": "configuración del adaptador de plantilla",
|
||||||
|
"test1": "Prueba 1",
|
||||||
|
"test2": "Prueba 2"
|
||||||
|
}
|
9
admin/i18n/fr/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manuel",
|
||||||
|
"My select": "Mon choix",
|
||||||
|
"on save adapter restarts with new config immediately": "sur l'adaptateur de sauvegarde redémarre avec la nouvelle config immédiatement",
|
||||||
|
"template adapter settings": "paramètres de l'adaptateur de modèle",
|
||||||
|
"test1": "Test 1",
|
||||||
|
"test2": "Test 2"
|
||||||
|
}
|
9
admin/i18n/it/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manuale",
|
||||||
|
"My select": "La mia selezione",
|
||||||
|
"on save adapter restarts with new config immediately": "su save adapter si riavvia immediatamente con la nuova configurazione",
|
||||||
|
"template adapter settings": "impostazioni dell'adattatore del modello",
|
||||||
|
"test1": "Test 1",
|
||||||
|
"test2": "Test 2"
|
||||||
|
}
|
9
admin/i18n/nl/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Met de hand",
|
||||||
|
"My select": "Mijn select",
|
||||||
|
"on save adapter restarts with new config immediately": "on save-adapter wordt onmiddellijk opnieuw opgestart met nieuwe config",
|
||||||
|
"template adapter settings": "sjabloon-adapterinstellingen",
|
||||||
|
"test1": "Test 1",
|
||||||
|
"test2": "Test 2"
|
||||||
|
}
|
9
admin/i18n/pt/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Auto",
|
||||||
|
"Manual": "Manual",
|
||||||
|
"My select": "Meu selecionado",
|
||||||
|
"on save adapter restarts with new config immediately": "no adaptador de salvar reinicia com nova configuração imediatamente",
|
||||||
|
"template adapter settings": "configurações do adaptador de modelo",
|
||||||
|
"test1": "Teste 1",
|
||||||
|
"test2": "Teste 2"
|
||||||
|
}
|
9
admin/i18n/ru/translations.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Auto": "Автоматически",
|
||||||
|
"Manual": "Вручную",
|
||||||
|
"My select": "Выбор",
|
||||||
|
"on save adapter restarts with new config immediately": "При сохранении настроек адаптера он сразу же перезапускается",
|
||||||
|
"template adapter settings": "Пример",
|
||||||
|
"test1": "Тест 1",
|
||||||
|
"test2": "Тест 2"
|
||||||
|
}
|
89
admin/index.html
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<html>
|
||||||
|
<!-- This file is deprecated!!!!! Please use index_m.html -->
|
||||||
|
<!-- This file is required only for backward compatibility and will be deleted soon -->
|
||||||
|
<!-- 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>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- optional: use jqGrid
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../lib/css/jqGrid/ui.jqgrid-4.5.4.css"/>
|
||||||
|
<script type="text/javascript" src="../../lib/js/jqGrid/jquery.jqGrid-4.5.4.min.js"></script>
|
||||||
|
<script type="text/javascript" src="../../lib/js/jqGrid/i18n/grid.locale-all.js"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- optional: use multiselect
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../lib/css/jquery.multiselect-1.13.css"/>
|
||||||
|
<script type="text/javascript" src="../../lib/js/jquery.multiselect-1.13.min.js"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- these two file always have to be included -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
|
||||||
|
<script type="text/javascript" src="../../js/translate.js"></script>
|
||||||
|
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
|
||||||
|
<script type="text/javascript" src="words.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- you have to define 2 functions in the global scope: -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
// the function loadSettings has to exist ...
|
||||||
|
function load(settings, onChange) {
|
||||||
|
// example: select elements with id=key and class=value and insert value
|
||||||
|
if (!settings) return;
|
||||||
|
$('.value').each(function () {
|
||||||
|
var $key = $(this);
|
||||||
|
var id = $key.attr('id');
|
||||||
|
if ($key.attr('type') === 'checkbox') {
|
||||||
|
// do not call onChange direct, because onChange could expect some arguments
|
||||||
|
$key.prop('checked', settings[id]).change(function() {
|
||||||
|
onChange();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// do not call onChange direct, because onChange could expect some arguments
|
||||||
|
$key.val(settings[id]).change(function() {
|
||||||
|
onChange();
|
||||||
|
}).keyup(function() {
|
||||||
|
onChange();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and the function save has to exist.
|
||||||
|
// you have to make sure the callback is called with the settings object as first param!
|
||||||
|
function save(callback) {
|
||||||
|
// example: select elements with class=value and build settings object
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
callback(obj);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- you have to put your config page in a div with id adapter-container -->
|
||||||
|
<div id="adapter-container">
|
||||||
|
|
||||||
|
<table><tr>
|
||||||
|
<td><img src="template.png"/></td>
|
||||||
|
<td><h3 class="translate">template adapter settings</h3></td>
|
||||||
|
</tr></table>
|
||||||
|
<p>
|
||||||
|
<span><label for="test1" class="translate">test1</label></span><input class="value" id="test1"/><br>
|
||||||
|
<span><label for="test2" class="translate">test2</label></span><input class="value" id="test2"/><br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="translate">on save adapter restarts with new config immediately</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</html>
|
128
admin/index_m.html
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- these 4 files always have to be included -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="../../css/adapter.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>
|
||||||
|
|
||||||
|
<!-- these files always have to be included -->
|
||||||
|
<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>
|
||||||
|
<style>
|
||||||
|
.m .col .select-wrapper+label {
|
||||||
|
top: -26px;
|
||||||
|
}
|
||||||
|
.m span{
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- you have to define 2 functions in the global scope: -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
// the function loadSettings has to exist ...
|
||||||
|
function load(settings, onChange) {
|
||||||
|
// example: select elements with id=key and class=value and insert value
|
||||||
|
if (!settings) return;
|
||||||
|
$('.value').each(function () {
|
||||||
|
var $key = $(this);
|
||||||
|
var id = $key.attr('id');
|
||||||
|
if ($key.attr('type') === 'checkbox') {
|
||||||
|
// do not call onChange direct, because onChange could expect some arguments
|
||||||
|
$key.prop('checked', settings[id]).on('change', function() {
|
||||||
|
onChange();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// do not call onChange direct, because onChange could expect some arguments
|
||||||
|
$key.val(settings[id]).on('change', function() {
|
||||||
|
onChange();
|
||||||
|
}).on('keyup', function() {
|
||||||
|
onChange();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onChange(false);
|
||||||
|
M.updateTextFields(); // function Materialize.updateTextFields(); to reinitialize all the Materialize labels on the page if you are dynamically adding inputs.
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and the function save has to exist.
|
||||||
|
// you have to make sure the callback is called with the settings object as first param!
|
||||||
|
function save(callback) {
|
||||||
|
// example: select elements with class=value and build settings object
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
callback(obj);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- you have to put your config page in a div with id adapter-container -->
|
||||||
|
<div class="m adapter-container">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Forms are the standard way to receive user inputted data.
|
||||||
|
Learn more http://materializecss.com/forms.html-->
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<img src="template.png" class="logo">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s3">
|
||||||
|
<input class="value" id="test1" type="checkbox" />
|
||||||
|
<label for="test1" class="translate">test1</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12 m6 l4">
|
||||||
|
<input class="value" id="myText" type="text">
|
||||||
|
<label for="myText">Text</label>
|
||||||
|
<span class="translate">Descriptions of the input field</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12 m6 l4">
|
||||||
|
<input type="number" class="value" id="test2" />
|
||||||
|
<label for="test2" class="translate">Number</label>
|
||||||
|
<!-- Important: label must come directly after input. Label is important. -->
|
||||||
|
<span class="translate">test2</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12 m6 l4">
|
||||||
|
<input id="email" type="email" class="value validate">
|
||||||
|
<label for="email" data-error="wrong" data-success="right">Email</label>
|
||||||
|
<!-- You can add custom validation messages by adding either data-error or data-success attributes to your input field labels.-->
|
||||||
|
<span class="translate">Verification input</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12 m4">
|
||||||
|
<select class="value" id="mySelect">
|
||||||
|
<option value="auto" class="translate">Auto</option>
|
||||||
|
<option value="manual" class="translate">Manual</option>
|
||||||
|
</select>
|
||||||
|
<label for="mySelect" class="translate">My select</label>
|
||||||
|
<!-- Important: label must come directly after select. Label is important. -->
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s12 m8">
|
||||||
|
<i class="material-icons prefix">mode_edit</i>
|
||||||
|
<textarea id="Textarea" class="value materialize-textarea"></textarea>
|
||||||
|
<label for="Textarea">Message</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<p class="translate">on save adapter restarts with new config immediately</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
BIN
admin/template.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
13
admin/words.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
|
||||||
|
/*global systemDictionary:true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
systemDictionary = {
|
||||||
|
"Auto": { "en": "Auto", "de": "Auto", "ru": "Автоматически", "pt": "Auto", "nl": "Auto", "fr": "Auto", "it": "Auto", "es": "Auto"},
|
||||||
|
"Manual": { "en": "Manual", "de": "Manual", "ru": "Вручную", "pt": "Manual", "nl": "Met de hand", "fr": "Manuel", "it": "Manuale", "es": "Manual"},
|
||||||
|
"My select": { "en": "My select", "de": "Mein Auswahl", "ru": "Выбор", "pt": "Meu selecionado", "nl": "Mijn select", "fr": "Mon choix", "it": "La mia selezione", "es": "Mi seleccion"},
|
||||||
|
"on save adapter restarts with new config immediately": {"en": "on save adapter restarts with new config immediately", "de": "Beim Speichern von Einstellungen der Adapter wird sofort neu gestartet.", "ru": "При сохранении настроек адаптера он сразу же перезапускается", "pt": "no adaptador de salvar reinicia com nova configuração imediatamente", "nl": "on save-adapter wordt onmiddellijk opnieuw opgestart met nieuwe config", "fr": "sur l'adaptateur de sauvegarde redémarre avec la nouvelle config immédiatement", "it": "su save adapter si riavvia immediatamente con la nuova configurazione", "es": "en el adaptador de guardar se reinicia con nueva configuración de inmediato"},
|
||||||
|
"template adapter settings": { "en": "template adapter settings", "de": "Beispiel", "ru": "Пример", "pt": "configurações do adaptador de modelo", "nl": "sjabloon-adapterinstellingen", "fr": "paramètres de l'adaptateur de modèle", "it": "impostazioni dell'adattatore del modello", "es": "configuración del adaptador de plantilla"},
|
||||||
|
"test1": { "en": "Test 1", "de": "Test 1", "ru": "Тест 1", "pt": "Teste 1", "nl": "Test 1", "fr": "Test 1", "it": "Test 1", "es": "Prueba 1"},
|
||||||
|
"test2": { "en": "Test 2", "de": "Test 2", "ru": "Тест 2", "pt": "Teste 2", "nl": "Test 2", "fr": "Test 2", "it": "Test 2", "es": "Prueba 2"}
|
||||||
|
};
|
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'
|
BIN
docs/de/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/de/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Das ist die Dokumentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/en/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/en/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/es/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/es/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/fr/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/fr/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/it/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/it/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/nl/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/nl/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/pt/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/pt/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This is Documentation
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
BIN
docs/ru/img/picture.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
3
docs/ru/template.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Это документация
|
||||||
|
|
||||||
|
(Picture)[img/picture.png)
|
489
gulpfile.js
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var fs = require('fs');
|
||||||
|
var pkg = require('./package.json');
|
||||||
|
var iopackage = require('./io-package.json');
|
||||||
|
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
|
||||||
|
/*var appName = getAppName();
|
||||||
|
|
||||||
|
function getAppName() {
|
||||||
|
var parts = __dirname.replace(/\\/g, '/').split('/');
|
||||||
|
return parts[parts.length - 1].split('.')[0].toLowerCase();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const fileName = 'words.js';
|
||||||
|
var languages = {
|
||||||
|
en: {},
|
||||||
|
de: {},
|
||||||
|
ru: {},
|
||||||
|
pt: {},
|
||||||
|
nl: {},
|
||||||
|
fr: {},
|
||||||
|
it: {},
|
||||||
|
es: {},
|
||||||
|
pl: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function lang2data(lang, isFlat) {
|
||||||
|
var str = isFlat ? '' : '{\n';
|
||||||
|
var count = 0;
|
||||||
|
for (var w in lang) {
|
||||||
|
if (lang.hasOwnProperty(w)) {
|
||||||
|
count++;
|
||||||
|
if (isFlat) {
|
||||||
|
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
|
||||||
|
} else {
|
||||||
|
var 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 {
|
||||||
|
var words;
|
||||||
|
if (fs.existsSync(src + 'js/' + fileName)) {
|
||||||
|
words = fs.readFileSync(src + 'js/' + fileName).toString();
|
||||||
|
} else {
|
||||||
|
words = fs.readFileSync(src + fileName).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = words.split(/\r\n|\r|\n/g);
|
||||||
|
var 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');
|
||||||
|
var 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) {
|
||||||
|
var text = '';
|
||||||
|
text += '/*global systemDictionary:true */\n';
|
||||||
|
text += '\'use strict\';\n\n';
|
||||||
|
text += 'systemDictionary = {\n';
|
||||||
|
for (var word in data) {
|
||||||
|
if (data.hasOwnProperty(word)) {
|
||||||
|
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
|
||||||
|
var line = '';
|
||||||
|
for (var 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) {
|
||||||
|
var langs = Object.assign({}, languages);
|
||||||
|
var data = readWordJs(src);
|
||||||
|
if (data) {
|
||||||
|
for (var word in data) {
|
||||||
|
if (data.hasOwnProperty(word)) {
|
||||||
|
for (var lang in data[word]) {
|
||||||
|
if (data[word].hasOwnProperty(lang)) {
|
||||||
|
langs[lang][word] = data[word][lang];
|
||||||
|
// pre-fill all other languages
|
||||||
|
for (var j in langs) {
|
||||||
|
if (langs.hasOwnProperty(j)) {
|
||||||
|
langs[j][word] = langs[j][word] || EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(src + 'i18n/')) {
|
||||||
|
fs.mkdirSync(src + 'i18n/');
|
||||||
|
}
|
||||||
|
for (var l in langs) {
|
||||||
|
if (!langs.hasOwnProperty(l)) continue;
|
||||||
|
var keys = Object.keys(langs[l]);
|
||||||
|
keys.sort();
|
||||||
|
var obj = {};
|
||||||
|
for (var 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) {
|
||||||
|
var langs = Object.assign({}, languages);
|
||||||
|
var data = readWordJs(src);
|
||||||
|
if (data) {
|
||||||
|
for (var word in data) {
|
||||||
|
if (data.hasOwnProperty(word)) {
|
||||||
|
for (var lang in data[word]) {
|
||||||
|
if (data[word].hasOwnProperty(lang)) {
|
||||||
|
langs[lang][word] = data[word][lang];
|
||||||
|
// pre-fill all other languages
|
||||||
|
for (var j in langs) {
|
||||||
|
if (langs.hasOwnProperty(j)) {
|
||||||
|
langs[j][word] = langs[j][word] || EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var keys = Object.keys(langs.en);
|
||||||
|
keys.sort();
|
||||||
|
for (var l in langs) {
|
||||||
|
if (!langs.hasOwnProperty(l)) continue;
|
||||||
|
var obj = {};
|
||||||
|
for (var 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 (var 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) {
|
||||||
|
var dirs = fs.readdirSync(src + 'i18n/');
|
||||||
|
var langs = {};
|
||||||
|
var bigOne = {};
|
||||||
|
var order = Object.keys(languages);
|
||||||
|
dirs.sort(function (a, b) {
|
||||||
|
var posA = order.indexOf(a);
|
||||||
|
var 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
|
||||||
|
|
||||||
|
for (var l = 0; l < dirs.length; l++) {
|
||||||
|
if (dirs[l] === 'flat.txt') continue;
|
||||||
|
var lang = dirs[l];
|
||||||
|
var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
|
||||||
|
langs[lang] = {};
|
||||||
|
keys.forEach(function (word, i) {
|
||||||
|
langs[lang][word] = values[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
var words = langs[lang];
|
||||||
|
for (var word in words) {
|
||||||
|
if (words.hasOwnProperty(word)) {
|
||||||
|
bigOne[word] = bigOne[word] || {};
|
||||||
|
if (words[word] !== EMPTY) {
|
||||||
|
bigOne[word][lang] = words[word];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read actual words.js
|
||||||
|
var aWords = readWordJs();
|
||||||
|
|
||||||
|
var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
|
||||||
|
if (aWords) {
|
||||||
|
// Merge words together
|
||||||
|
for (var 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) {
|
||||||
|
var dirs = fs.readdirSync(src + 'i18n/');
|
||||||
|
var langs = {};
|
||||||
|
var bigOne = {};
|
||||||
|
var order = Object.keys(languages);
|
||||||
|
dirs.sort(function (a, b) {
|
||||||
|
var posA = order.indexOf(a);
|
||||||
|
var 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 (var l = 0; l < dirs.length; l++) {
|
||||||
|
if (dirs[l] === 'flat.txt') continue;
|
||||||
|
var lang = dirs[l];
|
||||||
|
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
|
||||||
|
langs[lang] = JSON.parse(langs[lang]);
|
||||||
|
var words = langs[lang];
|
||||||
|
for (var word in words) {
|
||||||
|
if (words.hasOwnProperty(word)) {
|
||||||
|
bigOne[word] = bigOne[word] || {};
|
||||||
|
if (words[word] !== EMPTY) {
|
||||||
|
bigOne[word][lang] = words[word];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// read actual words.js
|
||||||
|
var aWords = readWordJs();
|
||||||
|
|
||||||
|
var temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
|
||||||
|
if (aWords) {
|
||||||
|
// Merge words together
|
||||||
|
for (var 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]) {
|
||||||
|
var news = iopackage.common.news;
|
||||||
|
var 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('rename', function () {
|
||||||
|
var newname;
|
||||||
|
var author = '@@Author@@';
|
||||||
|
var email = '@@email@@';
|
||||||
|
for (var a = 0; a < process.argv.length; a++) {
|
||||||
|
if (process.argv[a] === '--name') {
|
||||||
|
newname = process.argv[a + 1]
|
||||||
|
} else if (process.argv[a] === '--email') {
|
||||||
|
email = process.argv[a + 1]
|
||||||
|
} else if (process.argv[a] === '--author') {
|
||||||
|
author = process.argv[a + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log('Try to rename to "' + newname + '"');
|
||||||
|
if (!newname) {
|
||||||
|
console.log('Please write the new template name, like: "gulp rename --name mywidgetset" --author "Author Name"');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (newname.indexOf(' ') !== -1) {
|
||||||
|
console.log('Name may not have space in it.');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (newname.toLowerCase() !== newname) {
|
||||||
|
console.log('Name must be lower case.');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
if (fs.existsSync(__dirname + '/admin/template.png')) {
|
||||||
|
fs.renameSync(__dirname + '/admin/template.png', __dirname + '/admin/' + newname + '.png');
|
||||||
|
}
|
||||||
|
if (fs.existsSync(__dirname + '/widgets/template.html')) {
|
||||||
|
fs.renameSync(__dirname + '/widgets/template.html', __dirname + '/widgets/' + newname + '.html');
|
||||||
|
}
|
||||||
|
if (fs.existsSync(__dirname + '/widgets/template/js/template.js')) {
|
||||||
|
fs.renameSync(__dirname + '/widgets/template/js/template.js', __dirname + '/widgets/template/js/' + newname + '.js');
|
||||||
|
}
|
||||||
|
if (fs.existsSync(__dirname + '/widgets/template')) {
|
||||||
|
fs.renameSync(__dirname + '/widgets/template', __dirname + '/widgets/' + newname);
|
||||||
|
}
|
||||||
|
var patterns = [
|
||||||
|
{
|
||||||
|
match: /ioBroker template Adapter/g,
|
||||||
|
replacement: newname
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /template/g,
|
||||||
|
replacement: newname
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /Template/g,
|
||||||
|
replacement: newname ? (newname[0].toUpperCase() + newname.substring(1)) : 'Template'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /@@Author@@/g,
|
||||||
|
replacement: author
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /@@email@@/g,
|
||||||
|
replacement: email
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var files = [
|
||||||
|
__dirname + '/io-package.json',
|
||||||
|
__dirname + '/LICENSE',
|
||||||
|
__dirname + '/package.json',
|
||||||
|
__dirname + '/README.md',
|
||||||
|
__dirname + '/main.js',
|
||||||
|
__dirname + '/gulpfile.js',
|
||||||
|
__dirname + '/widgets/' + newname +'.html',
|
||||||
|
__dirname + '/www/index.html',
|
||||||
|
__dirname + '/admin/index.html',
|
||||||
|
__dirname + '/admin/index_m.html',
|
||||||
|
__dirname + '/widgets/' + newname + '/js/' + newname +'.js',
|
||||||
|
__dirname + '/widgets/' + newname + '/css/style.css'
|
||||||
|
];
|
||||||
|
files.forEach(function (f) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(f)) {
|
||||||
|
var data = fs.readFileSync(f).toString('utf-8');
|
||||||
|
for (var r = 0; r < patterns.length; r++) {
|
||||||
|
data = data.replace(patterns[r].match, patterns[r].replacement);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(f, data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('updateReadme', function (done) {
|
||||||
|
var readme = fs.readFileSync('README.md').toString();
|
||||||
|
var pos = readme.indexOf('## Changelog\n');
|
||||||
|
if (pos !== -1) {
|
||||||
|
var readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
|
||||||
|
var readmeEnd = readme.substring(pos + '## Changelog\n'.length);
|
||||||
|
|
||||||
|
if (readme.indexOf(version) === -1) {
|
||||||
|
var timestamp = new Date();
|
||||||
|
var date = timestamp.getFullYear() + '-' +
|
||||||
|
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
|
||||||
|
('0' + (timestamp.getDate()).toString(10)).slice(-2);
|
||||||
|
|
||||||
|
var 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']);
|
89
io-package.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"common": {
|
||||||
|
"name": "template",
|
||||||
|
"version": "0.6.0",
|
||||||
|
"news": {
|
||||||
|
"0.6.0": {
|
||||||
|
"en": "Support of admin3",
|
||||||
|
"de": "Unterstützung von admin3",
|
||||||
|
"ru": "Поддержка admin3",
|
||||||
|
"pt": "Suporte de admin3",
|
||||||
|
"nl": "Ondersteuning van admin3",
|
||||||
|
"fr": "Support de admin3",
|
||||||
|
"it": "Supporto di admin3",
|
||||||
|
"es": "Soporte de admin3"
|
||||||
|
},
|
||||||
|
"0.5.0": {
|
||||||
|
"en": "beta version",
|
||||||
|
"de": "Betaversion",
|
||||||
|
"ru": "Бета версия",
|
||||||
|
"pt": "Versão beta",
|
||||||
|
"fr": "Version bêta",
|
||||||
|
"nl": "Beta versie"
|
||||||
|
},
|
||||||
|
"0.0.1": {
|
||||||
|
"en": "initial adapter",
|
||||||
|
"de": "Initiale Version",
|
||||||
|
"ru": "Первоначальный адаптер",
|
||||||
|
"pt": "Versão inicial",
|
||||||
|
"fr": "Version initiale",
|
||||||
|
"nl": "Eerste release"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Javascript/Node.js based template",
|
||||||
|
"titleLang": {
|
||||||
|
"en": "Javascript/Node.js based template",
|
||||||
|
"de": "Javascript / Node.js basierte Vorlagenadapter",
|
||||||
|
"ru": "Адаптер шаблонов на основе Javascript / Node.js",
|
||||||
|
"pt": "Adaptador de modelo baseado em Javascript / Node.js",
|
||||||
|
"nl": "Javascript / Node.js gebaseerde sjabloonadapter",
|
||||||
|
"fr": "Javascript / Node.js basé adaptateur de modèle",
|
||||||
|
"it": "Adattatore modello basato su Javascript / Node.js",
|
||||||
|
"es": "Adaptador de plantilla basado en JavaScript / Node.js"
|
||||||
|
},
|
||||||
|
"desc": {
|
||||||
|
"en": "ioBroker template",
|
||||||
|
"de": "ioBroker Template",
|
||||||
|
"ru": "ioBroker Template как образец",
|
||||||
|
"pt": "Modelo de adaptador para o ioBroker",
|
||||||
|
"fr": "ioBroker adaptateur modèle",
|
||||||
|
"nl": "ioBroker Template",
|
||||||
|
"it": "Adattatore modello ioBroker",
|
||||||
|
"es": "Adaptador de plantilla ioBroker"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
"@@Author@@ <@@email@@>"
|
||||||
|
],
|
||||||
|
"docs": {
|
||||||
|
"en": "docs/en/admin.md",
|
||||||
|
"ru": "docs/ru/admin.md",
|
||||||
|
"de": "docs/de/admin.md",
|
||||||
|
"es": "docs/es/admin.md",
|
||||||
|
"it": "docs/it/admin.md",
|
||||||
|
"fr": "docs/fr/admin.md",
|
||||||
|
"nl": "docs/nl/admin.md",
|
||||||
|
"pt": "docs/pt/admin.md"
|
||||||
|
},
|
||||||
|
"platform": "Javascript/Node.js",
|
||||||
|
"mode": "daemon",
|
||||||
|
"icon": "template.png",
|
||||||
|
"materialize": true,
|
||||||
|
"enabled": true,
|
||||||
|
"extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.template/master/admin/template.png",
|
||||||
|
"keywords": ["template", "vis", "GUI", "graphical", "scada"],
|
||||||
|
"readme": "https://github.com/ioBroker/ioBroker.template/blob/master/README.md",
|
||||||
|
"loglevel": "info",
|
||||||
|
"type": "general",
|
||||||
|
"license": "MIT",
|
||||||
|
"messagebox": false,
|
||||||
|
"restartAdapters": ["vis"]
|
||||||
|
},
|
||||||
|
"native": {
|
||||||
|
"test1": true,
|
||||||
|
"test2": 42,
|
||||||
|
"mySelect": "auto"
|
||||||
|
},
|
||||||
|
"objects": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
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;
|
158
main.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* template adapter
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* file io-package.json comments:
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "common": {
|
||||||
|
* "name": "template", // name has to be set and has to be equal to adapters folder name and main file name excluding extension
|
||||||
|
* "version": "0.0.0", // use "Semantic Versioning"! see http://semver.org/
|
||||||
|
* "title": "Node.js template Adapter", // Adapter title shown in User Interfaces
|
||||||
|
* "authors": [ // Array of authord
|
||||||
|
* "name <mail@template.com>"
|
||||||
|
* ]
|
||||||
|
* "desc": "template adapter", // Adapter description shown in User Interfaces. Can be a language object {de:"...",ru:"..."} or a string
|
||||||
|
* "platform": "Javascript/Node.js", // possible values "javascript", "javascript/Node.js" - more coming
|
||||||
|
* "mode": "daemon", // possible values "daemon", "schedule", "subscribe"
|
||||||
|
* "materialize": true, // support of admin3
|
||||||
|
* "schedule": "0 0 * * *" // cron-style schedule. Only needed if mode=schedule
|
||||||
|
* "loglevel": "info" // Adapters Log Level
|
||||||
|
* },
|
||||||
|
* "native": { // the native object is available via adapter.config in your adapters code - use it for configuration
|
||||||
|
* "test1": true,
|
||||||
|
* "test2": 42,
|
||||||
|
* "mySelect": "auto"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* jshint -W097 */// jshint strict:false
|
||||||
|
/*jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// you have to require the utils module and call adapter function
|
||||||
|
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
|
||||||
|
|
||||||
|
// you have to call the adapter function and pass a options object
|
||||||
|
// name has to be set and has to be equal to adapters folder name and main file name excluding extension
|
||||||
|
// adapter will be restarted automatically every time as the configuration changed, e.g system.adapter.template.0
|
||||||
|
const adapter = new utils.Adapter('template');
|
||||||
|
|
||||||
|
/*Variable declaration, since ES6 there are let to declare variables. Let has a more clearer definition where
|
||||||
|
it is available then var.The variable is available inside a block and it's childs, but not outside.
|
||||||
|
You can define the same variable name inside a child without produce a conflict with the variable of the parent block.*/
|
||||||
|
let variable = 1234;
|
||||||
|
|
||||||
|
// is called when adapter shuts down - callback has to be called under any circumstances!
|
||||||
|
adapter.on('unload', function (callback) {
|
||||||
|
try {
|
||||||
|
adapter.log.info('cleaned everything up...');
|
||||||
|
callback();
|
||||||
|
} catch (e) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// is called if a subscribed object changes
|
||||||
|
adapter.on('objectChange', function (id, obj) {
|
||||||
|
// Warning, obj can be null if it was deleted
|
||||||
|
adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj));
|
||||||
|
});
|
||||||
|
|
||||||
|
// is called if a subscribed state changes
|
||||||
|
adapter.on('stateChange', function (id, state) {
|
||||||
|
// Warning, state can be null if it was deleted
|
||||||
|
adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state));
|
||||||
|
|
||||||
|
// you can use the ack flag to detect if it is status (true) or command (false)
|
||||||
|
if (state && !state.ack) {
|
||||||
|
adapter.log.info('ack is not set!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ...
|
||||||
|
adapter.on('message', function (obj) {
|
||||||
|
if (typeof obj === 'object' && obj.message) {
|
||||||
|
if (obj.command === 'send') {
|
||||||
|
// e.g. send email or pushover or whatever
|
||||||
|
console.log('send command');
|
||||||
|
|
||||||
|
// Send response in callback if required
|
||||||
|
if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// is called when databases are connected and adapter received configuration.
|
||||||
|
// start here!
|
||||||
|
adapter.on('ready', function () {
|
||||||
|
main();
|
||||||
|
});
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
|
||||||
|
// The adapters config (in the instance object everything under the attribute "native") is accessible via
|
||||||
|
// adapter.config:
|
||||||
|
adapter.log.info('config test1: ' + adapter.config.test1);
|
||||||
|
adapter.log.info('config test1: ' + adapter.config.test2);
|
||||||
|
adapter.log.info('config mySelect: ' + adapter.config.mySelect);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* For every state in the system there has to be also an object of type state
|
||||||
|
*
|
||||||
|
* Here a simple template for a boolean variable named "testVariable"
|
||||||
|
*
|
||||||
|
* Because every adapter instance uses its own unique namespace variable names can't collide with other adapters variables
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
adapter.setObject('testVariable', {
|
||||||
|
type: 'state',
|
||||||
|
common: {
|
||||||
|
name: 'testVariable',
|
||||||
|
type: 'boolean',
|
||||||
|
role: 'indicator'
|
||||||
|
},
|
||||||
|
native: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// in this template all states changes inside the adapters namespace are subscribed
|
||||||
|
adapter.subscribeStates('*');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setState examples
|
||||||
|
*
|
||||||
|
* you will notice that each setState will cause the stateChange event to fire (because of above subscribeStates cmd)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// the variable testVariable is set to true as command (ack=false)
|
||||||
|
adapter.setState('testVariable', true);
|
||||||
|
|
||||||
|
// same thing, but the value is flagged "ack"
|
||||||
|
// ack should be always set to true if the value is received from or acknowledged from the target system
|
||||||
|
adapter.setState('testVariable', {val: true, ack: true});
|
||||||
|
|
||||||
|
// same thing, but the state is deleted after 30s (getState will return null afterwards)
|
||||||
|
adapter.setState('testVariable', {val: true, ack: true, expire: 30});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// examples for the checkPassword/checkGroup functions
|
||||||
|
adapter.checkPassword('admin', 'iobroker', function (res) {
|
||||||
|
console.log('check user admin pw ioboker: ' + res);
|
||||||
|
});
|
||||||
|
|
||||||
|
adapter.checkGroup('admin', 'admin', function (res) {
|
||||||
|
console.log('check group user admin group admin: ' + res);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
41
package.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "iobroker.template",
|
||||||
|
"version": "0.6.0",
|
||||||
|
"description": "ioBroker template Adapter",
|
||||||
|
"author": {
|
||||||
|
"name": "@@Author@@",
|
||||||
|
"email": "@@email@@"
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "@@Author@@",
|
||||||
|
"email": "@@email@@"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/ioBroker/ioBroker.template",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"ioBroker",
|
||||||
|
"template",
|
||||||
|
"Smart Home",
|
||||||
|
"home automation"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ioBroker/ioBroker.template"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"mocha": "^4.1.0",
|
||||||
|
"chai": "^4.1.2"
|
||||||
|
},
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node node_modules/mocha/bin/mocha --exit"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ioBroker/ioBroker.template/issues"
|
||||||
|
},
|
||||||
|
"readmeFilename": "README.md"
|
||||||
|
}
|
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;
|
||||||
|
}
|
140
test/testAdapter.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/* jshint -W097 */// jshint strict:false
|
||||||
|
/*jslint node: true */
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
var setup = require(__dirname + '/lib/setup');
|
||||||
|
|
||||||
|
var objects = null;
|
||||||
|
var states = null;
|
||||||
|
var onStateChanged = null;
|
||||||
|
var onObjectChanged = null;
|
||||||
|
var sendToID = 1;
|
||||||
|
|
||||||
|
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1);
|
||||||
|
|
||||||
|
function checkConnectionOfAdapter(cb, counter) {
|
||||||
|
counter = counter || 0;
|
||||||
|
console.log('Try check #' + counter);
|
||||||
|
if (counter > 30) {
|
||||||
|
if (cb) cb('Cannot check connection');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) {
|
||||||
|
if (err) console.error(err);
|
||||||
|
if (state && state.val) {
|
||||||
|
if (cb) cb();
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
checkConnectionOfAdapter(cb, counter + 1);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValueOfState(id, value, cb, counter) {
|
||||||
|
counter = counter || 0;
|
||||||
|
if (counter > 20) {
|
||||||
|
if (cb) cb('Cannot check value Of State ' + id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
states.getState(id, function (err, state) {
|
||||||
|
if (err) console.error(err);
|
||||||
|
if (value === null && !state) {
|
||||||
|
if (cb) cb();
|
||||||
|
} else
|
||||||
|
if (state && (value === undefined || state.val === value)) {
|
||||||
|
if (cb) cb();
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
checkValueOfState(id, value, cb, counter + 1);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTo(target, command, message, callback) {
|
||||||
|
onStateChanged = function (id, state) {
|
||||||
|
if (id === 'messagebox.system.adapter.test.0') {
|
||||||
|
callback(state.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
states.pushMessage('system.adapter.' + target, {
|
||||||
|
command: command,
|
||||||
|
message: message,
|
||||||
|
from: 'system.adapter.test.0',
|
||||||
|
callback: {
|
||||||
|
message: message,
|
||||||
|
id: sendToID++,
|
||||||
|
ack: false,
|
||||||
|
time: (new Date()).getTime()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Test ' + adapterShortName + ' adapter', function() {
|
||||||
|
before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) {
|
||||||
|
this.timeout(600000); // because of first install from npm
|
||||||
|
|
||||||
|
setup.setupController(function () {
|
||||||
|
var config = setup.getAdapterConfig();
|
||||||
|
// enable adapter
|
||||||
|
config.common.enabled = true;
|
||||||
|
config.common.loglevel = 'debug';
|
||||||
|
|
||||||
|
//config.native.dbtype = 'sqlite';
|
||||||
|
|
||||||
|
setup.setAdapterConfig(config.common, config.native);
|
||||||
|
|
||||||
|
setup.startController(true, function(id, obj) {}, function (id, state) {
|
||||||
|
if (onStateChanged) onStateChanged(id, state);
|
||||||
|
},
|
||||||
|
function (_objects, _states) {
|
||||||
|
objects = _objects;
|
||||||
|
states = _states;
|
||||||
|
_done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
ENABLE THIS WHEN ADAPTER RUNS IN DEAMON MODE TO CHECK THAT IT HAS STARTED SUCCESSFULLY
|
||||||
|
*/
|
||||||
|
it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) {
|
||||||
|
this.timeout(60000);
|
||||||
|
checkConnectionOfAdapter(function (res) {
|
||||||
|
if (res) console.log(res);
|
||||||
|
expect(res).not.to.be.equal('Cannot check connection');
|
||||||
|
objects.setObject('system.adapter.test.0', {
|
||||||
|
common: {
|
||||||
|
|
||||||
|
},
|
||||||
|
type: 'instance'
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
states.subscribeMessage('system.adapter.test.0');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/**/
|
||||||
|
|
||||||
|
/*
|
||||||
|
PUT YOUR OWN TESTS HERE USING
|
||||||
|
it('Testname', function ( done) {
|
||||||
|
...
|
||||||
|
});
|
||||||
|
|
||||||
|
You can also use "sendTo" method to send messages to the started adapter
|
||||||
|
*/
|
||||||
|
|
||||||
|
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
|
||||||
|
this.timeout(10000);
|
||||||
|
|
||||||
|
setup.stopController(function (normalTerminated) {
|
||||||
|
console.log('Adapter normal terminated: ' + normalTerminated);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
91
test/testPackageFiles.js
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
138
widgets/template.html
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
<!--
|
||||||
|
ioBroker.template Widget-Set
|
||||||
|
|
||||||
|
version: "0.5.0"
|
||||||
|
|
||||||
|
Copyright 10.2015-2016 @@Author@@<@@email@@>
|
||||||
|
-->
|
||||||
|
<!-- here you can include so many css as you want -->
|
||||||
|
<link rel="stylesheet" href="widgets/template/css/style.css" />
|
||||||
|
<!-- here you can include so many js-files as you want -->
|
||||||
|
<script type="text/javascript" src="widgets/template/js/template.js"></script>
|
||||||
|
|
||||||
|
<!-- the full description of format in data-vis-attrs can be found here:
|
||||||
|
https://github.com/ioBroker/ioBroker.vis/blob/master/www/js/visEditInspect.js#L39
|
||||||
|
|
||||||
|
----------------------------------------------------------
|
||||||
|
Mandatory XML attributes:
|
||||||
|
id="tplTemplateWIDGETNAME"
|
||||||
|
type="text/ejs"
|
||||||
|
class="vis-tpl"
|
||||||
|
|
||||||
|
"type" and "class" are always the same. "id" is unique and must start with "tplSETNAME" and ends with widget name. Use camel-case for it.
|
||||||
|
|
||||||
|
----------------------------------------------------------
|
||||||
|
data-vis-attrs - divided with semicolon the list of attributes, like attr1/id;attr2/color
|
||||||
|
Full format of one attribute is: attr_name(start-end)[default_value]/type,idFilter/onChangeFunc
|
||||||
|
attr_name - the name of the attribute, e.g. "myID"
|
||||||
|
start-end - creates automatically attributes from attr_namestart to attr_nameend, like "myID(1-3)" creates myID1, myID2, myID3
|
||||||
|
default_value - by creation of widget this attribute will be filled with this value, e.g. "myID[#001122]/color"
|
||||||
|
type - some predefined types have edit helpers, else it will be shown as text field
|
||||||
|
|
||||||
|
Type format:
|
||||||
|
id - Object ID Dialog
|
||||||
|
checkbox
|
||||||
|
image - image
|
||||||
|
number,min,max,step - non-float number. min,max,step are optional
|
||||||
|
color - color picker
|
||||||
|
views - Name of the view
|
||||||
|
effect - jquery UI show/hide effects
|
||||||
|
eff_opt - additional option to effect slide (up, down, left, right)
|
||||||
|
fontName - Font name
|
||||||
|
slider,min,max,step - Default step is ((max - min) / 100)
|
||||||
|
select,value1,value2,... - dropdown select
|
||||||
|
nselect,value1,value2,... - same as select, but without translation of items
|
||||||
|
style,fileFilter,nameFilter,attrFilter
|
||||||
|
custom,functionName,options,... - custom editor - functionName is starting from vis.binds.[widgetset.funct]. E.g. custom/timeAndWeather.editWeather,short
|
||||||
|
group.name - define new or old group. All following attributes belongs to new group till new group.xyz
|
||||||
|
group.name/byindex - like group, but all following attributes will be grouped by ID. Like group.windows/byindex;slide(1-4)/id;slide_type(1-4)/select,open,closed Following groups will be created Windows1(slide1,slide_type1), Windows2(slide2,slide_type2), Windows3(slide3,slide_type3), Windows4(slide4,slide_type4)
|
||||||
|
text - dialog box with html editor
|
||||||
|
html - dialog box with html editor
|
||||||
|
|
||||||
|
If type is "id", you can define filer for "Select ID Dialog", like "myID/id,level.temperature".
|
||||||
|
Additionally you can define callback(onChangeFunc), which will be called if this attribute was changed by user for different purposes: validate entry, fill other attributes, ...
|
||||||
|
|
||||||
|
You can define additional data-vis-attrs line: data-vis-attrs0, data-vis-attrs1. Anyway data-vis-attrs must be there. You may not skip numbers.
|
||||||
|
E.g. in "data-vis-attrs="A;" data-vis-attrs1="B" attribute B will be not parsed.
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-type
|
||||||
|
Help information for user. Used for search.
|
||||||
|
You can define more than one type divided by comma.
|
||||||
|
There are following common types, but you can use your own specific types:
|
||||||
|
ctrl - widget that can write some Object IDs
|
||||||
|
val - widget that shows some information from Object IDs
|
||||||
|
static - widget do not read information from Object IDs or URL
|
||||||
|
button - button widget
|
||||||
|
dimmer - dimmer widget
|
||||||
|
weather - weather widget
|
||||||
|
...
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-set
|
||||||
|
Name of the widget set. Must be equal to the name of this HTML file
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-prev
|
||||||
|
Html code used as preview of this widget. If widget is complex you can just use image as preview:
|
||||||
|
data-vis-prev='<img src="widgets/hqwidgets/img/prev/Prev_tplTemplateShowInstance.png"></img>'
|
||||||
|
Of course the image must exist.
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-name
|
||||||
|
Readable name of widget shown in vis editor.
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-beta="true"
|
||||||
|
Shows "BETA" symbol in vis editor by preview
|
||||||
|
|
||||||
|
--------------------------------------------------------
|
||||||
|
data-vis-update-style="true"
|
||||||
|
Call redraw of widget if some of the CSS styles in editor for this widget was changed
|
||||||
|
|
||||||
|
|
||||||
|
You can read about Magic tags here: http://canjs.com/guides/EJS.html
|
||||||
|
Following magic tags are exist:
|
||||||
|
<% %> - execute javascript
|
||||||
|
<%= %> - place escaped result to HTML document
|
||||||
|
<%== %> - place unescaped result to HTML document
|
||||||
|
|
||||||
|
You can do "if" conditions and "for" cycles.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<script id="tplTemplateShowInstance"
|
||||||
|
type="text/ejs"
|
||||||
|
class="vis-tpl"
|
||||||
|
data-vis-prev='<div id="prev_tplMysetShowInstance" style="position: relative; text-align: initial;padding: 4px "><div class="vis-widget_prev " style="width: 280px; height: 159px;" > <div class="myset-class vis-widget-prev-body " style="padding:2px"> OID: hm-rpc.0.EEQ0006629.1.STATE<br> OID value: true<br> Color: <span style="color: rgb(128, 0, 0);">#800000</span><br> htmlText: <textarea readonly="" style="width:100%">asda</textarea></div>'
|
||||||
|
data-vis-attrs="oid/id;myColor/color;htmlText/text;"
|
||||||
|
data-vis-attrs0="group.extraTemplate;extraAttr"
|
||||||
|
data-vis-set="template"
|
||||||
|
data-vis-type="helper"
|
||||||
|
data-vis-name="Show instance">
|
||||||
|
<div class="vis-widget <%== this.data.attr('class') %>" style="width:230px; height:210px;" id="<%= this.data.attr('wid') %>" >
|
||||||
|
<div class="template-class vis-widget-body <%== this.data.attr('class') %>" style="padding:2px" >
|
||||||
|
OID: <%= this.data.attr('oid') %><br>
|
||||||
|
OID value: <%== vis.states[this.data.attr('oid') + '.val'] %><br>
|
||||||
|
Color: <span style="color: <%= this.data.attr('myColor') %>"><%= this.data.attr('myColor') %></span><br>
|
||||||
|
extraAttr: <%== this.data.attr('extraAttr') %><br>
|
||||||
|
Browser instance: <%= vis.instance %><br>
|
||||||
|
htmlText: <textarea readonly style="width: calc(100% - 10px)"><%== this.data.attr('htmlText') %></textarea><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="tplTemplateHelper"
|
||||||
|
type="text/ejs"
|
||||||
|
class="vis-tpl"
|
||||||
|
data-vis-prev='<div id="prev_tplTemplateHelper" style="position: relative; text-align: initial;padding: 4px "><div class="vis-widget_prev " style="width: 280px; height: 159px;" > <div class="myset-class vis-widget-prev-body " style="padding:2px"> OID: hm-rpc.0.EEQ0006629.1.STATE<br> OID value: true<br> Color: <span style="color: rgb(128, 0, 0);">#800000</span><br> htmlText: <textarea readonly="" style="width:100%">asda</textarea></div>'
|
||||||
|
data-vis-attrs="oid/id;myColor/color;htmlText/text;"
|
||||||
|
data-vis-attrs0="group.extraTemplate;extraAttr"
|
||||||
|
data-vis-set="template"
|
||||||
|
data-vis-type="helper"
|
||||||
|
data-vis-name="Helper">
|
||||||
|
<div class="vis-widget <%== this.data.attr('class') %>" style="overflow:visible; width: 230px; height: 210px" id="<%= this.data.attr('wid') %>"><%
|
||||||
|
vis.binds.template.createWidget(this.data.wid, this.view, this.data, this.style);
|
||||||
|
%></div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
3
widgets/template/css/style.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.template-class {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
BIN
widgets/template/img/test.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
68
widgets/template/js/template.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
ioBroker.template Widget-Set
|
||||||
|
|
||||||
|
version: "0.5.0"
|
||||||
|
|
||||||
|
Copyright 10.2015-2016 @@Author@@<@@email@@>
|
||||||
|
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// add translations for edit mode
|
||||||
|
if (vis.editMode) {
|
||||||
|
$.extend(true, systemDictionary, {
|
||||||
|
"myColor": {"en": "myColor", "de": "mainColor", "ru": "Мой цвет"},
|
||||||
|
"myColor_tooltip": {
|
||||||
|
"en": "Description of\x0AmyColor",
|
||||||
|
"de": "Beschreibung von\x0AmyColor",
|
||||||
|
"ru": "Описание\x0AmyColor"
|
||||||
|
},
|
||||||
|
"htmlText": {"en": "htmlText", "de": "htmlText", "ru": "htmlText"},
|
||||||
|
"group_extraMyset": {"en": "extraMyset", "de": "extraMyset", "ru": "extraMyset"},
|
||||||
|
"extraAttr": {"en": "extraAttr", "de": "extraAttr", "ru": "extraAttr"}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add translations for non-edit mode
|
||||||
|
$.extend(true, systemDictionary, {
|
||||||
|
"Instance": {"en": "Instance", "de": "Instanz", "ru": "Инстанция"}
|
||||||
|
});
|
||||||
|
|
||||||
|
// this code can be placed directly in template.html
|
||||||
|
vis.binds.template = {
|
||||||
|
version: "0.5.0",
|
||||||
|
showVersion: function () {
|
||||||
|
if (vis.binds.template.version) {
|
||||||
|
console.log('Version template: ' + vis.binds.template.version);
|
||||||
|
vis.binds.template.version = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createWidget: function (widgetID, view, data, style) {
|
||||||
|
var $div = $('#' + widgetID);
|
||||||
|
// if nothing found => wait
|
||||||
|
if (!$div.length) {
|
||||||
|
return setTimeout(function () {
|
||||||
|
vis.binds.template.createWidget(widgetID, view, data, style);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = '';
|
||||||
|
text += 'OID: ' + data.oid + '</div><br>';
|
||||||
|
text += 'OID value: <span class="myset-value">' + vis.states[data.oid + '.val'] + '</span><br>';
|
||||||
|
text += 'Color: <span style="color: ' + data.myColor + '">' + data.myColor + '</span><br>';
|
||||||
|
text += 'extraAttr: ' + data.extraAttr + '<br>';
|
||||||
|
text += 'Browser instance: ' + vis.instance + '<br>';
|
||||||
|
text += 'htmlText: <textarea readonly style="width:100%">' + (data.htmlText || '') + '</textarea><br>';
|
||||||
|
|
||||||
|
$('#' + widgetID).html(text);
|
||||||
|
|
||||||
|
// subscribe on updates of value
|
||||||
|
if (data.oid) {
|
||||||
|
vis.states.bind(data.oid + '.val', function (e, newVal, oldVal) {
|
||||||
|
$div.find('.template-value').html(newVal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vis.binds.template.showVersion();
|
2
www/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
if you put files in this directory they will be uploaded to DB on adapter install/upgrade.
|
||||||
|
they can then be accessed via ioBroker WEB adapter http://<iobrokerIP>:8082/<adapter-name>/...
|
4
www/index.html
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<h3>template adapter</h3>
|
||||||
|
<p>
|
||||||
|
|
||||||
|
</p>
|