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
|
name: Test and Release
|
||||||
|
|
||||||
# Run this job on all pushes and pull requests
|
# Run this job on all pushes and pull requests
|
||||||
@@ -8,14 +5,15 @@ name: Test and Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '*'
|
- "master"
|
||||||
tags:
|
tags:
|
||||||
# normal versions
|
# normal versions
|
||||||
- "v?[0-9]+.[0-9]+.[0-9]+"
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||||
# pre-releases
|
# pre-releases
|
||||||
- "v?[0-9]+.[0-9]+.[0-9]+-**"
|
- "v[0-9]+.[0-9]+.[0-9]+-**"
|
||||||
pull_request: {}
|
pull_request: {}
|
||||||
|
|
||||||
|
# Cancel previous PR/branch runs when a new commit is pushed
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.ref }}
|
group: ${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -27,134 +25,47 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [16.x]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: ioBroker/testing-action-check@v1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
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:
|
adapter-tests:
|
||||||
if: contains(github.event.head_commit.message, '[skip ci]') == false
|
|
||||||
|
|
||||||
needs: [check-and-lint]
|
needs: [check-and-lint]
|
||||||
|
|
||||||
|
if: contains(github.event.head_commit.message, '[skip ci]') == false
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: ioBroker/testing-action-adapter@v1
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
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:
|
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: |
|
if: |
|
||||||
contains(github.event.head_commit.message, '[skip ci]') == false &&
|
contains(github.event.head_commit.message, '[skip ci]') == false &&
|
||||||
github.event_name == 'push' &&
|
github.event_name == 'push' &&
|
||||||
startsWith(github.ref, 'refs/tags/')
|
startsWith(github.ref, 'refs/tags/v')
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [16.x]
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: ioBroker/testing-action-deploy@v1
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: '22.x'
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- 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/
|
|
||||||
|
|||||||
@@ -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)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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 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
|
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: {
|
native: {
|
||||||
"fields": {
|
"fields": {
|
||||||
"transition": {
|
"transition": {
|
||||||
@@ -65,13 +66,15 @@ For the last option on a light.turn_off with e.g. `{"transition":10,"flash":"sho
|
|||||||
"attr": "turn_off",
|
"attr": "turn_off",
|
||||||
"type": "light"
|
"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:
|
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: {
|
native: {
|
||||||
"fields": {
|
"fields": {
|
||||||
"speed": {
|
"speed": {
|
||||||
@@ -84,9 +87,10 @@ For some services like set_speed it is required to call with a JSON object like
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
...
|
// ...
|
||||||
|
}
|
||||||
|
// ...
|
||||||
}
|
}
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## 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):
|
Placeholder for the next version (at the beginning of the line):
|
||||||
### __WORK IN PROGRESS__
|
### **WORK IN PROGRESS**
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
@@ -140,7 +144,7 @@ Please check it https://www.smarthomejetzt.de/mit-iobroker-auf-eine-home-assista
|
|||||||
## License
|
## License
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
+49
-37
@@ -1,6 +1,6 @@
|
|||||||
const util = require('util');
|
const util = require('node:util');
|
||||||
const EventEmitter = require('events').EventEmitter;
|
const { EventEmitter } = require('node:events');
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('node:ws');
|
||||||
|
|
||||||
function HASS(options, log) {
|
function HASS(options, log) {
|
||||||
if (!(this instanceof HASS)) {
|
if (!(this instanceof HASS)) {
|
||||||
@@ -14,7 +14,7 @@ function HASS(options, log) {
|
|||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
1: 'ERR_CANNOT_CONNECT',
|
1: 'ERR_CANNOT_CONNECT',
|
||||||
2: 'ERR_INVALID_AUTH',
|
2: 'ERR_INVALID_AUTH',
|
||||||
3: 'ERR_CONNECTION_LOST'
|
3: 'ERR_CONNECTION_LOST',
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
@@ -30,11 +30,13 @@ function HASS(options, log) {
|
|||||||
const id = currentId++;
|
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,
|
id: id,
|
||||||
type: 'subscribe_events'
|
type: 'subscribe_events',
|
||||||
/*event_type: 'state_changed'*/
|
/*event_type: 'state_changed'*/
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -44,10 +46,12 @@ function HASS(options, log) {
|
|||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
const id = currentId++;
|
const id = currentId++;
|
||||||
requests[id] = { type: 'get_config', cb: callback, ts: Date.now() };
|
requests[id] = { type: 'get_config', cb: callback, ts: Date.now() };
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
id: id,
|
id: id,
|
||||||
type: 'get_config'
|
type: 'get_config',
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -57,10 +61,12 @@ function HASS(options, log) {
|
|||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
const id = currentId++;
|
const id = currentId++;
|
||||||
requests[id] = { type: 'get_states', cb: callback, ts: Date.now() };
|
requests[id] = { type: 'get_states', cb: callback, ts: Date.now() };
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
id: id,
|
id: id,
|
||||||
type: 'get_states'
|
type: 'get_states',
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -70,10 +76,12 @@ function HASS(options, log) {
|
|||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
const id = currentId++;
|
const id = currentId++;
|
||||||
requests[id] = { type: 'get_panels', cb: callback, ts: Date.now() };
|
requests[id] = { type: 'get_panels', cb: callback, ts: Date.now() };
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
id: id,
|
id: id,
|
||||||
type: 'get_panels'
|
type: 'get_panels',
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -83,10 +91,12 @@ function HASS(options, log) {
|
|||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
const id = currentId++;
|
const id = currentId++;
|
||||||
requests[id] = { type: 'get_services', cb: callback, ts: Date.now() };
|
requests[id] = { type: 'get_services', cb: callback, ts: Date.now() };
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
id: id,
|
id: id,
|
||||||
type: 'get_services'
|
type: 'get_services',
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -96,14 +106,16 @@ function HASS(options, log) {
|
|||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
const id = currentId++;
|
const id = currentId++;
|
||||||
requests[id] = { type: 'call_service', cb: callback, ts: Date.now() };
|
requests[id] = { type: 'call_service', cb: callback, ts: Date.now() };
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
id: id,
|
id: id,
|
||||||
type: 'call_service',
|
type: 'call_service',
|
||||||
domain: domain || '',
|
domain: domain || '',
|
||||||
service: service,
|
service: service,
|
||||||
service_data: serviceData,
|
service_data: serviceData,
|
||||||
target
|
target,
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
callback && callback('not connected');
|
callback && callback('not connected');
|
||||||
}
|
}
|
||||||
@@ -111,10 +123,12 @@ function HASS(options, log) {
|
|||||||
|
|
||||||
function sendAuth(socket, pass) {
|
function sendAuth(socket, pass) {
|
||||||
if (socket && typeof socket.send === 'function') {
|
if (socket && typeof socket.send === 'function') {
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
type: 'auth',
|
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 && response.event.event_type === 'system_log_event') {
|
||||||
if (response.event.data.level === 'WARNING') {
|
if (response.event.data.level === 'WARNING') {
|
||||||
log.warn('EVENT: ' + response.event.data.message);
|
log.warn('EVENT: ' + response.event.data.message);
|
||||||
} else
|
} else if (response.event.data.level === 'ERROR') {
|
||||||
if (response.event.data.level === 'ERROR') {
|
|
||||||
log.error('EVENT: ' + response.event.data.message);
|
log.error('EVENT: ' + response.event.data.message);
|
||||||
} else {
|
} else {
|
||||||
log.debug('EVENT: ' + response.event.data.message);
|
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') {
|
} else if (response.event && response.event.event_type === 'state_changed') {
|
||||||
that.emit('state_changed', response.event.data.new_state);
|
that.emit('state_changed', response.event.data.new_state);
|
||||||
}
|
}
|
||||||
} else
|
} else if (response.type === 'auth_required') {
|
||||||
if (response.type === 'auth_required') {
|
|
||||||
if (!options.password) {
|
if (!options.password) {
|
||||||
that.emit('error', 'Password required. Connection closed');
|
that.emit('error', 'Password required. Connection closed');
|
||||||
socket.terminate();
|
socket.terminate();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => sendAuth(socket, options.password), 50);
|
setTimeout(() => sendAuth(socket, options.password), 50);
|
||||||
}
|
}
|
||||||
} else
|
} else if (response.type === 'auth_ok') {
|
||||||
if (response.type === 'auth_ok') {
|
|
||||||
setImmediate(() =>
|
setImmediate(() =>
|
||||||
subscribeEvents(socket, err => {
|
subscribeEvents(socket, err => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
connected = true;
|
connected = true;
|
||||||
that.emit('connected');
|
that.emit('connected');
|
||||||
}
|
}
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
} else if (response.id === undefined) {
|
} else if (response.id === undefined) {
|
||||||
log.error(`Invalid answer: ${msg}`);
|
log.error(`Invalid answer: ${msg}`);
|
||||||
} else {
|
} else {
|
||||||
if (response.type === 'result' && requests[response.id]) {
|
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') {
|
if (typeof requests[response.id].cb === 'function') {
|
||||||
requests[response.id].cb(!response.success, response.result);
|
requests[response.id].cb(!response.success, response.result);
|
||||||
delete requests[response.id];
|
delete requests[response.id];
|
||||||
@@ -176,7 +190,6 @@ function HASS(options, log) {
|
|||||||
});
|
});
|
||||||
socket.on('open', () => {
|
socket.on('open', () => {
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('close', () => {
|
socket.on('close', () => {
|
||||||
@@ -184,7 +197,6 @@ function HASS(options, log) {
|
|||||||
if (connected) {
|
if (connected) {
|
||||||
connected = false;
|
connected = false;
|
||||||
that.emit('disconnected');
|
that.emit('disconnected');
|
||||||
|
|
||||||
}
|
}
|
||||||
if (!connectTimeout && !closed) {
|
if (!connectTimeout && !closed) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -244,7 +256,7 @@ function HASS(options, log) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.socket = new WebSocket(`ws${options.secure ? 's' : ''}://${options.host}:${options.port}/api/websocket`, {
|
this.socket = new WebSocket(`ws${options.secure ? 's' : ''}://${options.host}:${options.port}/api/websocket`, {
|
||||||
perMessageDeflate: false
|
perMessageDeflate: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
initSocket(this.socket);
|
initSocket(this.socket);
|
||||||
@@ -259,7 +271,7 @@ function HASS(options, log) {
|
|||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.close();
|
this.socket.close();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
const utils = require('@iobroker/adapter-core');
|
const utils = require('@iobroker/adapter-core');
|
||||||
const HASS = require('./lib/hass');
|
const HASS = require('./lib/hass');
|
||||||
const adapterName = require('./package.json').name.split('.').pop();
|
|
||||||
|
|
||||||
let connected = false;
|
let connected = false;
|
||||||
let hass;
|
let hass;
|
||||||
@@ -17,7 +16,7 @@ let stopped = false;
|
|||||||
|
|
||||||
function startAdapter(options) {
|
function startAdapter(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
Object.assign(options, {name: adapterName, unload: stop});
|
Object.assign(options, { name: 'hass', unload: stop });
|
||||||
adapter = new utils.Adapter(options);
|
adapter = new utils.Adapter(options);
|
||||||
|
|
||||||
// is called if a subscribed state changes
|
// 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) {
|
if (fields) {
|
||||||
for (const field in fields) {
|
for (const field in fields) {
|
||||||
if (!fields.hasOwnProperty(field)) {
|
if (!Object.prototype.hasOwnProperty.call(fields, field)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'entity_id') {
|
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) {
|
} else if (requestFields[field] !== undefined) {
|
||||||
serviceData[field] = requestFields[field];
|
serviceData[field] = requestFields[field];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const noFields = Object.keys(serviceData).length === 0;
|
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)}`)
|
adapter.log.debug(
|
||||||
hass.callService(hassObjects[id].native.attr, hassObjects[id].native.domain || hassObjects[id].native.type, serviceData, target, err => {
|
`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}`);
|
err && adapter.log.error(`Cannot control ${id}: ${err}`);
|
||||||
if (err && fields && noFields) {
|
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(', ')}`);
|
adapter.log.warn(`Allowed field keys are: ${Object.keys(fields).join(', ')}`);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,22 +117,6 @@ function stop(callback) {
|
|||||||
callback && 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) {
|
function syncStates(states, cb) {
|
||||||
if (!states || !states.length) {
|
if (!states || !states.length) {
|
||||||
return cb();
|
return cb();
|
||||||
@@ -131,28 +126,33 @@ function syncStates(states, cb) {
|
|||||||
delete state.id;
|
delete state.id;
|
||||||
|
|
||||||
adapter.setForeignState(id, state, err => {
|
adapter.setForeignState(id, state, err => {
|
||||||
err && adapter.log.error(err);
|
if (err) {
|
||||||
|
adapter.log.error(err);
|
||||||
|
}
|
||||||
setImmediate(syncStates, states, cb);
|
setImmediate(syncStates, states, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncObjects(objects, cb) {
|
function syncObjects(objects, cb) {
|
||||||
if (!objects || !objects.length) {
|
if (!objects?.length) {
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
const obj = objects.shift();
|
const obj = objects.shift();
|
||||||
hassObjects[obj._id] = obj;
|
hassObjects[obj._id] = obj;
|
||||||
|
|
||||||
adapter.getForeignObject(obj._id, (err, oldObj) => {
|
adapter.getForeignObject(obj._id, (err, oldObj) => {
|
||||||
|
if (err) {
|
||||||
err && adapter.log.error(err);
|
adapter.log.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
if (!oldObj) {
|
if (!oldObj) {
|
||||||
adapter.log.debug(`Create "${obj._id}": ${JSON.stringify(obj.common)}`);
|
adapter.log.debug(`Create "${obj._id}": ${JSON.stringify(obj.common)}`);
|
||||||
hassObjects[obj._id] = obj;
|
hassObjects[obj._id] = obj;
|
||||||
adapter.setForeignObject(obj._id, obj, err => {
|
adapter.setForeignObject(obj._id, obj, err => {
|
||||||
err && adapter.log.error(err);
|
if (err) {
|
||||||
setImmediate(syncObjects, objects, cb);
|
adapter.log.error(err);
|
||||||
|
}
|
||||||
|
setImmediate(() => syncObjects(objects, cb));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
hassObjects[obj._id] = oldObj;
|
hassObjects[obj._id] = oldObj;
|
||||||
@@ -161,51 +161,13 @@ function syncObjects(objects, cb) {
|
|||||||
|
|
||||||
adapter.log.debug(`Update "${obj._id}": ${JSON.stringify(obj.common)}`);
|
adapter.log.debug(`Update "${obj._id}": ${JSON.stringify(obj.common)}`);
|
||||||
adapter.setForeignObject(obj._id, oldObj, err => {
|
adapter.setForeignObject(obj._id, oldObj, err => {
|
||||||
err => adapter.log.error(err);
|
if (err) {
|
||||||
setImmediate(syncObjects, objects, cb);
|
adapter.log.error(err);
|
||||||
|
}
|
||||||
|
setImmediate(() => syncObjects(objects, cb));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setImmediate(syncObjects, objects, cb);
|
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -213,24 +175,16 @@ function syncRoom(room, members, cb) {
|
|||||||
|
|
||||||
const knownAttributes = {
|
const knownAttributes = {
|
||||||
azimuth: { write: false, read: true, unit: '°' },
|
azimuth: { write: false, read: true, unit: '°' },
|
||||||
elevation: {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 = {
|
const mapTypes = {
|
||||||
'string': 'string',
|
string: 'string',
|
||||||
'number': 'number',
|
number: 'number',
|
||||||
'object': 'mixed',
|
object: 'mixed',
|
||||||
'boolean': 'boolean'
|
boolean: 'boolean',
|
||||||
};
|
};
|
||||||
const skipServices = [
|
const skipServices = ['persistent_notification'];
|
||||||
'persistent_notification'
|
|
||||||
];
|
|
||||||
|
|
||||||
function parseStates(entities, services, callback) {
|
function parseStates(entities, services, callback) {
|
||||||
const objs = [];
|
const objs = [];
|
||||||
@@ -239,23 +193,29 @@ function parseStates(entities, services, callback) {
|
|||||||
let channel;
|
let channel;
|
||||||
for (let e = 0; e < entities.length; e++) {
|
for (let e = 0; e < entities.length; e++) {
|
||||||
const entity = entities[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;
|
const desc = entity.attributes && entity.attributes.attribution ? entity.attributes.attribution : undefined;
|
||||||
|
|
||||||
channel = {
|
channel = {
|
||||||
_id: `${adapter.namespace}.entities.${entity.entity_id}`,
|
_id: `${adapter.namespace}.entities.${entity.entity_id}`,
|
||||||
common: {
|
common: {
|
||||||
name: name
|
name: name,
|
||||||
},
|
},
|
||||||
type: 'channel',
|
type: 'channel',
|
||||||
native: {
|
native: {
|
||||||
object_id: entity.object_id,
|
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);
|
objs.push(channel);
|
||||||
|
|
||||||
const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined;
|
const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined;
|
||||||
@@ -269,18 +229,20 @@ function parseStates(entities, services, callback) {
|
|||||||
name: `${name} STATE`,
|
name: `${name} STATE`,
|
||||||
type: typeof entity.state,
|
type: typeof entity.state,
|
||||||
read: true,
|
read: true,
|
||||||
write: false
|
write: false,
|
||||||
},
|
},
|
||||||
native: {
|
native: {
|
||||||
object_id: entity.object_id,
|
object_id: entity.object_id,
|
||||||
domain: entity.domain,
|
domain: entity.domain,
|
||||||
entity_id: entity.entity_id
|
entity_id: entity.entity_id,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
if (entity.attributes && entity.attributes.unit_of_measurement) {
|
if (entity.attributes && entity.attributes.unit_of_measurement) {
|
||||||
obj.common.unit = 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);
|
objs.push(obj);
|
||||||
|
|
||||||
let val = entity.state;
|
let val = entity.state;
|
||||||
@@ -288,12 +250,12 @@ function parseStates(entities, services, callback) {
|
|||||||
val = JSON.stringify(val);
|
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) {
|
if (entity.attributes) {
|
||||||
for (const attr in 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') {
|
if (attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -314,8 +276,8 @@ function parseStates(entities, services, callback) {
|
|||||||
object_id: entity.object_id,
|
object_id: entity.object_id,
|
||||||
domain: entity.domain,
|
domain: entity.domain,
|
||||||
entity_id: entity.entity_id,
|
entity_id: entity.entity_id,
|
||||||
attr: attr
|
attr: attr,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
if (!common.name) {
|
if (!common.name) {
|
||||||
common.name = `${name} ${attr.replace(/_/g, ' ')}`;
|
common.name = `${name} ${attr.replace(/_/g, ' ')}`;
|
||||||
@@ -330,7 +292,9 @@ function parseStates(entities, services, callback) {
|
|||||||
common.type = mapTypes[typeof entity.attributes[attr]];
|
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);
|
objs.push(obj);
|
||||||
|
|
||||||
@@ -349,7 +313,7 @@ function parseStates(entities, services, callback) {
|
|||||||
if (services[serviceType] && !skipServices.includes(serviceType)) {
|
if (services[serviceType] && !skipServices.includes(serviceType)) {
|
||||||
const service = services[serviceType];
|
const service = services[serviceType];
|
||||||
for (const s in service) {
|
for (const s in service) {
|
||||||
if (service.hasOwnProperty(s)) {
|
if (Object.prototype.hasOwnProperty.call(service, s)) {
|
||||||
obj = {
|
obj = {
|
||||||
_id: `${adapter.namespace}.entities.${entity.entity_id}.${s}`,
|
_id: `${adapter.namespace}.entities.${entity.entity_id}.${s}`,
|
||||||
type: 'state',
|
type: 'state',
|
||||||
@@ -357,7 +321,7 @@ function parseStates(entities, services, callback) {
|
|||||||
desc: service[s].description,
|
desc: service[s].description,
|
||||||
read: false,
|
read: false,
|
||||||
write: true,
|
write: true,
|
||||||
type: 'mixed'
|
type: 'mixed',
|
||||||
},
|
},
|
||||||
native: {
|
native: {
|
||||||
object_id: entity.object_id,
|
object_id: entity.object_id,
|
||||||
@@ -365,11 +329,13 @@ function parseStates(entities, services, callback) {
|
|||||||
fields: service[s].fields,
|
fields: service[s].fields,
|
||||||
entity_id: entity.entity_id,
|
entity_id: entity.entity_id,
|
||||||
attr: s,
|
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);
|
objs.push(obj);
|
||||||
}
|
}
|
||||||
@@ -377,8 +343,7 @@ function parseStates(entities, services, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syncObjects(objs, () =>
|
syncObjects(objs, () => syncStates(states, callback));
|
||||||
syncStates(states, callback));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
@@ -389,8 +354,7 @@ function main() {
|
|||||||
|
|
||||||
hass = new HASS(adapter.config, adapter.log);
|
hass = new HASS(adapter.config, adapter.log);
|
||||||
|
|
||||||
hass.on('error', err =>
|
hass.on('error', err => adapter.log.error(err));
|
||||||
adapter.log.error(err));
|
|
||||||
|
|
||||||
hass.on('state_changed', entity => {
|
hass.on('state_changed', entity => {
|
||||||
adapter.log.debug(`HASS-Message: State Changed: ${JSON.stringify(entity)}`);
|
adapter.log.debug(`HASS-Message: State Changed: ${JSON.stringify(entity)}`);
|
||||||
@@ -405,12 +369,20 @@ function main() {
|
|||||||
if (hassObjects[`${adapter.namespace}.${id}state`]) {
|
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 {
|
} 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) {
|
if (entity.attributes) {
|
||||||
for (const attr in 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;
|
continue;
|
||||||
}
|
}
|
||||||
let val = entity.attributes[attr];
|
let val = entity.attributes[attr];
|
||||||
@@ -421,7 +393,9 @@ function main() {
|
|||||||
if (hassObjects[`${adapter.namespace}.${id}state`]) {
|
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 {
|
} 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');
|
adapter.log.debug('Connected');
|
||||||
connected = true;
|
connected = true;
|
||||||
adapter.setState('info.connection', true, true);
|
adapter.setState('info.connection', true, true);
|
||||||
hass.getConfig((err, config) => {
|
hass.getConfig(err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
adapter.log.error(`Cannot read config: ${err}`);
|
adapter.log.error(`Cannot read config: ${err}`);
|
||||||
return;
|
return;
|
||||||
@@ -440,7 +414,8 @@ function main() {
|
|||||||
//adapter.log.debug(JSON.stringify(config));
|
//adapter.log.debug(JSON.stringify(config));
|
||||||
delayTimeout = setTimeout(() => {
|
delayTimeout = setTimeout(() => {
|
||||||
delayTimeout = null;
|
delayTimeout = null;
|
||||||
!stopped && hass.getStates((err, states) => {
|
if (!stopped) {
|
||||||
|
hass.getStates((err, states) => {
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -450,7 +425,8 @@ function main() {
|
|||||||
//adapter.log.debug(JSON.stringify(states));
|
//adapter.log.debug(JSON.stringify(states));
|
||||||
delayTimeout = setTimeout(() => {
|
delayTimeout = setTimeout(() => {
|
||||||
delayTimeout = null;
|
delayTimeout = null;
|
||||||
!stopped && hass.getServices((err, services) => {
|
if (!stopped) {
|
||||||
|
hass.getServices((err, services) => {
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -459,12 +435,18 @@ function main() {
|
|||||||
} else {
|
} else {
|
||||||
//adapter.log.debug(JSON.stringify(services));
|
//adapter.log.debug(JSON.stringify(services));
|
||||||
parseStates(states, 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('*');
|
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"
|
"url": "https://github.com/ioBroker/ioBroker.hass"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"websocket": "^1.0.34",
|
"websocket": "^1.0.35",
|
||||||
"ws": "^8.13.0",
|
"ws": "^8.20.0",
|
||||||
"@iobroker/adapter-core": "^2.6.8"
|
"@iobroker/adapter-core": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@alcalzone/release-script": "^3.7.0",
|
"@alcalzone/release-script": "^5.1.1",
|
||||||
"@alcalzone/release-script-plugin-iobroker": "^3.6.0",
|
"@alcalzone/release-script-plugin-iobroker": "^5.1.2",
|
||||||
"@alcalzone/release-script-plugin-license": "^3.5.9",
|
"@alcalzone/release-script-plugin-license": "^5.1.1",
|
||||||
"@iobroker/adapter-dev": "^1.2.0",
|
"@iobroker/adapter-dev": "^1.5.0",
|
||||||
"mocha": "^10.2.0",
|
"@iobroker/eslint-config": "^2.2.0",
|
||||||
"chai": "^4.3.10"
|
"@iobroker/legacy-testing": "^2.0.2",
|
||||||
|
"@iobroker/testing": "^5.2.2",
|
||||||
|
"mocha": "^11.7.5"
|
||||||
},
|
},
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"files": [
|
||||||
|
"lib/",
|
||||||
|
"admin/",
|
||||||
|
"io-package.json",
|
||||||
|
"main.js"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node node_modules/mocha/bin/mocha --exit",
|
"test": "node node_modules/mocha/bin/mocha --exit",
|
||||||
"release": "release-script",
|
"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;
|
||||||
|
});
|
||||||
|
|||||||
+21
-83
@@ -1,84 +1,20 @@
|
|||||||
/* jshint -W097 */// jshint strict:false
|
/* jshint -W097 */
|
||||||
|
/* jshint strict: false */
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
var expect = require('chai').expect;
|
const setup = require('@iobroker/legacy-testing');
|
||||||
var setup = require(__dirname + '/lib/setup');
|
|
||||||
|
|
||||||
var objects = null;
|
let objects = null;
|
||||||
var states = null;
|
let states = null;
|
||||||
var onStateChanged = null;
|
const onStateChanged = null;
|
||||||
var onObjectChanged = null;
|
|
||||||
var sendToID = 1;
|
|
||||||
|
|
||||||
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1);
|
const adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1);
|
||||||
|
|
||||||
function checkConnectionOfAdapter(cb, counter) {
|
describe(`Test ${adapterShortName} adapter`, function () {
|
||||||
counter = counter || 0;
|
before(`Test ${adapterShortName} adapter: Start js-controller`, function (_done) {
|
||||||
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
|
this.timeout(600000); // because of first install from npm
|
||||||
|
|
||||||
setup.setupController(async function () {
|
setup.setupController(async () => {
|
||||||
var config = await setup.getAdapterConfig();
|
const config = await setup.getAdapterConfig();
|
||||||
// enable adapter
|
// enable adapter
|
||||||
config.common.enabled = true;
|
config.common.enabled = true;
|
||||||
config.common.loglevel = 'debug';
|
config.common.loglevel = 'debug';
|
||||||
@@ -87,14 +23,16 @@ describe('Test ' + adapterShortName + ' adapter', function() {
|
|||||||
|
|
||||||
await setup.setAdapterConfig(config.common, config.native);
|
await setup.setAdapterConfig(config.common, config.native);
|
||||||
|
|
||||||
setup.startController(true, function(id, obj) {}, function (id, state) {
|
setup.startController(
|
||||||
if (onStateChanged) onStateChanged(id, state);
|
true,
|
||||||
},
|
(id, obj) => {},
|
||||||
function (_objects, _states) {
|
(id, state) => onStateChanged?.(id, state),
|
||||||
|
(_objects, _states) => {
|
||||||
objects = _objects;
|
objects = _objects;
|
||||||
states = _states;
|
states = _states;
|
||||||
_done();
|
_done();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,11 +53,11 @@ describe('Test ' + adapterShortName + ' adapter', function() {
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
|
after(`Test ${adapterShortName} adapter: Stop js-controller`, function (done) {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
|
|
||||||
setup.stopController(function (normalTerminated) {
|
setup.stopController(normalTerminated => {
|
||||||
console.log('Adapter normal terminated: ' + normalTerminated);
|
console.log(`Adapter normal terminated: ${normalTerminated}`);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,47 +1,5 @@
|
|||||||
/* jshint -W097 */
|
const path = require('path');
|
||||||
/* jshint strict:false */
|
const { tests } = require('@iobroker/testing');
|
||||||
/* jslint node: true */
|
|
||||||
/* jshint expr: true */
|
|
||||||
var expect = require('chai').expect;
|
|
||||||
var fs = require('fs');
|
|
||||||
|
|
||||||
describe('Test package.json and io-package.json', function() {
|
// Validate the package files
|
||||||
it('Test package files', function (done) {
|
tests.packageFiles(path.join(__dirname, '..'));
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -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