Initial commit

This commit is contained in:
zhongjin 2018-09-25 10:42:52 +08:00
commit 3d6226b615
48 changed files with 2540 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.git
.idea
node_modules
nbproject
admin/i18n/flat.txt
admin/i18n/*/flat.txt

10
.npmignore Normal file
View 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
View 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
View 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
View 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://&lt;iobrokerIP&gt;:8082/&lt;adapter-name&gt;/
* 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.

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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"
}

View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

13
admin/words.js Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/de/template.md Normal file
View File

@ -0,0 +1,3 @@
# Das ist die Dokumentation
(Picture)[img/picture.png)

BIN
docs/en/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/en/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/es/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/es/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/fr/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/fr/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/it/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/it/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/nl/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/nl/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/pt/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/pt/template.md Normal file
View File

@ -0,0 +1,3 @@
# This is Documentation
(Picture)[img/picture.png)

BIN
docs/ru/img/picture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

3
docs/ru/template.md Normal file
View File

@ -0,0 +1,3 @@
# Это документация
(Picture)[img/picture.png)

489
gulpfile.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

View File

@ -0,0 +1,3 @@
.template-class {
font-style: italic;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View 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
View 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://&lt;iobrokerIP&gt;:8082/&lt;adapter-name&gt;/...

4
www/index.html Normal file
View File

@ -0,0 +1,4 @@
<h3>template adapter</h3>
<p>
</p>