Initial commit

This commit is contained in:
zhongjin 2018-09-23 14:32:21 +08:00
commit 8628696294
19 changed files with 1928 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/node_modules
/.idea

10
.npmignore Normal file
View File

@ -0,0 +1,10 @@
Gruntfile.js
tasks
node_modules
.idea
.gitignore
.git
.DS_Store
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://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

130
Gruntfile.js Normal file
View File

@ -0,0 +1,130 @@
// To use this file in WebStorm, right click on the file name in the Project Panel (normally left) and select "Open Grunt Console"
/** @namespace __dirname */
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
"use strict";
module.exports = function (grunt) {
var srcDir = __dirname + '/';
var dstDir = srcDir + '.build/';
var pkg = grunt.file.readJSON('package.json');
var iopackage = grunt.file.readJSON('io-package.json');
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
// Project configuration.
grunt.initConfig({
pkg: pkg,
replace: {
core: {
options: {
patterns: [
{
match: /var version = *'[\.0-9]*';/g,
replacement: "var version = '" + version + "';"
},
{
match: /"version"\: *"[\.0-9]*",/g,
replacement: '"version": "' + version + '",'
}
]
},
files: [
{
expand: true,
flatten: true,
src: [
srcDir + 'controller.js',
srcDir + 'package.json',
srcDir + 'io-package.json'
],
dest: srcDir
}
]
}
},
// Javascript code styler
jscs: require(__dirname + '/tasks/jscs.js'),
// Lint
jshint: require(__dirname + '/tasks/jshint.js'),
http: {
get_hjscs: {
options: {
url: 'https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/tasks/jscs.js'
},
dest: 'tasks/jscs.js'
},
get_jshint: {
options: {
url: 'https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/tasks/jshint.js'
},
dest: 'tasks/jshint.js'
},
get_gruntfile: {
options: {
url: 'https://raw.githubusercontent.com/yunkong2/yunkong2.build/master/adapters/Gruntfile.js'
},
dest: 'Gruntfile.js'
},
get_utilsfile: {
options: {
url: 'https://raw.githubusercontent.com/yunkong2/yunkong2.build/master/adapters/utils.js'
},
dest: 'lib/utils.js'
},
get_jscsRules: {
options: {
url: 'https://raw.githubusercontent.com/yunkong2/yunkong2.js-controller/master/tasks/jscsRules.js'
},
dest: 'tasks/jscsRules.js'
}
}
});
grunt.registerTask('updateReadme', function () {
var readme = grunt.file.read('README.md');
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.whatsNew) {
for (var i = 0; i < iopackage.common.whatsNew.length; i++) {
if (typeof iopackage.common.whatsNew[i] == 'string') {
news += '* ' + iopackage.common.whatsNew[i] + '\n';
} else {
news += '* ' + iopackage.common.whatsNew[i].en + '\n';
}
}
}
grunt.file.write('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
}
}
});
grunt.loadNpmTasks('grunt-replace');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-jscs');
grunt.loadNpmTasks('grunt-http');
grunt.registerTask('default', [
'http',
'replace',
'updateReadme',
'jshint',
'jscs'
]);
grunt.registerTask('p', [
'replace',
'updateReadme'
]);
};

92
README.md Normal file
View File

@ -0,0 +1,92 @@
![Logo](admin/geofency.png)
# yunkong2.geofency
====================
[![NPM version](http://img.shields.io/npm/v/yunkong2.geofency.svg)](https://www.npmjs.com/package/yunkong2.geofency)
[![Downloads](https://img.shields.io/npm/dm/yunkong2.geofency.svg)](https://www.npmjs.com/package/yunkong2.geofency)
[![NPM](https://nodei.co/npm/yunkong2.geofency.png?downloads=true)](https://nodei.co/npm/yunkong2.geofency/)
This Adapter is able to receive [geofency](http://www.geofency.com/) events when entering or leaving a defined area with your mobile device.
All values of the geofency-webhook of the request are stored under the name of the location in yunkong2.
## configuration on mobile device:
* for any location -> properties -> webhook settings:
* URL for entry & exit: &lt;your yunkong2 Domain&gt;:&lt;configured port&gt;/&lt;any locationname&gt;
* Post Format: JSON-encoded: enabled
* authentication: set user / password from yunkong2.geofency config
## in yunkong2 Forum (German)
http://forum.yunkong2.net/viewtopic.php?f=20&t=2076
## security note:
It is not recommended to expose this adapter to the public internet.
Some kind of WAF/proxy/entry Server should be put before yunkong2. (e.g. nginx is nice and easy to configure).
## Changelog
### 0.3.2 (2018-03-07)
* (Apollon77) Fix Authentication
### 0.3.0 (2017-10-04)
* (Apollon77) BREAKING!!! Make sure 'entry' is really a boolean as defined in object
### 0.2.0 (2017-06-09)
* (Apollon77) Add missing authentication check
* (Apollon77) Add option to send in data as Message when received over other ways
* (Apollon77) Add option not to start a webserver for cases where data are received using messages
### 0.1.5 (2016-09-19)
* (soef) support of certificates
### 0.1.4 (2016-03-29)
* (dschaedl) replaced geofency Icon (on request of bluefox)
### 0.1.3 (2016-03-29)
* (soef) fixed atHome and atHomeCount state creation
### 0.1.2 (2016-02-13)
* (soef) Dots in location name will be replaced by an underscore
### 0.1.1 (2016-02-01)
* (Pmant) Fix config page
### 0.1.0 (2016-01-26)
* (soef) Fix error with "at home" settings
### 0.0.4 (2016-01-24)
* (soef) Added some new states
### 0.0.3 (2016-01-21)
* (soef) Some modifications
* (bluefox) change type
### 0.0.2
* (dschaedl) moved to yunkong2/yunkong2.geofency
### 0.0.1
* (dschaedl) initial release
## License
The MIT License (MIT)
Copyright (c) 2015 dschaedl <daniel.schaedler@gmail.com>
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.

BIN
admin/geofency.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

90
admin/index.html Normal file
View File

@ -0,0 +1,90 @@
<html>
<!-- these 4 files always have to be included -->
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css" />
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<!-- these two file always have to be included -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css" />
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<!-- you have to define 2 functions in the global scope: -->
<script type="text/javascript">
function load(settings, onChange) {
// example: select elements with id=key and class=value and insert value
if (settings.activate_server === undefined) settings.activate_server = true;
for (var key in settings) {
// example: select elements with id=key and class=value and insert value
if ($('#' + key + '.value').attr('type') == 'checkbox') {
$('#' + key + '.value').prop('checked', settings[key]).change(function () {
onChange();
});
} else {
$('#' + key + '.value').val(settings[key]).change(function () {
onChange();
}).keyup(function () {
$(this).trigger('change');
});
}
}
onChange(false);
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
var id = $this.attr('id');
if ($this.attr('type') == 'checkbox') {
obj[id] = $this.prop('checked');
} else {
obj[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="geofency.png" height="64" width="64" /></td>
<td style="padding-top: 20px;padding-left: 10px"><h3 class="translate">Geofency Adapter Settings</h3></td>
</tr>
</table>
<table>
<tr><td class="translate">atHome</td><td colspan="5"><input class="value number" id="atHome" type="input" size="5" /></td></tr>
<tr><td class="translate">activate_server</td><td colspan="2"><input type="checkbox" class="value" id="activate_server" size="5" /></td></tr>
<tr><td class="translate">port</td><td colspan="2"><input class="value number" id="port" type="input" size="5" /></td></tr>
<tr><td class="translate">ssl</td><td colspan="2"><input type="checkbox" class="value" id="ssl" size="5" /></td></tr>
<tr><td class="translate">user</td><td colspan="2"><input class="value number" id="user" type="input" size="5" /></td></tr>
<tr><td class="translate">password</td><td colspan="2"><input class="value number" id="pass" type="input" size="5" /></td></tr>
</table>
<h3 class="translate">port</h3>
<p class="translate">geofency is listening for events on this port</p>
<h3 class="translate">user / password</h3>
<p class="translate">set username and password for authentication of your geofenca device. Use the same values in your mobile app webhook settings</p>
<h3 class="translate">geofency mobile app</h3>
<p class="translate">
download geofency for your device<br />
* see <a target="blank" href="http://www.geofency.com/">Geofency website</a><br />
* for any new location -> properties -> webhook settings:<br />
-> URL for entry & exit: &lt;your yunkong2 Domain&gt;:&lt;port from above&gt;/&lt;any location name &gt;<br />
-> Post Format: JSON-encoded: enabled<br />
-> authentication: set user / password from above
</p>
</div>
</html>

8
admin/words.js Normal file
View File

@ -0,0 +1,8 @@
systemDictionary = {
"port": {"de": "Port", "en": "Port", "ru": "Порт"},
"activate_server": {"de": "Server aktivieren", "en": "Activate Server", "ru": "Activate Server"},
"user": {"de": "Benutzer", "en": "User", "ru": "Пользователь"},
"password": {"de": "Passwort", "en": "Password", "ru": "Пароль" },
"atHome": {"de": "Ortsname für Zuhause", "en": "Name of home", "ru": "Имя места для дома"},
"ssl": {"de": "SSL (https://) verwenden", "en": "Use SLL (https://)", "ru": "Использовать SSL(https://)"}
};

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://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
test_script:
- echo %cd%
- node --version
- npm --version
- npm test
build: 'off'

305
geofency.js Normal file
View File

@ -0,0 +1,305 @@
/**
*
* geofency adapter
* This Adapter is based on the geofency adapter of ccu.io
*
*/
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
"use strict";
var utils = require(__dirname + '/lib/utils'); // Get common adapter utils
var webServer = null;
var activate_server = false;
var adapter = utils.Adapter({
name: 'geofency',
unload: function (callback) {
try {
adapter.log.info("terminating http" + (webServer.settings.secure ? "s" : "") + " server on port " + webServer.settings.port);
callback();
} catch (e) {
callback();
}
},
ready: function () {
adapter.log.info("Adapter got 'Ready' Signal - initiating Main function...");
main();
},
message: function (msg) {
processMessage(msg);
}
});
function main() {
checkCreateNewObjects();
if (adapter.config.activate_server !== undefined) activate_server = adapter.config.activate_server;
else activate_server = true;
if (activate_server) {
if (adapter.config.ssl) {
// subscribe on changes of permissions
adapter.subscribeForeignObjects('system.group.*');
adapter.subscribeForeignObjects('system.user.*');
if (!adapter.config.certPublic) {
adapter.config.certPublic = 'defaultPublic';
}
if (!adapter.config.certPrivate) {
adapter.config.certPrivate = 'defaultPrivate';
}
// Load certificates
adapter.getForeignObject('system.certificates', function (err, obj) {
if (err || !obj || !obj.native.certificates || !adapter.config.certPublic || !adapter.config.certPrivate || !obj.native.certificates[adapter.config.certPublic] || !obj.native.certificates[adapter.config.certPrivate]
) {
adapter.log.error('Cannot enable secure web server, because no certificates found: ' + adapter.config.certPublic + ', ' + adapter.config.certPrivate);
} else {
adapter.config.certificates = {
key: obj.native.certificates[adapter.config.certPrivate],
cert: obj.native.certificates[adapter.config.certPublic]
};
}
webServer = initWebServer(adapter.config);
});
} else {
webServer = initWebServer(adapter.config);
}
}
}
function initWebServer(settings) {
var server = {
server: null,
settings: settings
};
if (settings.port) {
if (settings.ssl) {
if (!adapter.config.certificates) {
return null;
}
}
if (settings.ssl) {
server.server = require('https').createServer(adapter.config.certificates, requestProcessor);
} else {
server.server = require('http').createServer(requestProcessor);
}
server.server.__server = server;
} else {
adapter.log.error('port missing');
process.exit(1);
}
if (server.server) {
adapter.getPort(settings.port, function (port) {
if (port != settings.port && !adapter.config.findNextPort) {
adapter.log.error('port ' + settings.port + ' already in use');
process.exit(1);
}
server.server.listen(port);
adapter.log.info('http' + (settings.ssl ? 's' : '') + ' server listening on port ' + port);
});
}
if (server.server) {
return server;
} else {
return null;
}
}
function requestProcessor(req, res) {
var check_user = adapter.config.user;
var check_pass = adapter.config.pass;
// If they pass in a basic auth credential it'll be in a header called "Authorization" (note NodeJS lowercases the names of headers in its request object)
var auth = req.headers.authorization; // auth is in base64(username:password) so we need to decode the base64
adapter.log.debug("Authorization Header is: ", auth);
var username = '';
var password = '';
var request_valid = true;
if (auth && check_user.length > 0 && check_pass.length > 0) {
var tmp = auth.split(' '); // Split on a space, the original auth looks like "Basic Y2hhcmxlczoxMjM0NQ==" and we need the 2nd part
var buf = new Buffer(tmp[1], 'base64'); // create a buffer and tell it the data coming in is base64
var plain_auth = buf.toString(); // read it back out as a string
adapter.log.debug("Decoded Authorization ", plain_auth);
// At this point plain_auth = "username:password"
var creds = plain_auth.split(':'); // split on a ':'
username = creds[0];
password = creds[1];
if ((username != check_user) || (password != check_pass)) {
adapter.log.warn("User credentials invalid");
request_valid = false;
}
}
/*else {
adapter.log.warn("Authorization Header missing but user/pass defined");
request_valid = false;
}*/
if (!request_valid) {
res.statusCode = 403;
res.end();
return;
}
if (req.method === 'POST') {
var body = '';
adapter.log.debug("request path:" + req.path);
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
var user = req.url.slice(1);
var jbody = JSON.parse(body);
handleWebhookRequest(user, jbody);
res.writeHead(200);
res.write("OK");
res.end();
});
}
else {
res.writeHead(500);
res.write("Request error");
res.end();
}
}
function handleWebhookRequest(user, jbody) {
adapter.log.info("adapter geofency received webhook from device " + user + " with values: name: " + jbody.name + ", entry: " + jbody.entry);
var id = user + '.' + jbody.name.replace(/\s|\./g, '_');
// create Objects if not yet available
adapter.getObject(id, function (err, obj) {
if (err || !obj) return createObjects(id, jbody);
setStates(id, jbody);
setAtHome(user, jbody);
});
}
var lastStateNames = ["lastLeave", "lastEnter"],
stateAtHomeCount = "atHomeCount",
stateAtHome = "atHome";
function setStates(id, jbody) {
adapter.setState(id + '.entry', {val: ((jbody.entry == "1") ? true : false), ack: true});
var ts = adapter.formatDate(new Date(jbody.date), "YYYY-MM-DD hh:mm:ss");
adapter.setState(id + '.date', {val: ts, ack: true});
adapter.setState(id + '.' + lastStateNames[(jbody.entry == "1") ? 1 : 0], {val: ts, ack: true});
}
function createObjects(id, b) {
// create all Objects
var children = [];
var obj = {
type: 'state',
//parent: id,
common: {name: 'entry', read: true, write: true, type: 'boolean'},
native: {id: id}
};
adapter.setObjectNotExists(id + ".entry", obj);
children.push(obj);
obj = {
type: 'state',
//parent: id,
common: {name: 'date', read: true, write: true, type: 'string'},
native: {id: id}
};
adapter.setObjectNotExists(id + ".date", obj);
children.push(obj);
for (var i = 0; i < 2; i++) {
obj.common.name = lastStateNames[i];
adapter.setObjectNotExists(id + "." + lastStateNames[i], obj);
children.push(obj);
}
adapter.setObjectNotExists(id, {
type: 'device',
//children: children,
common: {id: id, name: b.name},
native: {name: b.name, lat: b.lat, long: b.long, radius: b.radius, device: b.device, beaconUUID: b.beaconUUID, major: b.major, minor: b.minor}
}, function (err, obj) {
if (!err && obj) setStates(id, b);
});
}
function setAtHome(userName, body) {
if (body.name.toLowerCase() !== adapter.config.atHome.toLowerCase()) return;
var atHomeCount, atHome;
adapter.getState(stateAtHomeCount, function (err, obj) {
if (err) return;
atHomeCount = obj ? obj.val : 0;
adapter.getState(stateAtHome, function (err, obj) {
if (err) return;
atHome = obj ? (obj.val ? JSON.parse(obj.val) : []) : [];
var idx = atHome.indexOf(userName);
if (body.entry === '1') {
if (idx < 0) {
atHome.push(userName);
adapter.setState(stateAtHome, JSON.stringify(atHome), true);
}
} else {
if (idx >= 0) {
atHome.splice(idx, 1);
adapter.setState(stateAtHome, JSON.stringify(atHome), true);
}
}
if (atHomeCount !== atHome.length) adapter.setState(stateAtHomeCount, atHome.length, true);
});
});
}
function createAndSetObject(id, obj) {
adapter.setObjectNotExists(id, obj, function (err) {
adapter.setState(id, 0, true);
});
}
function checkCreateNewObjects() {
function doIt() {
var fs = require('fs'),
io = fs.readFileSync(__dirname + "/io-package.json"),
objs = JSON.parse(io);
for (var i = 0; i < objs.instanceObjects.length; i++) {
createAndSetObject(objs.instanceObjects[i]._id, objs.instanceObjects[i]);
}
}
var timer = setTimeout(doIt, 2000);
adapter.getState(stateAtHome, function (err, obj) {
clearTimeout(timer);
if (!obj) {
doIt();
}
});
}
function processMessage(message) {
if (!message || !message.message.user || !message.message.data) return;
adapter.log.info('Message received = ' + JSON.stringify(message));
handleWebhookRequest(message.message.user, message.message.data);
}

92
io-package.json Normal file
View File

@ -0,0 +1,92 @@
{
"common": {
"name": "geofency",
"version": "0.3.2",
"news": {
"0.3.2": {
"en": "Fix Authentication",
"de": "Fehler bei Zugangsprüfung behoben",
"ru": "Fix Authentication"
},
"0.3.0": {
"en": "Make sure 'entry' is really a boolean as defined in object",
"de": "Sicherstellen das 'entry' wirklich ein Boolean ist wie im Objekt definiert.",
"ru": "Make sure 'entry' is really a boolean as defined in object"
},
"0.2.0": {
"en": "Add Message handling to process webhook data received from other sources then own webserver",
"de": "Behandling von Messages hinzugefügt um Webhook-Daten aus anderen Quellen als dem eigenen Webserver zu verarbeiten",
"ru": "Add Message handling to process webhook data received from other sources then own webserver"
},
"0.1.6": {
"en": "Catch parse errors",
"de": "Bearbeite Parsefehler",
"ru": "Обработка ошибок парсинга"
}
},
"title": "Geofency Adapter",
"desc": "listening for geofency events. Based on the location based mobile App (Geofency)",
"platform": "Javascript/Node.js",
"mode": "daemon",
"icon": "geofency.png",
"extIcon": "https://git.spacen.net/yunkong2/yunkong2.geofency/raw/master/admin/geofency.png",
"readme": "https://git.spacen.net/yunkong2/yunkong2.geofency",
"license": "MIT",
"npmLibs": [],
"type": "geoposition",
"keywords": [
"yunkong2",
"server",
"geofency",
"mobile app"
],
"loglevel": "info",
"enabled": true,
"localLink": "http://%ip%:%port%",
"messagebox": true,
"subscribe": "messagebox",
"authors": [
"Daniel Schaedler <daniel.schaedler@gmail.com>",
"Apollon77 <ingo@fischer-ka.de>"
]
},
"native": {
"activate_server": true,
"port": 7999,
"ssl": false,
"user": "geo",
"pass": "geo",
"atHome": "Home"
},
"objects": [],
"instanceObjects": [
{
"_id": "atHome",
"type": "state",
"common": {
"name": "atHome",
"type": "string",
"role": "state",
"read": true,
"write": false,
"def": 0,
"desc": "Present persons"
},
"native": {}
},
{
"_id": "atHomeCount",
"type": "state",
"common": {
"name": "atHomeCount",
"type": "number",
"role": "state",
"read": true,
"write": false,
"def": [],
"desc": "Number of present persons"
},
"native": {}
}
]
}

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 = [
'yunkong2.js-controller',
'yunkong2.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;

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "yunkong2.geofency",
"version": "0.3.2",
"description": "geofency adapter for yunkong2",
"author": "Daniel Schaedler <daniel.schaedler@gmail.com>",
"contributors": [
"Apollon77 <ingo@fischer-ka.de>"
],
"homepage": "",
"license": "MIT",
"keywords": [
"yunkong2",
"geofency",
"home automation"
],
"repository": {
"type": "git",
"url": "https://git.spacen.net/yunkong2/yunkong2.geofency"
},
"dependencies": {
"request": "^2.67.0"
},
"devDependencies": {
"grunt": "^1.0.1",
"grunt-replace": "^1.0.1",
"grunt-contrib-jshint": "^1.1.0",
"grunt-jscs": "^3.0.1",
"grunt-http": "^2.2.0",
"mocha": "^4.1.0",
"chai": "^4.1.2"
},
"bugs": {
"url": "https://git.spacen.net/yunkong2/yunkong2.geofency/issues"
},
"main": "geofency.js",
"scripts": {
"test": "node node_modules/mocha/bin/mocha --exit"
}
}

17
tasks/jscs.js Normal file
View File

@ -0,0 +1,17 @@
var srcDir = __dirname + "/../";
module.exports = {
all: {
src: [
srcDir + "*.js",
srcDir + "lib/*.js",
srcDir + "adapter/example/*.js",
srcDir + "tasks/**/*.js",
srcDir + "www/**/*.js",
'!' + srcDir + "www/lib/**/*.js",
'!' + srcDir + 'node_modules/**/*.js',
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
],
options: require('./jscsRules.js')
}
};

36
tasks/jscsRules.js Normal file
View File

@ -0,0 +1,36 @@
module.exports = {
force: true,
"requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], /*"if",*/
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true,
"disallowSpacesInFunctionDeclaration": {"beforeOpeningRoundBrace": true},
"disallowSpacesInNamedFunctionExpression": {"beforeOpeningRoundBrace": true},
"requireSpacesInFunctionExpression": {"beforeOpeningCurlyBrace": true},
"requireSpacesInAnonymousFunctionExpression": {"beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true},
"requireSpacesInNamedFunctionExpression": {"beforeOpeningCurlyBrace": true},
"requireSpacesInFunctionDeclaration": {"beforeOpeningCurlyBrace": true},
"disallowMultipleVarDecl": true,
"requireBlocksOnNewline": true,
"disallowEmptyBlocks": true,
"disallowSpacesInsideObjectBrackets": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpacesInsideParentheses": true,
"requireCommaBeforeLineBreak": true,
//"requireAlignedObjectValues": "all",
"requireOperatorBeforeLineBreak": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
// "disallowLeftStickedOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
// "requireRightStickedOperators": ["!"],
// "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
//"disallowSpaceAfterBinaryOperators": [","],
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
"requireSpaceAfterBinaryOperators": ["?", ">", ",", ">=", "<=", "<", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
//"validateIndentation": 4,
//"validateQuoteMarks": { "mark": "\"", "escape": true },
"disallowMixedSpacesAndTabs": true,
"disallowKeywordsOnNewLine": ["else", "catch"]
};

17
tasks/jshint.js Normal file
View File

@ -0,0 +1,17 @@
var srcDir = __dirname + "/../";
module.exports = {
options: {
force: true
},
all: [
srcDir + "*.js",
srcDir + "lib/*.js",
srcDir + "adapter/example/*.js",
srcDir + "tasks/**/*.js",
srcDir + "www/**/*.js",
'!' + srcDir + "www/lib/**/*.js",
'!' + srcDir + 'node_modules/**/*.js',
'!' + srcDir + 'adapter/*/node_modules/**/*.js'
]
};

728
test/lib/setup.js Normal file
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://git.spacen.net/' + 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/yunkong2-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('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('adapter') !== -1 ||
ioPackage.common.title.indexOf('Adapter') !== -1
) {
console.log('WARNING: title contains Adapter or yunkong2. It is clear anyway, that it is adapter for yunkong2.');
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();
});
});