Update packages
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
# This is a composition of lint and test scripts
|
||||
# Make sure to update this file along with the others
|
||||
|
||||
name: Test and Release
|
||||
|
||||
# Run this job on all pushes and pull requests
|
||||
@@ -8,14 +5,15 @@ name: Test and Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- "master"
|
||||
tags:
|
||||
# normal versions
|
||||
- "v?[0-9]+.[0-9]+.[0-9]+"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||
# pre-releases
|
||||
- "v?[0-9]+.[0-9]+.[0-9]+-**"
|
||||
- "v[0-9]+.[0-9]+.[0-9]+-**"
|
||||
pull_request: {}
|
||||
|
||||
# Cancel previous PR/branch runs when a new commit is pushed
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -27,134 +25,47 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
- uses: ioBroker/testing-action-check@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
node-version: '22.x'
|
||||
lint: true
|
||||
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
# - name: Perform a type check
|
||||
# run: npm run check:ts
|
||||
# env:
|
||||
# CI: true
|
||||
# - name: Lint TypeScript code
|
||||
# run: npm run lint
|
||||
# - name: Test package files
|
||||
# run: npm run test:package
|
||||
|
||||
# Runs adapter tests on all supported node versions and OSes
|
||||
adapter-tests:
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') == false
|
||||
|
||||
needs: [check-and-lint]
|
||||
|
||||
if: contains(github.event.head_commit.message, '[skip ci]') == false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x, 18.x]
|
||||
node-version: [20.x, 22.x, 24.x]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
- uses: ioBroker/testing-action-adapter@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
os: ${{ matrix.os }}
|
||||
extra-tests: npm run test:js
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run local tests
|
||||
run: npm test
|
||||
# - name: Run unit tests
|
||||
# run: npm run test:unit
|
||||
# - name: Run integration tests # (linux/osx)
|
||||
# if: startsWith(runner.OS, 'windows') == false
|
||||
# run: DEBUG=testing:* npm run test:integration
|
||||
# - name: Run integration tests # (windows)
|
||||
# if: startsWith(runner.OS, 'windows')
|
||||
# run: set DEBUG=testing:* & npm run test:integration
|
||||
|
||||
# Deploys the final package to NPM
|
||||
deploy:
|
||||
needs: [adapter-tests]
|
||||
needs: [check-and-lint, adapter-tests]
|
||||
|
||||
# Permissions are required to create GitHub releases and npm trusted publishing
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
# Trigger this step only when a commit on master is tagged with a version number
|
||||
if: |
|
||||
contains(github.event.head_commit.message, '[skip ci]') == false &&
|
||||
github.event_name == 'push' &&
|
||||
startsWith(github.ref, 'refs/tags/')
|
||||
startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
- uses: ioBroker/testing-action-deploy@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Extract the version and commit body from the tag
|
||||
id: extract_release
|
||||
# The body may be multiline, therefore we need to escape some characters
|
||||
run: |
|
||||
VERSION="${{ github.ref }}"
|
||||
VERSION=${VERSION##*/}
|
||||
VERSION=${VERSION##*v}
|
||||
echo "::set-output name=VERSION::$VERSION"
|
||||
BODY=$(git show -s --format=%b)
|
||||
BODY="${BODY//'%'/'%25'}"
|
||||
BODY="${BODY//$'\n'/'%0A'}"
|
||||
BODY="${BODY//$'\r'/'%0D'}"
|
||||
echo "::set-output name=BODY::$BODY"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
# - name: Create a clean build
|
||||
# run: npm run build
|
||||
- name: Publish package to npm
|
||||
run: |
|
||||
npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
|
||||
npm whoami
|
||||
npm publish
|
||||
|
||||
- name: Create Github Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release v${{ steps.extract_release.outputs.VERSION }}
|
||||
draft: false
|
||||
# Prerelease versions create prereleases on Github
|
||||
prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }}
|
||||
body: ${{ steps.extract_release.outputs.BODY }}
|
||||
|
||||
- name: Notify Sentry.io about the release
|
||||
run: |
|
||||
npm i -g @sentry/cli
|
||||
export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
export SENTRY_URL=https://sentry.iobroker.net
|
||||
export SENTRY_ORG=iobroker
|
||||
export SENTRY_PROJECT=iobroker-hass
|
||||
export SENTRY_VERSION=iobroker.hass@${{ steps.extract_release.outputs.VERSION }}
|
||||
sentry-cli releases new $SENTRY_VERSION
|
||||
sentry-cli releases set-commits $SENTRY_VERSION --auto
|
||||
sentry-cli releases finalize $SENTRY_VERSION
|
||||
|
||||
# Add the following line BEFORE finalize if sourcemap uploads are needed
|
||||
# sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/
|
||||
node-version: '22.x'
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"require": [
|
||||
"./test/mocha.setup.js"
|
||||
]
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
/**/*
|
||||
!/admin/**/*
|
||||
!/admin/*
|
||||
!/lib/**/*
|
||||
!/lib/*
|
||||
!/io-package.json
|
||||
!/package.json
|
||||
!/LICENSE
|
||||
!/main.js
|
||||
!/README.md
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 bluefox <dogafox@gmail.com>
|
||||
Copyright (c) 2018-2026 bluefox <dogafox@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
|
||||
|
||||
@@ -30,10 +30,11 @@ Allowed field keys are: temperature, target_temp_high, target_temp_low, hvac_mod
|
||||
### Set a stringified JSON to provide one or multiple fields
|
||||
Set the state with an ack=false String value which is a stringified JSON object to call the service and use the JSON object as service data
|
||||
|
||||
For the last option on a light.turn_off with e.g. `{"transition":10,"flash":"short"}` these two service data details are sent with the call to HASS. The available fields with their exact data definition can be seen in the JSON definition of the ioBroker object in the native.fields section and would look like the following in the above example:
|
||||
For the last option on a light.turn_off with e.g. `{"transition":10,"flash":"short"}` these two service data details are sent with the call to HASS. The available fields with their exact data definition can be seen in the JSON definition of the ioBroker object in the `native` fields section and would look like the following in the above example:
|
||||
|
||||
`
|
||||
...
|
||||
```json5
|
||||
{
|
||||
// ...
|
||||
native: {
|
||||
"fields": {
|
||||
"transition": {
|
||||
@@ -65,13 +66,15 @@ For the last option on a light.turn_off with e.g. `{"transition":10,"flash":"sho
|
||||
"attr": "turn_off",
|
||||
"type": "light"
|
||||
}
|
||||
...
|
||||
`
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
For some services like set_speed it is required to call with a JSON object like `{speed: "high"}` in general to provide required values. In this case the field definition look e.g. like:
|
||||
|
||||
```
|
||||
...
|
||||
```json5
|
||||
{
|
||||
//...
|
||||
native: {
|
||||
"fields": {
|
||||
"speed": {
|
||||
@@ -84,9 +87,10 @@ For some services like set_speed it is required to call with a JSON object like
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
// ...
|
||||
}
|
||||
...
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -98,7 +102,7 @@ Please check it https://www.smarthomejetzt.de/mit-iobroker-auf-eine-home-assista
|
||||
|
||||
<!--
|
||||
Placeholder for the next version (at the beginning of the line):
|
||||
### __WORK IN PROGRESS__
|
||||
### **WORK IN PROGRESS**
|
||||
-->
|
||||
|
||||
## Changelog
|
||||
@@ -140,7 +144,7 @@ Please check it https://www.smarthomejetzt.de/mit-iobroker-auf-eine-home-assista
|
||||
## License
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-2023 bluefox <dogafox@gmail.com>
|
||||
Copyright (c) 2018-2026 bluefox <dogafox@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
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import config from '@iobroker/eslint-config';
|
||||
|
||||
export default [
|
||||
...config,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
allowDefaultProject: {
|
||||
allow: ['*.js', '*.mjs'],
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
// project: './tsconfig.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// disable temporary the rule 'jsdoc/require-param' and enable 'jsdoc/require-jsdoc'
|
||||
rules: {
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/require-param': 'off',
|
||||
'jsdoc/check-param-names': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
'build/**/*',
|
||||
'src/lib/**/*.js',
|
||||
'admin/**/*',
|
||||
'test/**/*',
|
||||
'src-admin/**/*',
|
||||
'src-rules/**/*',
|
||||
'tasks.js',
|
||||
'**/*.mjs'
|
||||
],
|
||||
},
|
||||
];
|
||||
+55
-43
@@ -1,6 +1,6 @@
|
||||
const util = require('util');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const WebSocket = require('ws');
|
||||
const util = require('node:util');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const WebSocket = require('node:ws');
|
||||
|
||||
function HASS(options, log) {
|
||||
if (!(this instanceof HASS)) {
|
||||
@@ -14,7 +14,7 @@ function HASS(options, log) {
|
||||
const ERRORS = {
|
||||
1: 'ERR_CANNOT_CONNECT',
|
||||
2: 'ERR_INVALID_AUTH',
|
||||
3: 'ERR_CONNECTION_LOST'
|
||||
3: 'ERR_CONNECTION_LOST',
|
||||
};
|
||||
|
||||
this.socket = null;
|
||||
@@ -28,13 +28,15 @@ function HASS(options, log) {
|
||||
function subscribeEvents(socket, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'subscribe_events', ts: Date.now(), cb: callback};
|
||||
requests[id] = { type: 'subscribe_events', ts: Date.now(), cb: callback };
|
||||
|
||||
socket.send(JSON.stringify({
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'subscribe_events'
|
||||
type: 'subscribe_events',
|
||||
/*event_type: 'state_changed'*/
|
||||
}));
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -43,11 +45,13 @@ function HASS(options, log) {
|
||||
function getConfig(socket, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'get_config', cb: callback, ts: Date.now()};
|
||||
socket.send(JSON.stringify({
|
||||
requests[id] = { type: 'get_config', cb: callback, ts: Date.now() };
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'get_config'
|
||||
}));
|
||||
type: 'get_config',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -56,11 +60,13 @@ function HASS(options, log) {
|
||||
function getStates(socket, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'get_states', cb: callback, ts: Date.now()};
|
||||
socket.send(JSON.stringify({
|
||||
requests[id] = { type: 'get_states', cb: callback, ts: Date.now() };
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'get_states'
|
||||
}));
|
||||
type: 'get_states',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -69,11 +75,13 @@ function HASS(options, log) {
|
||||
function getPanels(socket, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'get_panels', cb: callback, ts: Date.now()};
|
||||
socket.send(JSON.stringify({
|
||||
requests[id] = { type: 'get_panels', cb: callback, ts: Date.now() };
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'get_panels'
|
||||
}));
|
||||
type: 'get_panels',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -82,11 +90,13 @@ function HASS(options, log) {
|
||||
function getServices(socket, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'get_services', cb: callback, ts: Date.now()};
|
||||
socket.send(JSON.stringify({
|
||||
requests[id] = { type: 'get_services', cb: callback, ts: Date.now() };
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'get_services'
|
||||
}));
|
||||
type: 'get_services',
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -95,15 +105,17 @@ function HASS(options, log) {
|
||||
function callService(socket, service, domain, serviceData, target, callback) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
const id = currentId++;
|
||||
requests[id] = {type: 'call_service', cb: callback, ts: Date.now()};
|
||||
socket.send(JSON.stringify({
|
||||
requests[id] = { type: 'call_service', cb: callback, ts: Date.now() };
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: id,
|
||||
type: 'call_service',
|
||||
domain: domain || '',
|
||||
service: service,
|
||||
service_data: serviceData,
|
||||
target
|
||||
}));
|
||||
target,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
callback && callback('not connected');
|
||||
}
|
||||
@@ -111,10 +123,12 @@ function HASS(options, log) {
|
||||
|
||||
function sendAuth(socket, pass) {
|
||||
if (socket && typeof socket.send === 'function') {
|
||||
socket.send(JSON.stringify({
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
type: 'auth',
|
||||
access_token: pass
|
||||
}));
|
||||
access_token: pass,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +141,7 @@ function HASS(options, log) {
|
||||
if (response.event.data && response.event.event_type === 'system_log_event') {
|
||||
if (response.event.data.level === 'WARNING') {
|
||||
log.warn('EVENT: ' + response.event.data.message);
|
||||
} else
|
||||
if (response.event.data.level === 'ERROR') {
|
||||
} else if (response.event.data.level === 'ERROR') {
|
||||
log.error('EVENT: ' + response.event.data.message);
|
||||
} else {
|
||||
log.debug('EVENT: ' + response.event.data.message);
|
||||
@@ -136,28 +149,29 @@ function HASS(options, log) {
|
||||
} else if (response.event && response.event.event_type === 'state_changed') {
|
||||
that.emit('state_changed', response.event.data.new_state);
|
||||
}
|
||||
} else
|
||||
if (response.type === 'auth_required') {
|
||||
} else if (response.type === 'auth_required') {
|
||||
if (!options.password) {
|
||||
that.emit('error', 'Password required. Connection closed');
|
||||
socket.terminate();
|
||||
} else {
|
||||
setTimeout(() => sendAuth(socket, options.password), 50);
|
||||
}
|
||||
} else
|
||||
if (response.type === 'auth_ok') {
|
||||
} else if (response.type === 'auth_ok') {
|
||||
setImmediate(() =>
|
||||
subscribeEvents(socket, err => {
|
||||
if (!err) {
|
||||
connected = true;
|
||||
that.emit('connected');
|
||||
}
|
||||
}));
|
||||
}),
|
||||
);
|
||||
} else if (response.id === undefined) {
|
||||
log.error(`Invalid answer: ${msg}`);
|
||||
} else {
|
||||
if (response.type === 'result' && requests[response.id]) {
|
||||
log.debug(`got answer for ${requests[response.id].type} success = ${response.success}, result = ${JSON.stringify(response.result)}`);
|
||||
log.debug(
|
||||
`got answer for ${requests[response.id].type} success = ${response.success}, result = ${JSON.stringify(response.result)}`,
|
||||
);
|
||||
if (typeof requests[response.id].cb === 'function') {
|
||||
requests[response.id].cb(!response.success, response.result);
|
||||
delete requests[response.id];
|
||||
@@ -176,7 +190,6 @@ function HASS(options, log) {
|
||||
});
|
||||
socket.on('open', () => {
|
||||
if (!connected) {
|
||||
|
||||
}
|
||||
});
|
||||
socket.on('close', () => {
|
||||
@@ -184,7 +197,6 @@ function HASS(options, log) {
|
||||
if (connected) {
|
||||
connected = false;
|
||||
that.emit('disconnected');
|
||||
|
||||
}
|
||||
if (!connectTimeout && !closed) {
|
||||
setTimeout(() => {
|
||||
@@ -244,7 +256,7 @@ function HASS(options, log) {
|
||||
}
|
||||
|
||||
this.socket = new WebSocket(`ws${options.secure ? 's' : ''}://${options.host}:${options.port}/api/websocket`, {
|
||||
perMessageDeflate: false
|
||||
perMessageDeflate: false,
|
||||
});
|
||||
|
||||
initSocket(this.socket);
|
||||
@@ -259,7 +271,7 @@ function HASS(options, log) {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
const utils = require('@iobroker/adapter-core');
|
||||
const HASS = require('./lib/hass');
|
||||
const adapterName = require('./package.json').name.split('.').pop();
|
||||
|
||||
let connected = false;
|
||||
let hass;
|
||||
@@ -17,7 +16,7 @@ let stopped = false;
|
||||
|
||||
function startAdapter(options) {
|
||||
options = options || {};
|
||||
Object.assign(options, {name: adapterName, unload: stop});
|
||||
Object.assign(options, { name: 'hass', unload: stop });
|
||||
adapter = new utils.Adapter(options);
|
||||
|
||||
// is called if a subscribed state changes
|
||||
@@ -62,31 +61,43 @@ function startAdapter(options) {
|
||||
}
|
||||
}
|
||||
|
||||
adapter.log.debug(`Prepare service call for ${id} with (mapped) request parameters ${JSON.stringify(requestFields)} from value: ${JSON.stringify(state.val)}`);
|
||||
adapter.log.debug(
|
||||
`Prepare service call for ${id} with (mapped) request parameters ${JSON.stringify(requestFields)} from value: ${JSON.stringify(state.val)}`,
|
||||
);
|
||||
if (fields) {
|
||||
for (const field in fields) {
|
||||
if (!fields.hasOwnProperty(field)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(fields, field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field === 'entity_id') {
|
||||
target.entity_id = hassObjects[id].native.entity_id
|
||||
target.entity_id = hassObjects[id].native.entity_id;
|
||||
} else if (requestFields[field] !== undefined) {
|
||||
serviceData[field] = requestFields[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
const noFields = Object.keys(serviceData).length === 0;
|
||||
serviceData.entity_id = hassObjects[id].native.entity_id
|
||||
serviceData.entity_id = hassObjects[id].native.entity_id;
|
||||
|
||||
adapter.log.debug(`Send to HASS for service ${hassObjects[id].native.attr} with ${hassObjects[id].native.domain || hassObjects[id].native.type} and data ${JSON.stringify(serviceData)}`)
|
||||
hass.callService(hassObjects[id].native.attr, hassObjects[id].native.domain || hassObjects[id].native.type, serviceData, target, err => {
|
||||
adapter.log.debug(
|
||||
`Send to HASS for service ${hassObjects[id].native.attr} with ${hassObjects[id].native.domain || hassObjects[id].native.type} and data ${JSON.stringify(serviceData)}`,
|
||||
);
|
||||
hass.callService(
|
||||
hassObjects[id].native.attr,
|
||||
hassObjects[id].native.domain || hassObjects[id].native.type,
|
||||
serviceData,
|
||||
target,
|
||||
err => {
|
||||
err && adapter.log.error(`Cannot control ${id}: ${err}`);
|
||||
if (err && fields && noFields) {
|
||||
adapter.log.warn(`Please make sure to provide a stringified JSON as value to set relevant fields! Please refer to the Readme for details!`);
|
||||
adapter.log.warn(
|
||||
`Please make sure to provide a stringified JSON as value to set relevant fields! Please refer to the Readme for details!`,
|
||||
);
|
||||
adapter.log.warn(`Allowed field keys are: ${Object.keys(fields).join(', ')}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,22 +117,6 @@ function stop(callback) {
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
function getUnit(name) {
|
||||
name = name.toLowerCase();
|
||||
if (name.indexOf('temperature') !== -1) {
|
||||
return '°C';
|
||||
} else if (name.indexOf('humidity') !== -1) {
|
||||
return '%';
|
||||
} else if (name.indexOf('pressure') !== -1) {
|
||||
return 'hPa';
|
||||
} else if (name.indexOf('degrees') !== -1) {
|
||||
return '°';
|
||||
} else if (name.indexOf('speed') !== -1) {
|
||||
return 'kmh';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function syncStates(states, cb) {
|
||||
if (!states || !states.length) {
|
||||
return cb();
|
||||
@@ -131,28 +126,33 @@ function syncStates(states, cb) {
|
||||
delete state.id;
|
||||
|
||||
adapter.setForeignState(id, state, err => {
|
||||
err && adapter.log.error(err);
|
||||
if (err) {
|
||||
adapter.log.error(err);
|
||||
}
|
||||
setImmediate(syncStates, states, cb);
|
||||
});
|
||||
}
|
||||
|
||||
function syncObjects(objects, cb) {
|
||||
if (!objects || !objects.length) {
|
||||
if (!objects?.length) {
|
||||
return cb();
|
||||
}
|
||||
const obj = objects.shift();
|
||||
hassObjects[obj._id] = obj;
|
||||
|
||||
adapter.getForeignObject(obj._id, (err, oldObj) => {
|
||||
|
||||
err && adapter.log.error(err);
|
||||
if (err) {
|
||||
adapter.log.error(err);
|
||||
}
|
||||
|
||||
if (!oldObj) {
|
||||
adapter.log.debug(`Create "${obj._id}": ${JSON.stringify(obj.common)}`);
|
||||
hassObjects[obj._id] = obj;
|
||||
adapter.setForeignObject(obj._id, obj, err => {
|
||||
err && adapter.log.error(err);
|
||||
setImmediate(syncObjects, objects, cb);
|
||||
if (err) {
|
||||
adapter.log.error(err);
|
||||
}
|
||||
setImmediate(() => syncObjects(objects, cb));
|
||||
});
|
||||
} else {
|
||||
hassObjects[obj._id] = oldObj;
|
||||
@@ -161,76 +161,30 @@ function syncObjects(objects, cb) {
|
||||
|
||||
adapter.log.debug(`Update "${obj._id}": ${JSON.stringify(obj.common)}`);
|
||||
adapter.setForeignObject(obj._id, oldObj, err => {
|
||||
err => adapter.log.error(err);
|
||||
setImmediate(syncObjects, objects, cb);
|
||||
if (err) {
|
||||
adapter.log.error(err);
|
||||
}
|
||||
setImmediate(() => syncObjects(objects, cb));
|
||||
});
|
||||
} else {
|
||||
setImmediate(syncObjects, objects, cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function syncRoom(room, members, cb) {
|
||||
adapter.getForeignObject(`enum.rooms.${room}`, (err, obj) => {
|
||||
if (!obj) {
|
||||
obj = {
|
||||
_id: `enum.rooms.${room}`,
|
||||
type: 'enum',
|
||||
common: {
|
||||
name: room,
|
||||
members: members
|
||||
},
|
||||
native: {}
|
||||
};
|
||||
adapter.log.debug(`Update "${obj._id}"`);
|
||||
adapter.setForeignObject(obj._id, obj, err => {
|
||||
err && adapter.log.error(err);
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
obj.common = obj.common || {};
|
||||
obj.common.members = obj.common.members || [];
|
||||
let changed = false;
|
||||
for (let m = 0; m < members.length; m++) {
|
||||
if (obj.common.members.indexOf(members[m]) === -1) {
|
||||
changed = true;
|
||||
obj.common.members.push(members[m]);
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
adapter.log.debug(`Update "${obj._id}"`);
|
||||
adapter.setForeignObject(obj._id, obj, err => {
|
||||
err && adapter.log.error(err);
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
setImmediate(() => syncObjects(objects, cb));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const knownAttributes = {
|
||||
azimuth: {write: false, read: true, unit: '°'},
|
||||
elevation: {write: false, read: true, unit: '°'}
|
||||
azimuth: { write: false, read: true, unit: '°' },
|
||||
elevation: { write: false, read: true, unit: '°' },
|
||||
};
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
1: 'ERR_CANNOT_CONNECT',
|
||||
2: 'ERR_INVALID_AUTH',
|
||||
3: 'ERR_CONNECTION_LOST'
|
||||
};
|
||||
const mapTypes = {
|
||||
'string': 'string',
|
||||
'number': 'number',
|
||||
'object': 'mixed',
|
||||
'boolean': 'boolean'
|
||||
string: 'string',
|
||||
number: 'number',
|
||||
object: 'mixed',
|
||||
boolean: 'boolean',
|
||||
};
|
||||
const skipServices = [
|
||||
'persistent_notification'
|
||||
];
|
||||
const skipServices = ['persistent_notification'];
|
||||
|
||||
function parseStates(entities, services, callback) {
|
||||
const objs = [];
|
||||
@@ -239,23 +193,29 @@ function parseStates(entities, services, callback) {
|
||||
let channel;
|
||||
for (let e = 0; e < entities.length; e++) {
|
||||
const entity = entities[e];
|
||||
if (!entity) continue;
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const name = entity.name || (entity.attributes && entity.attributes.friendly_name ? entity.attributes.friendly_name : entity.entity_id);
|
||||
const name =
|
||||
entity.name ||
|
||||
(entity.attributes && entity.attributes.friendly_name ? entity.attributes.friendly_name : entity.entity_id);
|
||||
const desc = entity.attributes && entity.attributes.attribution ? entity.attributes.attribution : undefined;
|
||||
|
||||
channel = {
|
||||
_id: `${adapter.namespace}.entities.${entity.entity_id}`,
|
||||
common: {
|
||||
name: name
|
||||
name: name,
|
||||
},
|
||||
type: 'channel',
|
||||
native: {
|
||||
object_id: entity.object_id,
|
||||
entity_id: entity.entity_id
|
||||
}
|
||||
entity_id: entity.entity_id,
|
||||
},
|
||||
};
|
||||
if (desc) channel.common.desc = desc;
|
||||
if (desc) {
|
||||
channel.common.desc = desc;
|
||||
}
|
||||
objs.push(channel);
|
||||
|
||||
const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined;
|
||||
@@ -269,18 +229,20 @@ function parseStates(entities, services, callback) {
|
||||
name: `${name} STATE`,
|
||||
type: typeof entity.state,
|
||||
read: true,
|
||||
write: false
|
||||
write: false,
|
||||
},
|
||||
native: {
|
||||
object_id: entity.object_id,
|
||||
domain: entity.domain,
|
||||
entity_id: entity.entity_id
|
||||
}
|
||||
entity_id: entity.entity_id,
|
||||
},
|
||||
};
|
||||
if (entity.attributes && entity.attributes.unit_of_measurement) {
|
||||
obj.common.unit = entity.attributes.unit_of_measurement;
|
||||
}
|
||||
adapter.log.debug(`Found Entity state ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`)
|
||||
adapter.log.debug(
|
||||
`Found Entity state ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`,
|
||||
);
|
||||
objs.push(obj);
|
||||
|
||||
let val = entity.state;
|
||||
@@ -288,12 +250,12 @@ function parseStates(entities, services, callback) {
|
||||
val = JSON.stringify(val);
|
||||
}
|
||||
|
||||
states.push({id: obj._id, lc, ts, val, ack: true})
|
||||
states.push({ id: obj._id, lc, ts, val, ack: true });
|
||||
}
|
||||
|
||||
if (entity.attributes) {
|
||||
for (const attr in entity.attributes) {
|
||||
if (entity.attributes.hasOwnProperty(attr)) {
|
||||
if (Object.prototype.hasOwnProperty.call(entity.attributes, attr)) {
|
||||
if (attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') {
|
||||
continue;
|
||||
}
|
||||
@@ -314,8 +276,8 @@ function parseStates(entities, services, callback) {
|
||||
object_id: entity.object_id,
|
||||
domain: entity.domain,
|
||||
entity_id: entity.entity_id,
|
||||
attr: attr
|
||||
}
|
||||
attr: attr,
|
||||
},
|
||||
};
|
||||
if (!common.name) {
|
||||
common.name = `${name} ${attr.replace(/_/g, ' ')}`;
|
||||
@@ -330,7 +292,9 @@ function parseStates(entities, services, callback) {
|
||||
common.type = mapTypes[typeof entity.attributes[attr]];
|
||||
}
|
||||
|
||||
adapter.log.debug(`Found Entity attribute ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`)
|
||||
adapter.log.debug(
|
||||
`Found Entity attribute ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`,
|
||||
);
|
||||
|
||||
objs.push(obj);
|
||||
|
||||
@@ -339,7 +303,7 @@ function parseStates(entities, services, callback) {
|
||||
val = JSON.stringify(val);
|
||||
}
|
||||
|
||||
states.push({id: obj._id, lc, ts, val, ack: true});
|
||||
states.push({ id: obj._id, lc, ts, val, ack: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,7 +313,7 @@ function parseStates(entities, services, callback) {
|
||||
if (services[serviceType] && !skipServices.includes(serviceType)) {
|
||||
const service = services[serviceType];
|
||||
for (const s in service) {
|
||||
if (service.hasOwnProperty(s)) {
|
||||
if (Object.prototype.hasOwnProperty.call(service, s)) {
|
||||
obj = {
|
||||
_id: `${adapter.namespace}.entities.${entity.entity_id}.${s}`,
|
||||
type: 'state',
|
||||
@@ -357,7 +321,7 @@ function parseStates(entities, services, callback) {
|
||||
desc: service[s].description,
|
||||
read: false,
|
||||
write: true,
|
||||
type: 'mixed'
|
||||
type: 'mixed',
|
||||
},
|
||||
native: {
|
||||
object_id: entity.object_id,
|
||||
@@ -365,11 +329,13 @@ function parseStates(entities, services, callback) {
|
||||
fields: service[s].fields,
|
||||
entity_id: entity.entity_id,
|
||||
attr: s,
|
||||
type: serviceType
|
||||
}
|
||||
type: serviceType,
|
||||
},
|
||||
};
|
||||
|
||||
adapter.log.debug(`Found Entity service ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`)
|
||||
adapter.log.debug(
|
||||
`Found Entity service ${obj._id}: ${JSON.stringify(obj.common)} / ${JSON.stringify(obj.native)}`,
|
||||
);
|
||||
|
||||
objs.push(obj);
|
||||
}
|
||||
@@ -377,8 +343,7 @@ function parseStates(entities, services, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
syncObjects(objs, () =>
|
||||
syncStates(states, callback));
|
||||
syncObjects(objs, () => syncStates(states, callback));
|
||||
}
|
||||
|
||||
function main() {
|
||||
@@ -389,8 +354,7 @@ function main() {
|
||||
|
||||
hass = new HASS(adapter.config, adapter.log);
|
||||
|
||||
hass.on('error', err =>
|
||||
adapter.log.error(err));
|
||||
hass.on('error', err => adapter.log.error(err));
|
||||
|
||||
hass.on('state_changed', entity => {
|
||||
adapter.log.debug(`HASS-Message: State Changed: ${JSON.stringify(entity)}`);
|
||||
@@ -403,14 +367,22 @@ function main() {
|
||||
const ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined;
|
||||
if (entity.state !== undefined) {
|
||||
if (hassObjects[`${adapter.namespace}.${id}state`]) {
|
||||
adapter.setState(`${id}state`, {val: entity.state, ack: true, lc: lc, ts: ts});
|
||||
adapter.setState(`${id}state`, { val: entity.state, ack: true, lc: lc, ts: ts });
|
||||
} else {
|
||||
adapter.log.info(`State changed for unknown object ${`${id}state`}. Please restart the adapter to resync the objects.`);
|
||||
adapter.log.info(
|
||||
`State changed for unknown object ${`${id}state`}. Please restart the adapter to resync the objects.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (entity.attributes) {
|
||||
for (const attr in entity.attributes) {
|
||||
if (!entity.attributes.hasOwnProperty(attr) || attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon'|| !attr.length) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(entity.attributes, attr) ||
|
||||
attr === 'friendly_name' ||
|
||||
attr === 'unit_of_measurement' ||
|
||||
attr === 'icon' ||
|
||||
!attr.length
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let val = entity.attributes[attr];
|
||||
@@ -419,9 +391,11 @@ function main() {
|
||||
}
|
||||
const attrId = attr.replace(adapter.FORBIDDEN_CHARS, '_').replace(/\.+$/, '_');
|
||||
if (hassObjects[`${adapter.namespace}.${id}state`]) {
|
||||
adapter.setState(id + attrId, {val, ack: true, lc, ts});
|
||||
adapter.setState(id + attrId, { val, ack: true, lc, ts });
|
||||
} else {
|
||||
adapter.log.info(`State changed for unknown object ${id + attrId}. Please restart the adapter to resync the objects.`);
|
||||
adapter.log.info(
|
||||
`State changed for unknown object ${id + attrId}. Please restart the adapter to resync the objects.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,7 +406,7 @@ function main() {
|
||||
adapter.log.debug('Connected');
|
||||
connected = true;
|
||||
adapter.setState('info.connection', true, true);
|
||||
hass.getConfig((err, config) => {
|
||||
hass.getConfig(err => {
|
||||
if (err) {
|
||||
adapter.log.error(`Cannot read config: ${err}`);
|
||||
return;
|
||||
@@ -440,7 +414,8 @@ function main() {
|
||||
//adapter.log.debug(JSON.stringify(config));
|
||||
delayTimeout = setTimeout(() => {
|
||||
delayTimeout = null;
|
||||
!stopped && hass.getStates((err, states) => {
|
||||
if (!stopped) {
|
||||
hass.getStates((err, states) => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
@@ -450,7 +425,8 @@ function main() {
|
||||
//adapter.log.debug(JSON.stringify(states));
|
||||
delayTimeout = setTimeout(() => {
|
||||
delayTimeout = null;
|
||||
!stopped && hass.getServices((err, services) => {
|
||||
if (!stopped) {
|
||||
hass.getServices((err, services) => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
@@ -459,12 +435,18 @@ function main() {
|
||||
} else {
|
||||
//adapter.log.debug(JSON.stringify(services));
|
||||
parseStates(states, services, () => {
|
||||
adapter.log.debug('Initial parsing of states done, subscribe to ioBroker states');
|
||||
adapter.log.debug(
|
||||
'Initial parsing of states done, subscribe to ioBroker states',
|
||||
);
|
||||
adapter.subscribeStates('*');
|
||||
});
|
||||
}
|
||||
})}, 100);
|
||||
})}, 100);
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Generated
+9955
-3063
File diff suppressed because it is too large
Load Diff
+17
-9
@@ -25,19 +25,27 @@
|
||||
"url": "https://github.com/ioBroker/ioBroker.hass"
|
||||
},
|
||||
"dependencies": {
|
||||
"websocket": "^1.0.34",
|
||||
"ws": "^8.13.0",
|
||||
"@iobroker/adapter-core": "^2.6.8"
|
||||
"websocket": "^1.0.35",
|
||||
"ws": "^8.20.0",
|
||||
"@iobroker/adapter-core": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alcalzone/release-script": "^3.7.0",
|
||||
"@alcalzone/release-script-plugin-iobroker": "^3.6.0",
|
||||
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
||||
"@iobroker/adapter-dev": "^1.2.0",
|
||||
"mocha": "^10.2.0",
|
||||
"chai": "^4.3.10"
|
||||
"@alcalzone/release-script": "^5.1.1",
|
||||
"@alcalzone/release-script-plugin-iobroker": "^5.1.2",
|
||||
"@alcalzone/release-script-plugin-license": "^5.1.1",
|
||||
"@iobroker/adapter-dev": "^1.5.0",
|
||||
"@iobroker/eslint-config": "^2.2.0",
|
||||
"@iobroker/legacy-testing": "^2.0.2",
|
||||
"@iobroker/testing": "^5.2.2",
|
||||
"mocha": "^11.7.5"
|
||||
},
|
||||
"main": "main.js",
|
||||
"files": [
|
||||
"lib/",
|
||||
"admin/",
|
||||
"io-package.json",
|
||||
"main.js"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "node node_modules/mocha/bin/mocha --exit",
|
||||
"release": "release-script",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs';
|
||||
|
||||
export default prettierConfig;
|
||||
@@ -1,968 +0,0 @@
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
// check if tmp directory exists
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const child_process = require('child_process');
|
||||
const rootDir = path.normalize(__dirname + '/../../');
|
||||
const pkg = require(rootDir + 'package.json');
|
||||
const debug = typeof v8debug === 'object';
|
||||
pkg.main = pkg.main || 'main.js';
|
||||
|
||||
let JSONLDB;
|
||||
|
||||
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
|
||||
adapterName = adapterName[adapterName.length - 2];
|
||||
let adapterStarted = false;
|
||||
|
||||
function getAppName() {
|
||||
const parts = __dirname.replace(/\\/g, '/').split('/');
|
||||
return parts[parts.length - 3].split('.')[0];
|
||||
}
|
||||
|
||||
function loadJSONLDB() {
|
||||
if (!JSONLDB) {
|
||||
const dbPath = require.resolve('@alcalzone/jsonl-db', {
|
||||
paths: [rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller']
|
||||
});
|
||||
console.log('JSONLDB path: ' + dbPath);
|
||||
try {
|
||||
const { JsonlDB } = require(dbPath);
|
||||
JSONLDB = JsonlDB;
|
||||
} catch (err) {
|
||||
console.log('Jsonl require error: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const appName = getAppName().toLowerCase();
|
||||
|
||||
let objects;
|
||||
let states;
|
||||
|
||||
let pid = null;
|
||||
|
||||
let systemConfig = null;
|
||||
|
||||
function copyFileSync(source, target) {
|
||||
|
||||
let 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) {
|
||||
let files = [];
|
||||
|
||||
let base = path.basename(source);
|
||||
if (base === adapterName) {
|
||||
base = pkg.name;
|
||||
}
|
||||
//check if folder needs to be created or integrated
|
||||
const 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;
|
||||
}
|
||||
|
||||
const curSource = path.join(source, file);
|
||||
const 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');
|
||||
}
|
||||
|
||||
async function storeOriginalFiles() {
|
||||
console.log('Store original files...');
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
if (fs.existsSync(dataDir + 'objects.json')) {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const 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));
|
||||
console.log('Store original objects.json');
|
||||
}
|
||||
|
||||
if (fs.existsSync(dataDir + 'states.json')) {
|
||||
try {
|
||||
const f = fs.readFileSync(dataDir + 'states.json');
|
||||
fs.writeFileSync(dataDir + 'states.json.original', f);
|
||||
console.log('Store original states.json');
|
||||
} catch (err) {
|
||||
console.log('no states.json found - ignore');
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(dataDir + 'objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(dataDir + 'objects.jsonl');
|
||||
await db.open();
|
||||
|
||||
const admin0 = db.get('system.adapter.admin.0');
|
||||
if (admin0) {
|
||||
if (admin0.common) {
|
||||
admin0.common.enabled = false;
|
||||
db.set('system.adapter.admin.0', admin0);
|
||||
}
|
||||
}
|
||||
|
||||
const admin1 = db.get('system.adapter.admin.1');
|
||||
if (admin1) {
|
||||
if (admin1.common) {
|
||||
admin1.common.enabled = false;
|
||||
db.set('system.adapter.admin.1', admin1);
|
||||
}
|
||||
}
|
||||
await db.close();
|
||||
|
||||
const f = fs.readFileSync(dataDir + 'objects.jsonl');
|
||||
fs.writeFileSync(dataDir + 'objects.jsonl.original', f);
|
||||
console.log('Store original objects.jsonl');
|
||||
}
|
||||
|
||||
if (fs.existsSync(dataDir + 'states.jsonl')) {
|
||||
const f = fs.readFileSync(dataDir + 'states.jsonl');
|
||||
fs.writeFileSync(dataDir + 'states.jsonl.original', f);
|
||||
console.log('Store original states.jsonl');
|
||||
}
|
||||
}
|
||||
|
||||
function restoreOriginalFiles() {
|
||||
console.log('restoreOriginalFiles...');
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
if (fs.existsSync(dataDir + 'objects.json.original')) {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json.original');
|
||||
fs.writeFileSync(dataDir + 'objects.json', f);
|
||||
}
|
||||
if (fs.existsSync(dataDir + 'objects.json.original')) {
|
||||
const f = fs.readFileSync(dataDir + 'states.json.original');
|
||||
fs.writeFileSync(dataDir + 'states.json', f);
|
||||
}
|
||||
|
||||
if (fs.existsSync(dataDir + 'objects.jsonl.original')) {
|
||||
const f = fs.readFileSync(dataDir + 'objects.jsonl.original');
|
||||
fs.writeFileSync(dataDir + 'objects.jsonl', f);
|
||||
}
|
||||
if (fs.existsSync(dataDir + 'objects.jsonl.original')) {
|
||||
const f = fs.readFileSync(dataDir + 'states.jsonl.original');
|
||||
fs.writeFileSync(dataDir + 'states.jsonl', f);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIsAdapterInstalled(cb, counter, customName) {
|
||||
customName = customName || pkg.name.split('.').pop();
|
||||
counter = counter || 0;
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
console.log('checkIsAdapterInstalled...');
|
||||
|
||||
try {
|
||||
if (fs.existsSync(dataDir + 'objects.json')) {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const 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');
|
||||
}
|
||||
} else if (fs.existsSync(dataDir + 'objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(dataDir + 'objects.jsonl');
|
||||
try {
|
||||
await db.open();
|
||||
} catch (err) {
|
||||
if (err.message.includes('Failed to lock DB file')) {
|
||||
console.log('checkIsAdapterInstalled: DB still opened ...');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const obj = db.get('system.adapter.' + customName + '.0');
|
||||
await db.close();
|
||||
|
||||
if (obj) {
|
||||
console.log('checkIsAdapterInstalled: ready!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
} else {
|
||||
console.warn('checkIsAdapterInstalled: still not ready');
|
||||
}
|
||||
} else {
|
||||
console.error('checkIsAdapterInstalled: No objects file found in datadir ' + dataDir);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log('checkIsAdapterInstalled: 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);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkIsControllerInstalled(cb, counter) {
|
||||
counter = counter || 0;
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
console.log('checkIsControllerInstalled...');
|
||||
try {
|
||||
if (fs.existsSync(dataDir + 'objects.json')) {
|
||||
const f = fs.readFileSync(dataDir + 'objects.json');
|
||||
const objects = JSON.parse(f.toString());
|
||||
if (objects['system.certificates']) {
|
||||
console.log('checkIsControllerInstalled: installed!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
} else if (fs.existsSync(dataDir + 'objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(dataDir + 'objects.jsonl');
|
||||
try {
|
||||
await db.open();
|
||||
} catch (err) {
|
||||
if (err.message.includes('Failed to lock DB file')) {
|
||||
console.log('checkIsControllerInstalled: DB still opened ...');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
const obj = db.get('system.certificates');
|
||||
await db.close();
|
||||
|
||||
if (obj) {
|
||||
console.log('checkIsControllerInstalled: installed!');
|
||||
setTimeout(function () {
|
||||
if (cb) cb();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error('checkIsControllerInstalled: No objects file found in datadir ' + dataDir);
|
||||
}
|
||||
} 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...');
|
||||
const 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
|
||||
const _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...');
|
||||
let _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...');
|
||||
let __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
|
||||
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
|
||||
// TEST WISE!
|
||||
//config.objects.type = 'jsonl';
|
||||
//config.states.type = 'jsonl';
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
console.log('Setup finished.');
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(async function () {
|
||||
await storeOriginalFiles();
|
||||
if (cb) cb(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// check if port 9000 is free, else admin adapter will be added to running instance
|
||||
const client = new require('net').Socket();
|
||||
client.on('error', () => {});
|
||||
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 dev build from npm');
|
||||
|
||||
child_process.execSync('npm install ' + appName + '.js-controller@dev --prefix ./ --production', {
|
||||
cwd: rootDir + 'tmp/',
|
||||
stdio: [0, 1, 2]
|
||||
});
|
||||
} else {
|
||||
console.log('Setup js-controller...');
|
||||
let __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 () {
|
||||
let _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
|
||||
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
config.objects.port = 19001;
|
||||
config.states.port = 19000;
|
||||
|
||||
// TEST WISE!
|
||||
//config.objects.type = 'jsonl';
|
||||
//config.states.type = 'jsonl';
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
|
||||
|
||||
copyAdapterToController();
|
||||
|
||||
installAdapter(async function () {
|
||||
await 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() {
|
||||
const dirPath = rootDir + 'tmp/log';
|
||||
let 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 (let i = 0; i < files.length; i++) {
|
||||
const filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Controller log cleared');
|
||||
} catch (err) {
|
||||
console.error('cannot clear log: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearDB() {
|
||||
const dirPath = rootDir + 'tmp/iobroker-data/sqlite';
|
||||
let 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 (let i = 0; i < files.length; i++) {
|
||||
const filePath = dirPath + '/' + files[i];
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
console.log('Clear sqlite DB');
|
||||
} catch (err) {
|
||||
console.error('cannot clear DB: ' + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupController(cb) {
|
||||
installJsController(async function (isInited) {
|
||||
try {
|
||||
clearControllerLog();
|
||||
clearDB();
|
||||
|
||||
if (!isInited) {
|
||||
restoreOriginalFiles();
|
||||
copyAdapterToController();
|
||||
}
|
||||
// read system.config object
|
||||
const dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
if (fs.existsSync(dataDir + 'objects.json')) {
|
||||
let 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': {}};
|
||||
}
|
||||
|
||||
systemConfig = objs['system.config'];
|
||||
if (cb) cb(objs['system.config']);
|
||||
} else if (fs.existsSync(dataDir + 'objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(dataDir + 'objects.jsonl');
|
||||
await db.open();
|
||||
|
||||
let config = db.get('system.config');
|
||||
systemConfig = config || {};
|
||||
|
||||
await db.close();
|
||||
|
||||
if (cb) cb(systemConfig);
|
||||
} else {
|
||||
console.error('read SystemConfig: No objects file found in datadir ' + dataDir);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('setupController: ' + err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getSecret() {
|
||||
var dataDir = rootDir + 'tmp/' + appName + '-data/';
|
||||
|
||||
if (systemConfig) {
|
||||
return systemConfig.native.secret;
|
||||
}
|
||||
if (fs.existsSync(dataDir + 'objects.json')) {
|
||||
let objs;
|
||||
try {
|
||||
objs = fs.readFileSync(dataDir + 'objects.json');
|
||||
objs = JSON.parse(objs);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn("Could not load secret. Reason: " + e);
|
||||
return null;
|
||||
}
|
||||
if (!objs || !objs['system.config']) {
|
||||
objs = {'system.config': {}};
|
||||
}
|
||||
|
||||
return objs['system.config'].native.secre;
|
||||
} else if (fs.existsSync(dataDir + 'objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(dataDir + 'objects.jsonl');
|
||||
await db.open();
|
||||
|
||||
let config = db.get('system.config');
|
||||
config = config || {};
|
||||
|
||||
await db.close();
|
||||
|
||||
return config.native.secret;
|
||||
} else {
|
||||
console.error('read secret: No objects file found in datadir ' + dataDir);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function encrypt (key, value) {
|
||||
var result = '';
|
||||
for (var i = 0; i < value.length; ++i) {
|
||||
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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...');
|
||||
try {
|
||||
const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
|
||||
|
||||
adapterStarted = false;
|
||||
let isObjectConnected;
|
||||
let isStatesConnected;
|
||||
|
||||
// rootDir + 'tmp/node_modules
|
||||
const objPath = require.resolve(`@iobroker/db-objects-${config.objects.type}`, {
|
||||
paths: [ rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller']
|
||||
});
|
||||
console.log('Objects Path: ' + objPath);
|
||||
const Objects = require(objPath).Server;
|
||||
objects = new Objects({
|
||||
connection: {
|
||||
'type': config.objects.type,
|
||||
'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
|
||||
const statePath = require.resolve(`@iobroker/db-states-${config.states.type}`, {
|
||||
paths: [ rootDir + 'tmp/node_modules', rootDir, rootDir + 'tmp/node_modules/' + appName + '.js-controller']
|
||||
});
|
||||
console.log('States Path: ' + statePath);
|
||||
const States = require(statePath).Server;
|
||||
states = new States({
|
||||
connection: {
|
||||
type: config.states.type,
|
||||
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
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let 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
|
||||
async function setAdapterConfig(common, native, instance) {
|
||||
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
if (fs.existsSync(rootDir + 'tmp/' + appName + '-data/objects.json')) {
|
||||
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
if (common) objects[id].common = common;
|
||||
if (native) objects[id].native = native;
|
||||
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
|
||||
} else if (fs.existsSync(rootDir + 'tmp/' + appName + '-data/objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(rootDir + 'tmp/' + appName + '-data/objects.jsonl');
|
||||
await db.open();
|
||||
|
||||
let obj = db.get(id);
|
||||
if (common) obj.common = common;
|
||||
if (native) obj.native = native;
|
||||
db.set(id, obj);
|
||||
|
||||
await db.close();
|
||||
} else {
|
||||
console.error('setAdapterConfig: No objects file found in datadir ' + rootDir + 'tmp/' + appName + '-data/');
|
||||
}
|
||||
}
|
||||
|
||||
// Read config of the adapter
|
||||
async function getAdapterConfig(instance) {
|
||||
const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
|
||||
if (fs.existsSync(rootDir + 'tmp/' + appName + '-data/objects.json')) {
|
||||
const objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
|
||||
return objects[id];
|
||||
} else if (fs.existsSync(rootDir + 'tmp/' + appName + '-data/objects.jsonl')) {
|
||||
loadJSONLDB();
|
||||
const db = new JSONLDB(rootDir + 'tmp/' + appName + '-data/objects.jsonl');
|
||||
await db.open();
|
||||
|
||||
let obj = db.get(id);
|
||||
|
||||
await db.close();
|
||||
return obj;
|
||||
} else {
|
||||
console.error('getAdapterConfig: No objects file found in datadir ' + rootDir + 'tmp/' + appName + '-data/');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
module.exports.getSecret = getSecret;
|
||||
module.exports.encrypt = encrypt;
|
||||
}
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
process.on("unhandledRejection", (r) => { throw r; });
|
||||
process.on('unhandledRejection', r => {
|
||||
throw r;
|
||||
});
|
||||
|
||||
+24
-86
@@ -1,84 +1,20 @@
|
||||
/* jshint -W097 */// jshint strict:false
|
||||
/*jslint node: true */
|
||||
var expect = require('chai').expect;
|
||||
var setup = require(__dirname + '/lib/setup');
|
||||
/* jshint -W097 */
|
||||
/* jshint strict: false */
|
||||
/* jslint node: true */
|
||||
const setup = require('@iobroker/legacy-testing');
|
||||
|
||||
var objects = null;
|
||||
var states = null;
|
||||
var onStateChanged = null;
|
||||
var onObjectChanged = null;
|
||||
var sendToID = 1;
|
||||
let objects = null;
|
||||
let states = null;
|
||||
const onStateChanged = null;
|
||||
|
||||
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1);
|
||||
const 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) {
|
||||
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(async function () {
|
||||
var config = await setup.getAdapterConfig();
|
||||
setup.setupController(async () => {
|
||||
const config = await setup.getAdapterConfig();
|
||||
// enable adapter
|
||||
config.common.enabled = true;
|
||||
config.common.loglevel = 'debug';
|
||||
@@ -87,21 +23,23 @@ describe('Test ' + adapterShortName + ' adapter', function() {
|
||||
|
||||
await setup.setAdapterConfig(config.common, config.native);
|
||||
|
||||
setup.startController(true, function(id, obj) {}, function (id, state) {
|
||||
if (onStateChanged) onStateChanged(id, state);
|
||||
},
|
||||
function (_objects, _states) {
|
||||
setup.startController(
|
||||
true,
|
||||
(id, obj) => {},
|
||||
(id, state) => onStateChanged?.(id, state),
|
||||
(_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 connected', function (done) {
|
||||
this.timeout(60000);
|
||||
setTimeout(function () {
|
||||
@@ -115,11 +53,11 @@ describe('Test ' + adapterShortName + ' adapter', function() {
|
||||
}, 5000);
|
||||
});
|
||||
*/
|
||||
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
|
||||
after(`Test ${adapterShortName} adapter: Stop js-controller`, function (done) {
|
||||
this.timeout(10000);
|
||||
|
||||
setup.stopController(function (normalTerminated) {
|
||||
console.log('Adapter normal terminated: ' + normalTerminated);
|
||||
setup.stopController(normalTerminated => {
|
||||
console.log(`Adapter normal terminated: ${normalTerminated}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,47 +1,5 @@
|
||||
/* jshint -W097 */
|
||||
/* jshint strict:false */
|
||||
/* jslint node: true */
|
||||
/* jshint expr: true */
|
||||
var expect = require('chai').expect;
|
||||
var fs = require('fs');
|
||||
const path = require('path');
|
||||
const { tests } = require('@iobroker/testing');
|
||||
|
||||
describe('Test package.json and io-package.json', function() {
|
||||
it('Test package files', function (done) {
|
||||
var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json');
|
||||
var ioPackage = JSON.parse(fileContentIOPackage);
|
||||
|
||||
var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json');
|
||||
var npmPackage = JSON.parse(fileContentNPMPackage);
|
||||
|
||||
expect(ioPackage).to.be.an('object');
|
||||
expect(npmPackage).to.be.an('object');
|
||||
|
||||
expect(ioPackage.common.version).to.exist;
|
||||
expect(npmPackage.version).to.exist;
|
||||
|
||||
if (!expect(ioPackage.common.version).to.be.equal(npmPackage.version)) {
|
||||
console.log('ERROR: Version numbers in package.json and io-package.json differ!!');
|
||||
}
|
||||
|
||||
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!');
|
||||
}
|
||||
|
||||
expect(ioPackage.common.authors).to.exist;
|
||||
if (ioPackage.common.name.indexOf('template') !== 0) {
|
||||
if (Array.isArray(ioPackage.common.authors)) {
|
||||
expect(ioPackage.common.authors.length).to.not.be.equal(0);
|
||||
if (ioPackage.common.authors.length === 1) {
|
||||
expect(ioPackage.common.authors[0]).to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
expect(ioPackage.common.authors).to.not.be.equal('my Name <my@email.com>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Testing for set authors field in io-package skipped because template adapter');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
// Validate the package files
|
||||
tests.packageFiles(path.join(__dirname, '..'));
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"checkJs": false,
|
||||
"noEmit": false,
|
||||
"declaration": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Root tsconfig to set the settings and power editor support for all TS files
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
|
||||
"skipLibCheck": true, // Don't report errors in 3rd party definitions
|
||||
"noEmitOnError": true,
|
||||
"outDir": "./build",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"module": "Node16",
|
||||
"moduleResolution": "node16",
|
||||
"sourceMap": true, // required for debug
|
||||
"inlineSources": true, // required for debug
|
||||
|
||||
"strict": true,
|
||||
"target": "es2022",
|
||||
"inlineSourceMap": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"types": ["node", "@iobroker/types"]
|
||||
},
|
||||
"include": ["src/**/*.*", "src/**/*.d.*"],
|
||||
"exclude": ["build/**", "node_modules/**", "src-admin/**"]
|
||||
}
|
||||
Reference in New Issue
Block a user