Compare commits

...
83 Commits
Author SHA1 Message Date
Ingo Fischer 976c611f97 chore: release v1.4.0
* (Apollon77) Added more guidance logging when setting services incorrectly
* (Apollon77) Prevent crashes when attributes contain "." at the end of their names
* (Apollon77) Added logging for state updates for unknown objects
2023-01-03 22:53:58 +01:00
Ingo Fischer 3887500a62 * update license years 2023-01-03 22:53:11 +01:00
Ingo Fischer 62ef7cabf8 * detect json better 2023-01-03 00:38:58 +01:00
dependabot[bot]andGitHub 8fc6f4ac01 Bump mocha from 10.1.0 to 10.2.0 (#72) 2023-01-01 04:47:06 +00:00
Ingo Fischer 649bdfce2f * fix 2022-12-31 09:22:56 +01:00
Ingo Fischer e0fcc28746 * add translations 2022-12-22 23:29:01 +01:00
Ingo FischerandGitHub 71813c2df9 Merge pull request #71 from ioBrokerTranslator/weblate-adapters-hass
Translations update from ioBroker Translation System
2022-12-22 23:27:42 +01:00
mcm1957andWeblate ac8d3617b0 Translated using Weblate (Spanish)
Currently translated at 0.0% (0 of 6 strings)

Translation: ioBroker Adapters/hass
Translate-URL: https://weblate.iobroker.net/projects/adapters/hass/es/
2022-12-22 21:28:58 +01:00
Ingo Fischer 2986071d2e * update readme 2022-12-21 20:34:47 +01:00
Ingo Fischer 56a9cbbb1c * (Apollon77) Prevent crashes when attributes contain "." at the end of their names
* (Apollon77) Added logging for state updates for unknown objects
2022-12-08 23:31:18 +01:00
Ingo FischerandGitHub 5a8e22b408 Merge pull request #70 from ioBroker/dependabot/npm_and_yarn/minimatch-3.1.2
Bump minimatch from 3.0.4 to 3.1.2
2022-12-08 23:12:34 +01:00
dependabot[bot]andGitHub 1c46a7772f Bump minimatch from 3.0.4 to 3.1.2
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 22:09:10 +00:00
Ingo Fischer 659152be2e * add more guidance on how to call 2022-12-08 23:08:37 +01:00
Ingo Fischer ad8f360c17 * add adapter-dev, remove gulp
* add more guidance on how to call
* update deps
2022-12-08 23:06:36 +01:00
dependabot[bot]andGitHub 88d027d575 Update axios requirement from ^1.1.3 to ^1.2.0 (#67) 2022-12-01 11:50:51 +00:00
dependabot[bot]andGitHub 9727116d46 Update chai requirement from ^4.3.6 to ^4.3.7 (#66) 2022-12-01 08:12:36 +00:00
BluefoxandGitHub 03fba79027 Merge pull request #60 from ioBroker/dependabot/npm_and_yarn/ws-tw-8.10.0
Update ws requirement from ^8.8.1 to ^8.10.0
2022-12-01 01:42:19 +01:00
BluefoxandGitHub 50fd571f0b Merge pull request #63 from ioBroker/dependabot/npm_and_yarn/axios-1.1.3
Bump axios from 0.27.2 to 1.1.3
2022-12-01 01:42:04 +01:00
BluefoxandGitHub 39bd989a41 Merge pull request #64 from lgtm-migrator/codeql
Add CodeQL workflow for GitHub code scanning
2022-12-01 01:41:54 +01:00
LGTM Migrator 407b5bfb07 Add CodeQL workflow for GitHub code scanning 2022-11-10 18:24:33 +00:00
dependabot[bot]andGitHub cdc3aba0b2 Update ws requirement from ^8.8.1 to ^8.10.0
Updates the requirements on [ws](https://github.com/websockets/ws) to permit the latest version.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.8.1...8.10.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-01 09:23:11 +00:00
dependabot[bot]andGitHub 8a2933411f Update @iobroker/adapter-core requirement from ^2.6.6 to ^2.6.7 (#62) 2022-11-01 09:21:45 +00:00
dependabot[bot]andGitHub 68c6bd989f Bump axios from 0.27.2 to 1.1.3
Bumps [axios](https://github.com/axios/axios) from 0.27.2 to 1.1.3.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.27.2...v1.1.3)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-01 06:06:26 +00:00
dependabot[bot]andGitHub e03a2cfc80 Update mocha requirement from ^10.0.0 to ^10.1.0 (#61) 2022-11-01 06:04:19 +00:00
dependabot[bot]andGitHub 9558c39deb Update @iobroker/adapter-core requirement from ^2.6.0 to ^2.6.6 (#58) 2022-10-01 04:01:09 +00:00
dependabot[bot]andGitHub 2956ee506a Update ws requirement from ^8.8.0 to ^8.8.1 (#57) 2022-08-01 03:13:53 +00:00
Ingo Fischer 62ce173d22 chore: release v1.3.0
* (Apollon77) Further optimize sending data to HASS and allow to set values like numbers as normal states if the service has one attribute and it can be mapped
2022-07-01 10:10:38 +02:00
Ingo Fischer 45b0550e0c * add logging 2022-06-30 17:42:24 +02:00
Ingo Fischer 0ac6c66aaf * (Apollon77) Further optimize sending data to HASS and allow to set values like numbers as normal states if the service has one attribute and it can be mapped 2022-06-30 11:51:23 +02:00
Ingo Fischer b54916d743 chore: release v1.2.0
* (Apollon77) IMPORTANT: Replace special characters in entity attribute names with an underscore! Object IDs might change!
* (Apollon77) make sure a "null" value in state changes is not crashing
2022-06-17 23:23:11 +02:00
Ingo Fischer 52d8bddb11 * (Apollon77) make sure a "null" value in state changes is not crashing 2022-06-17 23:20:26 +02:00
dependabot[bot]andGitHub 271bd03d14 Update @alcalzone/release-script requirement from ^3.5.8 to ^3.5.9 (#53) 2022-06-01 09:45:24 +00:00
dependabot[bot]andGitHub 36bc7c99fd Update @alcalzone/release-script-plugin-license requirement (#51) 2022-06-01 07:48:32 +00:00
dependabot[bot]andGitHub cc76a49d62 Update @alcalzone/release-script-plugin-iobroker requirement (#54) 2022-06-01 07:05:24 +00:00
dependabot[bot]andGitHub 7a2475ba02 Update @alcalzone/release-script-plugin-license requirement (#47) 2022-05-01 14:15:54 +00:00
dependabot[bot]andGitHub 47cb4cfad8 Update @alcalzone/release-script requirement from ^3.5.6 to ^3.5.8 (#49) 2022-05-01 11:54:23 +00:00
dependabot[bot]andGitHub 2a473f88b0 Update @alcalzone/release-script-plugin-iobroker requirement (#48) 2022-05-01 09:16:22 +00:00
dependabot[bot]andGitHub 245032a178 Bump axios from 0.26.1 to 0.27.2 (#46) 2022-05-01 05:37:18 +00:00
BluefoxandGitHub 96e6c5b471 Merge pull request #45 from ioBroker/dependabot/github_actions/actions/checkout-3
Bump actions/checkout from 2 to 3
2022-04-03 14:39:36 +02:00
dependabot[bot]andGitHub 82614cdad0 Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 04:20:10 +00:00
Ingo Fischer e6b58530d4 chore: release v1.1.2
* (Apollon77) Fix crash cases reported by Sentry
2022-03-29 09:07:54 +02:00
Ingo Fischer dc47f48004 * (Apollon77) Fix crash cases reported by Sentry (IOBROKER-HASS-2) 2022-03-29 08:59:10 +02:00
Ingo Fischer 4096826099 chore: release v1.1.1
* (Apollon77) Show password fields masked again in config
2022-03-25 09:25:45 +01:00
Ingo Fischer 3021e3f5a5 * (Apollon77) Show password fields masked again in config 2022-03-25 09:25:32 +01:00
Ingo Fischer a4a7abedbe chore: release v1.1.0
* IMPORTANT: You need to re-enter the password once after installing this version!
* (Apollon77) Implement Service triggers to use any value to trigger or stringified JSON to call with fields
* (Apollon77) Optimize unload handling
* (Apollon7) Add Sentry for crash reporting
2022-03-24 21:33:49 +01:00
Ingo Fischer 9ab4db078f * add message for admin to io-package 2022-03-24 21:33:35 +01:00
Ingo Fischer 2ec6bf8150 * add documentation on how to call services 2022-03-24 18:31:15 +01:00
Ingo Fischer 0dd97e69c8 * update ui and translations 2022-03-23 10:28:24 +01:00
Ingo Fischer 8ff8341088 * update ui and translations 2022-03-23 08:23:24 +01:00
Ingo Fischer 0cb8ed8a13 * update ui and translations 2022-03-23 08:23:16 +01:00
Ingo Fischer 46d58e00e3 * rework index_m 2022-03-23 08:07:07 +01:00
Ingo Fischer 0fa719edd0 * rework index_m 2022-03-23 08:04:50 +01:00
Ingo Fischer 5cb493481e * IMPORTANT: You need to re-enter the password once after installing this version!
* rework index_m
2022-03-22 22:57:40 +01:00
Ingo Fischer 0d28c7e54b * revert 2022-03-22 17:18:29 +01:00
Ingo Fischer d89288a0b8 * ignore ignored services also when changes come in 2022-03-22 15:15:04 +01:00
Ingo Fischer f03bf3d9a6 * only process state changes tat are not null 2022-03-21 22:59:35 +01:00
Ingo Fischer 3b83e686f7 * (Apollon77) Implement Service triggers to use any value to trigger or stringified JSON to call with fields 2022-03-21 17:11:48 +01:00
BluefoxandGitHub b887a75ede Merge pull request #38 from ioBroker/dependabot/github_actions/actions/setup-node-3
Bump actions/setup-node from 2.5.1 to 3
2022-03-07 18:45:58 +01:00
BluefoxandGitHub 96bc8592b3 Merge pull request #42 from ioBroker/dependabot/npm_and_yarn/ws-tw-8.5.0
Update ws requirement from ^8.2.3 to ^8.5.0
2022-03-07 18:45:47 +01:00
BluefoxandGitHub 4d939862d6 Merge branch 'master' into dependabot/npm_and_yarn/ws-tw-8.5.0 2022-03-07 18:45:37 +01:00
BluefoxandGitHub d297bf3d27 Merge pull request #41 from ioBroker/dependabot/npm_and_yarn/iobroker/adapter-core-tw-2.6.0
Update @iobroker/adapter-core requirement from ^2.5.1 to ^2.6.0
2022-03-07 18:45:17 +01:00
dependabot[bot]andGitHub ba6f42601a Update mocha requirement from ^9.2.0 to ^9.2.1 (#40) 2022-03-01 09:22:32 +00:00
dependabot[bot]andGitHub 46eebb0a82 Update ws requirement from ^8.2.3 to ^8.5.0
Updates the requirements on [ws](https://github.com/websockets/ws) to permit the latest version.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.2.3...8.5.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 05:59:32 +00:00
dependabot[bot]andGitHub 3ffe4fe555 Update @iobroker/adapter-core requirement from ^2.5.1 to ^2.6.0
Updates the requirements on [@iobroker/adapter-core](https://github.com/ioBroker/adapter-core) to permit the latest version.
- [Release notes](https://github.com/ioBroker/adapter-core/releases)
- [Commits](https://github.com/ioBroker/adapter-core/compare/v2.5.1...v2.6.0)

---
updated-dependencies:
- dependency-name: "@iobroker/adapter-core"
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 05:59:26 +00:00
dependabot[bot]andGitHub 50962fb833 Bump actions/setup-node from 2.5.1 to 3
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.5.1 to 3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.5.1...v3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-01 04:21:24 +00:00
dependabot[bot]andGitHub aa7a06d750 Update chai requirement from ^4.3.4 to ^4.3.6 (#35) 2022-02-01 18:00:09 +00:00
dependabot[bot]andGitHub 7c966c5bd9 Update mocha requirement from ^9.1.3 to ^9.2.0 (#34) 2022-02-01 05:03:43 +00:00
Ingo Fischer 1f1fc7ef43 update testAdapter.js 2022-01-29 16:08:30 +01:00
Ingo Fischer 4e19c7e334 add .mocharc.json 2022-01-29 15:27:39 +01:00
Ingo Fischer 84dee7cd6a add mocha.setup.js 2022-01-29 15:27:39 +01:00
Ingo Fischer 5d8816f464 update testAdapter.js 2022-01-29 15:27:39 +01:00
Ingo Fischer f1376e5e5b update testing setup.js 2022-01-29 15:27:38 +01:00
dependabot[bot]andGitHub d042d65588 Bump actions/setup-node from 2.5.0 to 2.5.1 (#29) 2022-01-01 04:27:42 +00:00
BluefoxandGitHub 088113848d Merge pull request #28 from ioBroker/dependabot/github_actions/actions/setup-node-2.5.0
Bump actions/setup-node from 2.4.1 to 2.5.0
2021-12-14 13:15:57 +01:00
dependabot[bot]andGitHub c8be38859d Bump actions/setup-node from 2.4.1 to 2.5.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v2.4.1...v2.5.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-01 03:05:45 +00:00
dependabot[bot]andGitHub fd5517c976 Update mocha requirement from ^9.1.2 to ^9.1.3 (#24) 2021-11-01 06:03:44 +00:00
dependabot[bot]andGitHub aedde9a4b9 Update ws requirement from ^8.2.2 to ^8.2.3 (#23) 2021-11-01 05:35:58 +00:00
BluefoxandGitHub a148fa4e33 Merge pull request #22 from ioBroker/dependabot/npm_and_yarn/mocha-tw-9.1.2
Update mocha requirement from ^9.1.1 to ^9.1.2
2021-10-05 18:42:21 +02:00
BluefoxandGitHub 1451e497d8 Merge pull request #21 from ioBroker/dependabot/npm_and_yarn/ws-tw-8.2.2
Update ws requirement from ^8.2.1 to ^8.2.2
2021-10-05 18:42:12 +02:00
dependabot[bot]andGitHub 85ef3afc04 Bump actions/setup-node from 2.4.0 to 2.4.1 (#19) 2021-10-01 03:36:34 +00:00
dependabot[bot]andGitHub a68f254e60 Update mocha requirement from ^9.1.1 to ^9.1.2
Updates the requirements on [mocha](https://github.com/mochajs/mocha) to permit the latest version.
- [Release notes](https://github.com/mochajs/mocha/releases)
- [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mochajs/mocha/compare/v9.1.1...v9.1.2)

---
updated-dependencies:
- dependency-name: mocha
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-01 03:10:27 +00:00
dependabot[bot]andGitHub a75fbf84bb Update ws requirement from ^8.2.1 to ^8.2.2
Updates the requirements on [ws](https://github.com/websockets/ws) to permit the latest version.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.2.1...8.2.2)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-01 03:10:10 +00:00
Ingo Fischer 66953a6e0d update testing setup.js 2021-09-05 00:09:57 +02:00
32 changed files with 7614 additions and 978 deletions
+1 -1
View File
@@ -12,5 +12,5 @@ updates:
interval: monthly interval: monthly
time: "04:00" time: "04:00"
timezone: Europe/Berlin timezone: Europe/Berlin
open-pull-requests-limit: 20 open-pull-requests-limit: 5
versioning-strategy: increase versioning-strategy: increase
+41
View File
@@ -0,0 +1,41 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "4 5 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ javascript ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Check if PR should be auto-merged - name: Check if PR should be auto-merged
uses: ahmadnassri/action-dependabot-auto-merge@v2 uses: ahmadnassri/action-dependabot-auto-merge@v2
+27 -25
View File
@@ -16,6 +16,10 @@ on:
- "v?[0-9]+.[0-9]+.[0-9]+-**" - "v?[0-9]+.[0-9]+.[0-9]+-**"
pull_request: {} pull_request: {}
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
# Performs quick checks before the expensive test runs # Performs quick checks before the expensive test runs
check-and-lint: check-and-lint:
@@ -25,18 +29,18 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [16.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.0 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm ci
# - name: Perform a type check # - name: Perform a type check
# run: npm run check:ts # run: npm run check:ts
@@ -56,18 +60,18 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
node-version: [12.x, 14.x, 16.x] node-version: [12.x, 14.x, 16.x, 18.x]
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.0 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm ci
- name: Run local tests - name: Run local tests
run: npm test run: npm test
@@ -92,14 +96,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [16.x]
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2.4.0 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
@@ -118,7 +122,7 @@ jobs:
echo "::set-output name=BODY::$BODY" echo "::set-output name=BODY::$BODY"
- name: Install Dependencies - name: Install Dependencies
run: npm install run: npm ci
# - name: Create a clean build # - name: Create a clean build
# run: npm run build # run: npm run build
@@ -140,19 +144,17 @@ jobs:
prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }}
body: ${{ steps.extract_release.outputs.BODY }} body: ${{ steps.extract_release.outputs.BODY }}
#- name: Notify Sentry.io about the release - name: Notify Sentry.io about the release
# run: | run: |
# npm i -g @sentry/cli npm i -g @sentry/cli
# export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
# export SENTRY_URL=https://sentry.iobroker.net export SENTRY_URL=https://sentry.iobroker.net
# export SENTRY_ORG=iobroker export SENTRY_ORG=iobroker
# export SENTRY_PROJECT=iobroker-hass export SENTRY_PROJECT=iobroker-hass
# export SENTRY_VERSION=iobroker.hass@${{ steps.extract_release.outputs.VERSION }} export SENTRY_VERSION=iobroker.hass@${{ steps.extract_release.outputs.VERSION }}
# sentry-cli releases new $SENTRY_VERSION sentry-cli releases new $SENTRY_VERSION
# sentry-cli releases finalize $SENTRY_VERSION sentry-cli releases set-commits $SENTRY_VERSION --auto
sentry-cli releases finalize $SENTRY_VERSION
# Add the following line BEFORE finalize if repositories are connected in Sentry
# sentry-cli releases set-commits $SENTRY_VERSION --auto
# Add the following line BEFORE finalize if sourcemap uploads are needed # Add the following line BEFORE finalize if sourcemap uploads are needed
# sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/ # sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/
+5
View File
@@ -0,0 +1,5 @@
{
"require": [
"./test/mocha.setup.js"
]
}
-2
View File
@@ -1,6 +1,4 @@
/**/* /**/*
/gulpfile.js
gulpfile.js
!/admin/**/* !/admin/**/*
!/admin/* !/admin/*
!/lib/**/* !/lib/**/*
+3
View File
@@ -0,0 +1,3 @@
{
"plugins": ["iobroker", "license"]
}
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2018-2020 bluefox <dogafox@gmail.com> Copyright (c) 2018-2023 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
+103 -1
View File
@@ -1,15 +1,94 @@
![Logo](admin/hass.png) ![Logo](admin/hass.png)
# ioBroker.hass # ioBroker.hass
![Number of Installations](http://iobroker.live/badges/hass-installed.svg)
![Number of Installations](http://iobroker.live/badges/hass-stable.svg)
[![NPM version](http://img.shields.io/npm/v/iobroker.hass.svg)](https://www.npmjs.com/package/iobroker.hass) [![NPM version](http://img.shields.io/npm/v/iobroker.hass.svg)](https://www.npmjs.com/package/iobroker.hass)
![Test and Release](https://github.com/ioBroker/ioBroker.hass/workflows/Test%20and%20Release/badge.svg)
[![Translation status](https://weblate.iobroker.net/widgets/adapters/-/hass/svg-badge.svg)](https://weblate.iobroker.net/engage/adapters/?utm_source=widget)
[![Downloads](https://img.shields.io/npm/dm/iobroker.hass.svg)](https://www.npmjs.com/package/iobroker.hass) [![Downloads](https://img.shields.io/npm/dm/iobroker.hass.svg)](https://www.npmjs.com/package/iobroker.hass)
**This adapter uses Sentry libraries to automatically report exceptions and code errors to the developers.** For more details and for information how to disable the error reporting see [Sentry-Plugin Documentation](https://github.com/ioBroker/plugin-sentry#plugin-sentry)! Sentry reporting is used starting with js-controller 3.0.
This adapter allows the connecting of Home Assistant to ioBroker. This adapter allows the connecting of Home Assistant to ioBroker.
## Usage ## Usage
Create a long term token in HASS and use it as PW (copy it also in the repeat field). Create a long term token in HASS and use it as PW (copy it also in the repeat field).
Then it should read out all attributes for all devices. Services might be controllable (e.g. "turn_on"). To control services you have two options:
### Set a direct value
Set the state with an ack=false value which is not a string (e.g. Boolean true) then it will be triggered also in HASS without additional service data. This will only work if the service has one field to be sent - then the value is sent as this field! If the service has more than one field you will find a warning in the log that provides more details about the fields taht are possible to be sent, e.g.
```
Please make sure to provide a stringified JSON as value to set relevant fields! Please refer to the Readme for details!
Allowed field keys are: temperature, target_temp_high, target_temp_low, hvac_mode
```
### 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:
`
...
native: {
"fields": {
"transition": {
"name": "Transition",
"description": "Duration it takes to get to next state.",
"selector": {
"number": {
"min": 0,
"max": 300,
"unit_of_measurement": "seconds"
}
}
},
"flash": {
"name": "Flash",
"description": "If the light should flash.",
"advanced": true,
"selector": {
"select": {
"options": [
"long",
"short"
]
}
}
}
},
"entity_id": "light.mi_control_hub_light",
"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:
```
...
native: {
"fields": {
"speed": {
"name": "Speed",
"description": "Speed setting.",
"required": true,
"example": "low",
"selector": {
"text": null
}
}
}
...
}
...
```
## Configuration ## Configuration
There is a good article about connection. There is a good article about connection.
@@ -23,6 +102,29 @@ Please check it https://www.smarthomejetzt.de/mit-iobroker-auf-eine-home-assista
--> -->
## Changelog ## Changelog
### 1.4.0 (2023-01-03)
* (Apollon77) Added more guidance logging when setting services incorrectly
* (Apollon77) Prevent crashes when attributes contain "." at the end of their names
* (Apollon77) Added logging for state updates for unknown objects
### 1.3.0 (2022-07-01)
* (Apollon77) Further optimize sending data to HASS and allow to set values like numbers as normal states if the service has one attribute and it can be mapped
### 1.2.0 (2022-06-17)
* (Apollon77) IMPORTANT: Replace special characters in entity attribute names with an underscore! Object IDs might change!
* (Apollon77) make sure a "null" value in state changes is not crashing
### 1.1.2 (2022-03-29)
* (Apollon77) Fix crash cases reported by Sentry
### 1.1.1 (2022-03-25)
* (Apollon77) Show password fields masked again in config
### 1.1.0 (2022-03-24)
* IMPORTANT: You need to re-enter the password once after installing this version!
* (Apollon77) Implement Service triggers to use any value to trigger or stringified JSON to call with fields
* (Apollon77) Optimize unload handling
* (Apollon7) Add Sentry for crash reporting
### 1.0.1 (2021-09-04) ### 1.0.1 (2021-09-04)
* IMPORTANT: js-controller 2.0 is needed st least! * IMPORTANT: js-controller 2.0 is needed st least!
@@ -38,7 +140,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-2020 bluefox <dogafox@gmail.com> Copyright (c) 2018-2023 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
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "IP-Adresse des Heimassistenten:",
"Home assistant WS Port:": "Home Assistant WS-Port:",
"Password repeat:": "Passwort wiederholen:",
"Password:": "Passwort:",
"Passwords missmatch!": "Passwörter stimmen nicht überein!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "Home assistant IP:",
"Home assistant WS Port:": "Home assistant WS Port:",
"Password repeat:": "Password repeat:",
"Password:": "Password:",
"Passwords missmatch!": "Passwords missmatch!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "IP del asistente de hogar:",
"Home assistant WS Port:": "Asistente doméstico Puerto WS:",
"Password repeat:": "Repite la contraseña:",
"Password:": "Clave:",
"Passwords missmatch!": "¡Las contraseñas no coinciden!",
"Secure:": "¿HTTPS?:"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "IP de l'assistant à domicile :",
"Home assistant WS Port:": "Port WS de l'assistant domestique :",
"Password repeat:": "Répéter le mot de passe :",
"Password:": "Mot de passe:",
"Passwords missmatch!": "Les mots de passe ne correspondent pas !",
"Secure:": "HTTPS ?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "IP assistente domestico:",
"Home assistant WS Port:": "Porta WS dell'assistente domestico:",
"Password repeat:": "Ripeti password:",
"Password:": "Parola d'ordine:",
"Passwords missmatch!": "Le password non corrispondono!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "Thuisassistent IP:",
"Home assistant WS Port:": "Thuisassistent WS Poort:",
"Password repeat:": "Wachtwoord herhalen:",
"Password:": "Wachtwoord:",
"Passwords missmatch!": "Wachtwoorden komen niet overeen!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "Adres IP asystenta domowego:",
"Home assistant WS Port:": "Asystent domowy Port WS:",
"Password repeat:": "Powtórz hasło:",
"Password:": "Hasło:",
"Passwords missmatch!": "Niezgodność haseł!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "IP do assistente doméstico:",
"Home assistant WS Port:": "Assistente doméstico Porta WS:",
"Password repeat:": "Repetição de senha:",
"Password:": "Senha:",
"Passwords missmatch!": "As senhas não correspondem!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "Домашний помощник IP:",
"Home assistant WS Port:": "Домашний помощник WS Порт:",
"Password repeat:": "Повтор пароля:",
"Password:": "Пароль:",
"Passwords missmatch!": "Пароли не совпадают!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "Домашній помічник IP:",
"Home assistant WS Port:": "Домашній помічник WS Порт:",
"Password repeat:": "Повторення пароля:",
"Password:": "Пароль:",
"Passwords missmatch!": "Паролі не збігаються!",
"Secure:": "HTTPS?"
}
+8
View File
@@ -0,0 +1,8 @@
{
"Home assistant IP:": "家庭助理IP",
"Home assistant WS Port:": "家庭助理 WS 端口:",
"Password repeat:": "密码重复:",
"Password:": "密码:",
"Passwords missmatch!": "密码不匹配!",
"Secure:": "HTTPS"
}
-76
View File
@@ -1,76 +0,0 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<script type="text/javascript">
function load(settings, onChange) {
if (!settings) return;
if (settings.password === undefined) settings.password = '';
if (settings.prompt === undefined) settings.prompt = 'hass>';
$('.value').each(function () {
var key = $(this).attr('id');
var $value = $('#' + key + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', settings[key]).change(function() {
onChange();
});
} else {
$value.val(settings[key]).change(function() {
onChange();
}).keyup(function() {
onChange();
});
}
});
// Signal to admin, that no changes yet
onChange(false);
}
function save(callback) {
var obj = {};
if ($('#password').val() !== $('#passwordRepeat').val()) {
showMessage(_('Passwords missmatch!'), _('Warning'), 'alert');
return;
}
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
callback(obj);
}
</script>
</head>
<body>
<div id="adapter-container">
<table><tr>
<td><img src="hass.png"/></td>
<td><h3 class="translate">FHEM adapter settings</h3></td>
</tr></table>
<table>
<tr><td><label class="translate" for="host">Home assistant IP:</label></td><td class="admin-icon"></td><td><input class="value" id="host" /></td></tr>
<tr><td><label class="translate" for="port">Home assistant WS Port:</label></td><td class="admin-icon"></td><td><input class="value" id="port" size="5" maxlength="5"/></td></tr>
<tr><td><label class="translate" for="password">Password:</label></td><td class="admin-icon"></td><td><input class="value" id="password" type="password"/></td></tr>
<tr><td><label class="translate" for="passwordRepeat">Password repeat:</label></td><td class="admin-icon"></td><td><input type="password" id="passwordRepeat"/></td></tr>
<tr><td><label class="translate" for="secure">Secure:</label></td><td class="admin-icon"></td><td><input class="value" id="secure" type="checkbox"/></td></tr>
</table>
</div>
</body>
</html>
+49 -16
View File
@@ -1,12 +1,14 @@
<html> <html>
<head> <head>
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/> <!-- Materialze style -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/> <link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script> <script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script> <script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script> <script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script> <script type="text/javascript" src="words.js"></script>
@@ -16,7 +18,7 @@
if (!settings) return; if (!settings) return;
if (settings.password === undefined) settings.password = ''; if (settings.password === undefined) settings.password = '';
if (settings.prompt === undefined) settings.prompt = 'hass>'; settings.passwordRepeat = settings.password;
$('.value').each(function () { $('.value').each(function () {
var key = $(this).attr('id'); var key = $(this).attr('id');
@@ -53,23 +55,54 @@
obj[$this.attr('id')] = $this.val(); obj[$this.attr('id')] = $this.val();
} }
}); });
delete obj.passwordRepeat;
callback(obj); callback(obj);
} }
</script> </script>
</head> </head>
<body> <body>
<div id="adapter-container"> <div class="m adapter-container">
<table><tr> <div class="row">
<td><img src="hass.png" alt="logo"/></td> <div class="col s12 m4 l2">
<td><h3 class="translate">Home assistant adapter settings</h3></td> <img src="hass.png" class="logo">
</tr></table> </div>
<table> </div>
<tr><td><label class="translate" for="host">Home assistant IP:</label></td><td class="admin-icon"></td><td><input class="value" id="host" /></td></tr>
<tr><td><label class="translate" for="port">Home assistant WS Port:</label></td><td class="admin-icon"></td><td><input class="value" id="port" size="5" maxlength="5"/></td></tr> <div class="row">
<tr><td><label class="translate" for="password">Password:</label></td><td class="admin-icon"></td><td><input class="value" id="password" type="password"/></td></tr> <div class="col s12 m8 l8">
<tr><td><label class="translate" for="passwordRepeat">Password repeat:</label></td><td class="admin-icon"></td><td><input type="password" id="passwordRepeat"/></td></tr> <div class="col s4 input-field">
<tr><td><label class="translate" for="secure">Secure:</label></td><td class="admin-icon"></td><td><input class="value" id="secure" type="checkbox"/></td></tr> <input type="text" class="value" id="host" />
</table> <label for="host" class="translate">Home assistant IP:</label>
</div>
<div class="col s4 input-field">
<input type="text" class="value" id="port" size="5" maxlength="5"/>
<label for="port" class="translate">Home assistant WS Port:</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12 m8 l8">
<div class="col s6 m4 input-field">
<input type="password" class="value" id="password" />
<label for="password" class="translate">Password:</label>
</div>
<div class="col s6 m4 input-field">
<input type="password" class="value" id="passwordRepeat" />
<label for="passwordRepeat" class="translate">Password repeat:</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12 m8 l8">
<div class="col s6 m4 input-field">
<input id="secure" type="checkbox" class="value" />
<span for="secure" class="translate">Secure:</span>
</div>
</div>
</div>
</div> </div>
</body> </body>
</html> </html>
+15 -17
View File
@@ -1,19 +1,17 @@
/*global systemDictionary:true */
/*
+===================== DO NOT MODIFY ======================+
| This file was generated by translate-adapter, please use |
| `translate-adapter adminLanguages2words` to update it. |
+===================== DO NOT MODIFY ======================+
*/
'use strict';
systemDictionary = { systemDictionary = {
"FHEM adapter settings":{"de": "FHEM Adapter Einstellungen", "ru": "Настройки драйвера FHEM"}, "Home assistant IP:": { "en": "Home assistant IP:", "de": "IP-Adresse des Heimassistenten:", "ru": "Домашний помощник IP:", "pt": "IP do assistente doméstico:", "nl": "Thuisassistent IP:", "fr": "IP de l'assistant à domicile :", "it": "IP assistente domestico:", "es": "IP del asistente de hogar:", "pl": "Adres IP asystenta domowego:", "uk": "Домашній помічник IP:", "zh-cn": "家庭助理IP"},
"FHEM Telnet IP:": {"de": "FHEM Telnet IP:", "ru": "FHEM Telnet IP:"}, "Home assistant WS Port:": { "en": "Home assistant WS Port:", "de": "Home Assistant WS-Port:", "ru": "Домашний помощник WS Порт:", "pt": "Assistente doméstico Porta WS:", "nl": "Thuisassistent WS Poort:", "fr": "Port WS de l'assistant domestique :", "it": "Porta WS dell'assistente domestico:", "es": "Asistente doméstico Puerto WS:", "pl": "Asystent domowy Port WS:", "uk": "Домашній помічник WS Порт:", "zh-cn": "家庭助理 WS 端口:"},
"FHEM Telnet Port:": {"en": "FHEM Telnet Port:", "de": "FHEM Telnet Port:", "ru": "FHEM Telnet порт:"}, "Password repeat:": { "en": "Password repeat:", "de": "Passwort wiederholen:", "ru": "Повтор пароля:", "pt": "Repetição de senha:", "nl": "Wachtwoord herhalen:", "fr": "Répéter le mot de passe :", "it": "Ripeti password:", "es": "Repite la contraseña:", "pl": "Powtórz hasło:", "uk": "Повторення пароля:", "zh-cn": "密码重复:"},
"Password:": {"en": "Password:", "de": "Kennwort:", "ru": "Пароль:"}, "Password:": { "en": "Password:", "de": "Passwort:", "ru": "Пароль:", "pt": "Senha:", "nl": "Wachtwoord:", "fr": "Mot de passe:", "it": "Parola d'ordine:", "es": "Clave:", "pl": "Hasło:", "uk": "Пароль:", "zh-cn": "密码:"},
"Password repeat:": {"en": "Password repeat:", "de": "Kennwort-Woederholung:", "ru": "Подтверждение пароля:"}, "Passwords missmatch!": { "en": "Passwords missmatch!", "de": "Passwörter stimmen nicht überein!", "ru": "Пароли не совпадают!", "pt": "As senhas não correspondem!", "nl": "Wachtwoorden komen niet overeen!", "fr": "Les mots de passe ne correspondent pas !", "it": "Le password non corrispondono!", "es": "¡Las contraseñas no coinciden!", "pl": "Niezgodność haseł!", "uk": "Паролі не збігаються!", "zh-cn": "密码不匹配!"},
"Passwords missmatch!": {"en": "Passwords missmatch!", "de": "Kennworte sind unterschiedlich!", "ru": "Пароли не совпадают!"}, "Secure:": { "en": "HTTPS?", "de": "HTTPS?", "ru": "HTTPS?", "pt": "HTTPS?", "nl": "HTTPS?", "fr": "HTTPS ?", "it": "HTTPS?", "es": "¿HTTPS?:", "pl": "HTTPS?", "uk": "HTTPS?", "zh-cn": "HTTPS"},
"Prompt:": {"en": "Prompt:", "de": "Prompt:", "ru": "Промпт:"},
"tooltip_password": {
"en": "Optional. Only if FHEM requires password.",
"de": "Optional. Nur falls in FHEM konfiguriert",
"ru": "Не обязательно. Только если настроено в FHEM"
},
"tooltip_prompt": {
"en": "You can change standard prompt from 'hass>' to something else.",
"de": "Man kann hier ein eigenes Prompt setzen. Default ist 'hass>'",
"ru": "Можно задать собственный промпт. По умолчанию 'hass>'"
}
}; };
-403
View File
@@ -1,403 +0,0 @@
'use strict';
const gulp = require('gulp');
const fs = require('fs');
const pkg = require('./package.json');
const iopackage = require('./io-package.json');
const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
/*var appName = getAppName();
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 1].split('.')[0].toLowerCase();
}
*/
const fileName = 'words.js';
let languages = {
en: {},
de: {},
ru: {},
pt: {},
nl: {},
fr: {},
it: {},
es: {},
pl: {},
'zh-cn': {}
};
function lang2data(lang, isFlat) {
let str = isFlat ? '' : '{\n';
let count = 0;
for (const w in lang) {
if (lang.hasOwnProperty(w)) {
count++;
if (isFlat) {
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
} else {
const key = ' "' + w.replace(/"/g, '\\"') + '": ';
str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n';
}
}
}
if (!count) {
return isFlat ? '' : '{\n}';
}
if (isFlat) {
return str;
} else {
return str.substring(0, str.length - 2) + '\n}';
}
}
function readWordJs(src) {
try {
let words;
if (fs.existsSync(src + 'js/' + fileName)) {
words = fs.readFileSync(src + 'js/' + fileName).toString();
} else {
words = fs.readFileSync(src + fileName).toString();
}
const lines = words.split(/\r\n|\r|\n/g);
let i = 0;
while (!lines[i].match(/^systemDictionary = {/)) {
i++;
}
lines.splice(0, i);
// remove last empty lines
i = lines.length - 1;
while (!lines[i]) {
i--;
}
if (i < lines.length - 1) {
lines.splice(i + 1);
}
lines[0] = lines[0].replace('systemDictionary = ', '');
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
words = lines.join('\n');
const resultFunc = new Function('return ' + words + ';');
return resultFunc();
} catch (e) {
return null;
}
}
function padRight(text, totalLength) {
return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : '');
}
function writeWordJs(data, src) {
let text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
text += '/*global systemDictionary:true */\n';
text += '\'use strict\';\n\n';
text += 'systemDictionary = {\n';
for (const word in data) {
if (data.hasOwnProperty(word)) {
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
let line = '';
for (const lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' ';
}
}
if (line) {
line = line.trim();
line = line.substring(0, line.length - 1);
}
text += line + '},\n';
}
}
text = text.replace(/},\n$/, '}\n');
text += '};';
if (fs.existsSync(src + 'js/' + fileName)) {
fs.writeFileSync(src + 'js/' + fileName, text);
} else {
fs.writeFileSync(src + '' + fileName, text);
}
}
const EMPTY = '';
function words2languages(src) {
const langs = Object.assign({}, languages);
const data = readWordJs(src);
if (data) {
for (const word in data) {
if (data.hasOwnProperty(word)) {
for (const lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (const j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (const l in langs) {
if (!langs.hasOwnProperty(l)) continue;
const keys = Object.keys(langs[l]);
//keys.sort();
let obj = {};
for (let k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
if (!fs.existsSync(src + 'i18n/' + l)) {
fs.mkdirSync(src + 'i18n/' + l);
}
fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj));
}
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function words2languagesFlat(src) {
let langs = Object.assign({}, languages);
const data = readWordJs(src);
if (data) {
for (const word in data) {
if (data.hasOwnProperty(word)) {
for (const lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (const j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
const keys = Object.keys(langs.en);
//keys.sort();
for (const l in langs) {
if (!langs.hasOwnProperty(l)) continue;
let obj = {};
for (let k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
langs[l] = obj;
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (const ll in langs) {
if (!langs.hasOwnProperty(ll)) continue;
if (!fs.existsSync(src + 'i18n/' + ll)) {
fs.mkdirSync(src + 'i18n/' + ll);
}
fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en));
}
fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n'));
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function languagesFlat2words(src) {
const dirs = fs.readdirSync(src + 'i18n/');
let langs = {};
let bigOne = {};
const order = Object.keys(languages);
dirs.sort((a, b) => {
const posA = order.indexOf(a);
const posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
for (let l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
let lang = dirs[l];
const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
langs[lang] = {};
keys.forEach(function (word, i) {
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
});
const words = langs[lang];
for (const word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
const aWords = readWordJs();
const temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
if (aWords) {
// Merge words together
for (const w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
function languages2words(src) {
const dirs = fs.readdirSync(src + 'i18n/');
let langs = {};
let bigOne = {};
const order = Object.keys(languages);
dirs.sort((a, b) => {
const posA = order.indexOf(a);
const posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
for (let l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
const lang = dirs[l];
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
langs[lang] = JSON.parse(langs[lang]);
const words = langs[lang];
for (const word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
const aWords = readWordJs();
const temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
if (aWords) {
// Merge words together
for (const w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
gulp.task('admin (words.js => json)', done => {
words2languages('./admin/');
done();
});
gulp.task('admin (words.js => flat)', done => {
words2languagesFlat('./admin/');
done();
});
gulp.task('admin (flat => words.js)', done => {
languagesFlat2words('./admin/');
done();
});
gulp.task('admin (json => words.js)', done => {
languages2words('./admin/');
done();
});
gulp.task('updatePackages', done => {
iopackage.common.version = pkg.version;
iopackage.common.news = iopackage.common.news || {};
if (!iopackage.common.news[pkg.version]) {
const news = iopackage.common.news;
let newNews = {};
newNews[pkg.version] = {
en: 'news',
de: 'neues',
ru: 'новое'
};
iopackage.common.news = Object.assign(newNews, news);
}
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
done();
});
gulp.task('updateReadme', done => {
const readme = fs.readFileSync('README.md').toString();
const pos = readme.indexOf('## Changelog\n');
if (pos !== -1) {
let readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
let readmeEnd = readme.substring(pos + '## Changelog\n'.length);
if (readme.indexOf(version) === -1) {
const timestamp = new Date();
const date = timestamp.getFullYear() + '-' +
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
('0' + (timestamp.getDate()).toString(10)).slice(-2);
let news = '';
if (iopackage.common.news && iopackage.common.news[pkg.version]) {
news += '* ' + iopackage.common.news[pkg.version].en;
}
fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
}
}
done();
});
gulp.task('default', gulp.series('updatePackages', 'updateReadme'));
+186 -29
View File
@@ -1,7 +1,7 @@
{ {
"common": { "common": {
"name": "hass", "name": "hass",
"version": "1.0.1", "version": "1.4.0",
"title": "Home Assistant", "title": "Home Assistant",
"titleLang": { "titleLang": {
"en": "Home Assistant", "en": "Home Assistant",
@@ -13,7 +13,8 @@
"it": "Assistente domiciliare", "it": "Assistente domiciliare",
"es": "Asistente de hogar", "es": "Asistente de hogar",
"pl": "Asystent domowy", "pl": "Asystent domowy",
"zh-cn": "家庭助理" "zh-cn": "家庭助理",
"uk": "Домашній помічник"
}, },
"desc": { "desc": {
"en": "Home Assistant connection for ioBroker", "en": "Home Assistant connection for ioBroker",
@@ -24,9 +25,89 @@
"fr": "Connexion Home Assistant pour ioBroker", "fr": "Connexion Home Assistant pour ioBroker",
"it": "Connessione Home Assistant per ioBroker", "it": "Connessione Home Assistant per ioBroker",
"es": "Conexión de Home Assistant para ioBroker", "es": "Conexión de Home Assistant para ioBroker",
"pl": "Połączenie Home Assistant dla ioBroker" "pl": "Połączenie Home Assistant dla ioBroker",
"zh-cn": "ioBroker 的家庭助理连接",
"uk": "Підключення Home Assistant для ioBroker"
}, },
"news": { "news": {
"1.4.0": {
"en": "Added more guidance logging when setting services incorrectly\nPrevent crashes when attributes contain \".\" at the end of their names\nAdded logging for state updates for unknown objects",
"de": "Mehr Anleitungsprotokollierung hinzugefügt, wenn Dienste falsch eingestellt werden\nVerhindern Sie Abstürzen, wenn Attribute \" enthalten.\" am Ende ihrer Namen\nHinzugefügt Protokollierung für Zustandsaktualisierungen für unbekannte Objekte",
"ru": "Добавили больше руководства, когда установка услуг неправильно\nПредотвратить сбои, когда атрибуты содержат \".\" в конце их имен\nДобавлен вход для государственных обновлений для неизвестных объектов",
"pt": "Adicionado mais registro de orientação ao definir serviços incorretamente\nImpedir falhas quando os atributos contêm \". no final de seus nomes\nRegisto adicionado para atualizações de estado para objetos desconhecidos",
"nl": "Voeg meer begeleiding toe als ze onjuiste diensten indienen\nVertaling:\nVermeld inloggen op staatsinformatie voor onbekende objecten",
"fr": "Ajout d'autres lignes directrices lorsqu'on établit des services incorrectement\nPrévenir les crashes lorsque les attributs contiennent \".\" à la fin de leurs noms\nAjout de logging pour les mises à jour de l'état des objets inconnus",
"it": "Aggiunto più guida logging quando imposta i servizi in modo errato\nPrevenire crash quando gli attributi contengono \". alla fine dei loro nomi\nAggiunto logging per aggiornamenti di stato per oggetti sconosciuti",
"es": "Se agregó más registro de guía cuando se establecen los servicios incorrectamente\nEvitar fallos cuando los atributos contienen \".\" al final de sus nombres\nRegistro añadido para actualizaciones estatales para objetos desconocidos",
"pl": "Wprowadzone są bardziej wytyczne, gdy ustawienie usług nieprawidłowo błędnie\nPrevent rozbija się, gdy atrybuty zawierają „”\nWprowadzone logowanie dla aktualizacji stanów nieznanych obiektów",
"uk": "Додайте більше звітів при установці послуг неправильно\nПрофілактика аварійних випадків, коли атрибути містять \".\" в кінці їх імен\nДодана записка для державних оновлень для невідомих об'єктів",
"zh-cn": "在不正确地建立服务时增加指导标志\n在名称末尾含有“第2款”的预防事故\n增加用于不明物品的国家更新的伐木"
},
"1.3.0": {
"en": "Further optimize sending data to HASS and allow to set values like numbers as normal states if the service has one attribute and it can be mapped",
"de": "Weitere optimieren Sie das Senden von Daten an HASS und ermöglichen es, Werte wie Zahlen als normale Zustände festzulegen, wenn der Dienst ein Attribut hat und es abgebildet werden kann",
"ru": "Далее оптимизируйте отправку данных в HASS и позвольте установить значения, такие как цифры, как нормальные состояния, если услуга имеет один атрибут, и она может быть загружена",
"pt": "Além disso otimizar o envio de dados para HASS e permitir definir valores como números como estados normais se o serviço tem um atributo e pode ser mapeado",
"nl": "Verder optimiliseer het sturen van gegevens naar HASS en sta toe om waarden als normale staten te plaatsen als de dienst een Attribuut heeft en het kan worden in kaart gebracht",
"fr": "Optimiser l'envoi de données à HASS et permettre de définir des valeurs comme des nombres comme des états normaux si le service a un attribut et il peut être mappé",
"it": "Ottimizzare ulteriormente l'invio di dati a HASS e consentire di impostare valori come numeri come stati normali se il servizio ha un attributo e può essere mappato",
"es": "Optimizar aún más el envío de datos a HASS y permitir establecer valores como números como estados normales si el servicio tiene un atributo y puede ser mapeado",
"pl": "Dalsze optymalizowanie danych do HASS i umożliwienie ustalenie wartości takich jak stany normalne, jeśli usługa ma jeden atrybut i może być zmapowana",
"zh-cn": "进一步优化向人道协调厅系统传送数据,如果该服务具有属性,它可以绘制图表,从而能够将数字象正常国家一样。",
"uk": "Додатково оптимізуйте надсилання даних до HASS і дозвольте встановлювати такі значення, як числа, як звичайні стани, якщо служба має один атрибут і його можна зіставити"
},
"1.2.0": {
"en": "IMPORTANT: Replace special characters in entity attribute names with an underscore! Object IDs might change!\nmake sure a \"null\" value in state changes is not crashing",
"de": "WICHTIG: Ersetzen Sie Sonderzeichen in Namen des Attributes der Einheit mit einem Unterpunkt! Objekt-IDs können sich ändern!\nsicherstellen, dass ein nullwert bei zustandsänderungen nicht abstürzt",
"ru": "ВАЖНО: Замените специальные символы в имени сущности с подчеркиванием! ID объекта может измениться!\nубедитесь, что значение null в государственных изменениях не падает",
"pt": "IMPORTANTE: Substitua caracteres especiais em nomes de atributos de entidade com um sublinhado! IDs de objeto podem mudar!\ncertifique-se de que um valor nulo em mudanças de estado não está caindo",
"nl": "Vervang speciale personages in entiteitsadvocaten met een onderscore! ID's kunnen veranderen!\nzorg ervoor dat een waarde van de staat niet crasht",
"fr": "IMPORTANT: Remplacer les caractères spéciaux dans les noms d'attributs d'entité par un accent! Les identifiants d'objet peuvent changer !\ns'assurer qu'une valeur nulle dans les changements d'état ne s'écrase pas",
"it": "IMPORTANTE: Sostituisci caratteri speciali in nomi attributo entità con un underscore! Gli ID degli Oggetti potrebbero cambiare!\nassicurarsi che un valore nullo nei cambiamenti di stato non è crash",
"es": "IMPORTANTE: Reemplazar caracteres especiales en nombres de atributos de entidad con un subrayado! ¡Las identificaciones de objetos pueden cambiar!\nasegúrese de que un valor nulo en los cambios estatales no se estrella",
"pl": "IMPORTANT: Replace special characters in identity attribution names with an underscore! (ang.). Zmiany obiektów mogą się zmieniać!\nniewątpliwą wartością w zmianach stanowych nie jest rozbicie się",
"zh-cn": "IMPORTANT:将实体的特色性改为强调的! 目标国际组织可能改变!\n确保在国家变化中确保无效不失",
"uk": "ВАЖЛИВО: Замініть спеціальні символи в іменах атрибутів сутності на підкреслення! Ідентифікатори об'єктів можуть змінитися!\nпереконайтеся, що \"нульове\" значення в змінах стану не аварійно завершує роботу"
},
"1.1.2": {
"en": "Fix crash cases reported by Sentry",
"de": "Von Sentry gemeldete Absturzfälle beheben",
"ru": "Исправить случаи сбоев, о которых сообщил Sentry",
"pt": "Corrigir casos de travamento relatados pelo Sentinela",
"nl": "Herstel crashgevallen gemeld door Sentry",
"fr": "Correction des cas de plantage signalés par Sentry",
"it": "Risolvi i casi di crash segnalati da Sentry",
"es": "Solucionar casos de fallas informados por Sentry",
"pl": "Napraw przypadki awarii zgłoszone przez Sentry",
"zh-cn": "修复 Sentry 报告的崩溃案例",
"uk": "Виправте випадки збоїв, про які повідомляє Sentry"
},
"1.1.1": {
"en": "Show password fields masked again in config",
"de": "Passwortfelder in config wieder maskiert anzeigen",
"ru": "Показать поля пароля снова замаскированные в конфигурации",
"pt": "Mostrar campos de senha mascarados novamente na configuração",
"nl": "Toon wachtwoordvelden opnieuw gemaskeerd in config",
"fr": "Afficher à nouveau les champs de mot de passe masqués dans la configuration",
"it": "Mostra i campi password mascherati di nuovo in config",
"es": "Mostrar campos de contraseña enmascarados nuevamente en la configuración",
"pl": "Pokaż ponownie zamaskowane pola haseł w konfiguracji",
"zh-cn": "在配置中再次显示被屏蔽的密码字段",
"uk": "Знову показати замасковані поля пароля в конфігурації"
},
"1.1.0": {
"en": "IMPORTANT: You need to re-enter the password once after installing this version!\nImplement Service triggers to use any value to trigger or stringified JSON to call with fields\nOptimize unload handling\nAdd Sentry for crash reporting",
"de": "WICHTIG: Nach der Installation dieser Version müssen Sie das Passwort einmalig neu eingeben!\nImplementieren Sie Dienstauslöser, um einen beliebigen Wert zum Auslösen oder stringifiziertes JSON zum Aufrufen mit Feldern zu verwenden\nEntladehandling optimieren\nFügen Sie Sentry für Absturzberichte hinzu",
"ru": "ВАЖНО: Вам необходимо повторно ввести пароль один раз после установки этой версии!\nРеализовать триггеры службы, чтобы использовать любое значение для запуска или строкового JSON для вызова с полями.\nОптимизация обработки разгрузки\nДобавьте Sentry для отчетов о сбоях",
"pt": "IMPORTANTE: Você precisa redigitar a senha uma vez depois de instalar esta versão!\nImplemente gatilhos de serviço para usar qualquer valor para acionar ou JSON stringified para chamar com campos\nOtimize o manuseio de descarga\nAdicionar Sentinela para relatórios de falhas",
"nl": "BELANGRIJK: u moet het wachtwoord één keer opnieuw invoeren na het installeren van deze versie!\nImplementeer servicetriggers om elke waarde te gebruiken om te activeren of stringified JSON om met velden aan te roepen\nOptimaliseer de losverwerking\nSentry toevoegen voor crashrapportage",
"fr": "IMPORTANT : Vous devez ressaisir le mot de passe une fois après l'installation de cette version !\nImplémentez des déclencheurs de service pour utiliser n'importe quelle valeur pour déclencher ou JSON stringifié pour appeler avec des champs\nOptimiser la gestion des déchargements\nAjouter Sentry pour les rapports de plantage",
"it": "IMPORTANTE: è necessario reinserire la password una volta dopo aver installato questa versione!\nImplementa i trigger del servizio per utilizzare qualsiasi valore per attivare o JSON in formato stringa per chiamare con i campi\nOttimizza la gestione dello scarico\nAggiungi Sentinella per la segnalazione degli arresti anomali",
"es": "IMPORTANTE: ¡Debe volver a ingresar la contraseña una vez después de instalar esta versión!\nImplementar disparadores de servicio para usar cualquier valor para disparar o JSON en cadena para llamar con campos\nOptimizar el manejo de descarga\nAgregar Sentry para informes de fallas",
"pl": "WAŻNE: Po zainstalowaniu tej wersji należy raz ponownie wprowadzić hasło!\nZaimplementuj wyzwalacze usługi, aby używać dowolnej wartości do wyzwalania lub skróconego JSON do wywołania z polami\nZoptymalizuj obsługę rozładunku\nDodaj Sentry do zgłaszania awarii",
"zh-cn": "重要提示:安装此版本后需要重新输入一次密码!\n实现服务触发器以使用任何值触发或字符串化 JSON 以使用字段调用\n优化卸载处理\n添加 Sentry 以进行崩溃报告",
"uk": "ВАЖЛИВО: після встановлення цієї версії потрібно повторно ввести пароль!\nРеалізуйте тригери служби, щоб використовувати будь-яке значення для запуску або рядковий JSON для виклику з полями\nОптимізуйте роботу з розвантаженням\nДодайте Sentry для звітування про збої"
},
"1.0.1": { "1.0.1": {
"en": "IMPORTANT: js-controller 2.0 is needed st least!\nFix start issue\n(Apollon77/Garfonso) Fix issue where value could not be set in hass", "en": "IMPORTANT: js-controller 2.0 is needed st least!\nFix start issue\n(Apollon77/Garfonso) Fix issue where value could not be set in hass",
"de": "WICHTIG: js-controller 2.0 wird am wenigsten benötigt!\nStartproblem beheben\n(Apollon77/Garfonso) Problem behoben, bei dem der Wert nicht in Hass festgelegt werden konnte", "de": "WICHTIG: js-controller 2.0 wird am wenigsten benötigt!\nStartproblem beheben\n(Apollon77/Garfonso) Problem behoben, bei dem der Wert nicht in Hass festgelegt werden konnte",
@@ -37,30 +118,8 @@
"it": "IMPORTANTE: come minimo è necessario js-controller 2.0!\nRisolvi il problema di avvio\n(Apollon77/Garfonso) Risolto il problema per cui il valore non poteva essere impostato in hass", "it": "IMPORTANTE: come minimo è necessario js-controller 2.0!\nRisolvi il problema di avvio\n(Apollon77/Garfonso) Risolto il problema per cui il valore non poteva essere impostato in hass",
"es": "IMPORTANTE: ¡js-controller 2.0 es lo menos necesario!\nSolucionar problema de inicio\n(Apollon77 / Garfonso) Se solucionó el problema por el cual no se podía establecer el valor en hass", "es": "IMPORTANTE: ¡js-controller 2.0 es lo menos necesario!\nSolucionar problema de inicio\n(Apollon77 / Garfonso) Se solucionó el problema por el cual no se podía establecer el valor en hass",
"pl": "WAŻNE: js-controller 2.0 jest potrzebny przynajmniej!\nNapraw problem z uruchomieniem\n(Apollon77/Garfonso) Napraw problem, w którym nie można było ustawić wartości w hass", "pl": "WAŻNE: js-controller 2.0 jest potrzebny przynajmniej!\nNapraw problem z uruchomieniem\n(Apollon77/Garfonso) Napraw problem, w którym nie można było ustawić wartości w hass",
"zh-cn": "重要提示:至少需要 js-controller 2.0\n修复启动问题\n(Apollon77/Garfonso) 修复无法在 hass 中设置值的问题" "zh-cn": "重要提示:至少需要 js-controller 2.0\n修复启动问题\n(Apollon77/Garfonso) 修复无法在 hass 中设置值的问题",
}, "uk": "ВАЖЛИВО: як мінімум потрібен js-контроллер 2.0!\nВиправити проблему запуску\n(Apollon77/Garfonso) Виправлено проблему, через яку значення не можна було встановити в hass"
"1.0.0": {
"en": "added the support of compact mode",
"de": "fügte die Unterstützung des Kompaktmodus hinzu",
"ru": "добавлена поддержка компактного режима",
"pt": "adicionado o suporte do modo compacto",
"nl": "de ondersteuning van de compacte modus toegevoegd",
"fr": "ajouté le support du mode compact",
"it": "aggiunto il supporto della modalità compatta",
"es": "agregó el soporte del modo compacto",
"pl": "dodano obsługę trybu kompaktowego",
"zh-cn": "增加了对紧凑模式的支持"
},
"0.1.0": {
"en": "initial version",
"de": "erste Version",
"ru": "Первоначальная версия",
"pt": "versão inicial",
"nl": "eerste versie",
"fr": "version initiale",
"it": "versione iniziale",
"es": "versión inicial",
"pl": "początkowa wersja"
} }
}, },
"localLink": "http://%host%:8123", "localLink": "http://%host%:8123",
@@ -85,12 +144,104 @@
], ],
"dependencies": [ "dependencies": [
{ {
"js-controller": ">=2.0.0" "js-controller": ">=3.0.0"
} }
], ],
"globalDependencies": [
{
"admin": ">=5.1.28"
}
],
"plugins": {
"sentry": {
"dsn": "https://1b2a1e8335f84cc49a67dd5ac2b07573@sentry.iobroker.net/182"
}
},
"connectionType": "local", "connectionType": "local",
"dataSource": "push", "dataSource": "push",
"tier": 2 "tier": 2,
"messages": [
{
"condition": {
"operand": "and",
"rules": [
"oldVersion<1.1.0",
"newVersion>=1.1.0"
]
},
"title": {
"en": "Re-enter the password after Update!",
"de": "Passwort muss nach dem Update erneut eingegeben werden!",
"ru": "Повторно введите пароль после обновления!",
"pt": "Digite novamente a senha após Atualizar!",
"nl": "Voer het wachtwoord opnieuw in na Update!",
"fr": "Entrez à nouveau le mot de passe après la mise à jour !",
"it": "Reinserire la password dopo l'aggiornamento!",
"es": "Vuelva a ingresar la contraseña después de ¡Actualizar!",
"pl": "Wprowadź hasło ponownie po aktualizacji!",
"zh-cn": "更新后重新输入密码!",
"uk": "Повторно введіть пароль після оновлення!"
},
"text": {
"en": "After installing this update the password/token used to access the HASS installation needs to be re-entered in the Instance-Settings!",
"de": "Nach der Installation dieses Updates muss das Passwort/Token für den Zugriff auf die HASS-Installation in den Instanz-Einstellungen erneut eingegeben werden!",
"ru": "После установки этого обновления пароль/токен, используемый для доступа к установке HASS, необходимо повторно ввести в настройках экземпляра!",
"pt": "Depois de instalar esta atualização, a senha/token usado para acessar a instalação do HASS precisa ser reinserido nas configurações da instância!",
"nl": "Na het installeren van deze update moet het wachtwoord/token dat wordt gebruikt om toegang te krijgen tot de HASS-installatie opnieuw worden ingevoerd in de Instance-Settings!",
"fr": "Après l'installation de cette mise à jour, le mot de passe/jeton utilisé pour accéder à l'installation HASS doit être ressaisi dans les paramètres d'instance !",
"it": "Dopo aver installato questo aggiornamento, la password/token utilizzato per accedere all'installazione HASS deve essere reinserito nelle impostazioni dell'istanza!",
"es": "¡Después de instalar esta actualización, la contraseña/token utilizada para acceder a la instalación de HASS debe volver a ingresarse en la configuración de la instancia!",
"pl": "Po zainstalowaniu tej aktualizacji hasło/token używane do uzyskania dostępu do instalacji HASS należy ponownie wprowadzić w ustawieniach instancji!",
"zh-cn": "安装此更新后,用于访问 HASS 安装的密码/令牌需要在实例设置中重新输入!",
"uk": "Після встановлення цього оновлення пароль/токен, який використовується для доступу до встановлення HASS, потрібно повторно ввести в налаштуваннях екземпляра!"
},
"level": "warn",
"buttons": [
"agree",
"cancel"
]
},
{
"condition": {
"operand": "and",
"rules": [
"oldVersion<1.2.0",
"newVersion>=1.2.0"
]
},
"title": {
"en": "Important notice!",
"de": "Wichtiger Hinweis!",
"ru": "Важное замечание!",
"pt": "Notícia importante!",
"nl": "Belangrijke mededeling!",
"fr": "Avis important!",
"it": "Avviso IMPORTANTE!",
"es": "Noticia importante!",
"pl": "Ważna uwaga!",
"zh-cn": "重要通知!",
"uk": "Важливе повідомлення!"
},
"text": {
"en": "The new version 1.2.x will potentially change the Names of Objects when special characters like \".\", \"-\" or spaces were used in entity attributes! Please delete the old objects manually if needed.",
"de": "Die neue Version 1.2.x wird die Namen von Objekten möglicherweise ändern, wenn spezielle Zeichen wie \".\", \"-\" oder Leerzeichen in Entity-Attributen verwendet wurden! Alten Objekte bitte bei Bedarf manuell löschen.",
"ru": "Новая версия 1.2.x будет потенциально менять имена объектов, когда специальные символы типа \".\", \"-\" или пространства были использованы в атрибутах лица! Пожалуйста, удалите старые объекты вручную, если это необходимо.",
"pt": "A nova versão 1.2.x irá potencialmente alterar os Nomes de Objetos quando caracteres especiais como \".\", \"-\" ou espaços foram usados em atributos de entidade! Por favor, exclua os objetos antigos manualmente, se necessário.",
"nl": "De nieuwe versie 1.2.x zal de Namen van Objects veranderen als speciale personages zoals \", \" of ruimtes werden gebruikt in entiteitsaanklachten! Verwijder de oude objecten handmatig als het nodig is.",
"fr": "La nouvelle version 1.2.x modifiera potentiellement les Noms des Objets lorsque des caractères spéciaux comme \", \"-\" ou des espaces ont été utilisés dans des attributs d'entité ! Veuillez supprimer les anciens objets manuellement si nécessaire.",
"it": "La nuova versione 1.2.x cambierà potenzialmente i nomi degli oggetti quando caratteri speciali come \".\", \"-\" o spazi sono stati utilizzati negli attributi dell'entità! Si prega di eliminare i vecchi oggetti manualmente se necessario.",
"es": "La nueva versión 1.2.x potencialmente cambiará los Nombres de Objetos cuando caracteres especiales como \"., \"-\" o espacios fueron utilizados en atributos de entidad! Por favor, borre los viejos objetos manualmente si es necesario.",
"pl": "Nowa wersja 1.2.x będzie potencjalnie zmieniać nazwy obiektów, kiedy szczególne postacie takie jak „.”, „-” lub przestrzenie były używane w atrybutach. Okazuje się, że stare obiekty ręcznie będą potrzebne.",
"zh-cn": "新版本1.2.x将可能改变物体的名称,如“......”、“-”或空间在实体的属性中使用。 请在必要时删除旧物体。.",
"uk": "Нова версія 1.2.x потенційно змінить назви об’єктів, якщо в атрибутах об’єктів використовувалися спеціальні символи, такі як «.», «-» або пробіли! За потреби видаліть старі об’єкти вручну."
},
"level": "warn",
"buttons": [
"agree",
"cancel"
]
}
]
}, },
"native": { "native": {
"host": "127.0.0.1", "host": "127.0.0.1",
@@ -98,6 +249,12 @@
"password": "", "password": "",
"secure": false "secure": false
}, },
"protectedNative": [
"password"
],
"encryptedNative": [
"password"
],
"objects": [], "objects": [],
"instanceObjects": [ "instanceObjects": [
{ {
+5 -4
View File
@@ -92,7 +92,7 @@ function HASS(options, log) {
} }
} }
function callService(socket, service, domain, serviceData, callback) { function callService(socket, service, domain, serviceData, target, callback) {
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()};
@@ -101,7 +101,8 @@ function HASS(options, log) {
type: 'call_service', type: 'call_service',
domain: domain || '', domain: domain || '',
service: service, service: service,
service_data: serviceData service_data: serviceData,
target
})); }));
} else { } else {
callback && callback('not connected'); callback && callback('not connected');
@@ -228,11 +229,11 @@ function HASS(options, log) {
} }
}; };
this.callService = function (service, domain, serviceData, callback) { this.callService = function (service, domain, serviceData, target, callback) {
if (!connected) { if (!connected) {
typeof callback === 'function' && callback('not connected'); typeof callback === 'function' && callback('not connected');
} else { } else {
callService(this.socket, service, domain, serviceData, callback); callService(this.socket, service, domain, serviceData, target, callback);
} }
}; };
+89 -29
View File
@@ -12,6 +12,8 @@ let connected = false;
let hass; let hass;
let adapter; let adapter;
const hassObjects = {}; const hassObjects = {};
let delayTimeout = null;
let stopped = false;
function startAdapter(options) { function startAdapter(options) {
options = options || {}; options = options || {};
@@ -23,7 +25,7 @@ function startAdapter(options) {
// you can use the ack flag to detect if it is status (true) or command (false) // you can use the ack flag to detect if it is status (true) or command (false)
if (state && !state.ack) { if (state && !state.ack) {
if (!connected) { if (!connected) {
return adapter.log.warn('Cannot send command to "' + id + '", because not connected'); return adapter.log.warn(`Cannot send command to "${id}", because not connected`);
} }
/*if (id === adapter.namespace + '.' + '.info.resync') { /*if (id === adapter.namespace + '.' + '.info.resync') {
queue.push({command: 'resync'}); queue.push({command: 'resync'});
@@ -31,27 +33,60 @@ function startAdapter(options) {
} else */ } else */
if (hassObjects[id]) { if (hassObjects[id]) {
if (!hassObjects[id].common.write) { if (!hassObjects[id].common.write) {
adapter.log.warn('Object ' + id + ' is not writable!'); adapter.log.warn(`Object ${id} is not writable!`);
} else { } else {
const serviceData = {}; const serviceData = {};
const fields = hassObjects[id].native.fields; const fields = hassObjects[id].native.fields;
const target = {};
let requestFields = {};
if (typeof state.val === 'string') {
state.val = state.val.trim();
if (state.val.startsWith('{') && state.val.endsWith('}')) {
try {
requestFields = JSON.parse(state.val) || {};
} catch (err) {
adapter.log.info(`Ignore data for service call ${id} is no valid JSON: ${err.message}`);
requestFields = {};
}
}
}
// If a non-JSON value was set, and we only have one relevant field, use this field as value
if (fields && Object.keys(requestFields).length === 0) {
const fieldList = Object.keys(fields);
if (fieldList.length === 1 && fieldList[0] !== 'entity_id') {
requestFields[fieldList[0]] = state.val;
} else if (fieldList.length === 2 && fields.entity_id) {
requestFields[fieldList[1 - fields.indexOf('entity_id')]] = 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) { for (const field in fields) {
if (!fields.hasOwnProperty(field)) { if (!fields.hasOwnProperty(field)) {
continue; continue;
} }
if (field === 'entity_id') { if (field === 'entity_id') {
serviceData.entity_id = hassObjects[id].native.entity_id target.entity_id = hassObjects[id].native.entity_id
} else { } else if (requestFields[field] !== undefined) {
serviceData[field] = state.val; 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)}`) 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, err => 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) {
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(', ')}`);
}
});
} }
} }
} }
@@ -65,6 +100,8 @@ function startAdapter(options) {
} }
function stop(callback) { function stop(callback) {
stopped = true;
delayTimeout && clearTimeout(delayTimeout);
hass && hass.close(); hass && hass.close();
callback && callback(); callback && callback();
} }
@@ -111,7 +148,7 @@ function syncObjects(objects, cb) {
err && adapter.log.error(err); 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); err && adapter.log.error(err);
@@ -122,7 +159,7 @@ function syncObjects(objects, cb) {
if (JSON.stringify(obj.native) !== JSON.stringify(oldObj.native)) { if (JSON.stringify(obj.native) !== JSON.stringify(oldObj.native)) {
oldObj.native = obj.native; oldObj.native = obj.native;
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); err => adapter.log.error(err);
setImmediate(syncObjects, objects, cb); setImmediate(syncObjects, objects, cb);
@@ -135,10 +172,10 @@ function syncObjects(objects, cb) {
} }
function syncRoom(room, members, cb) { function syncRoom(room, members, cb) {
adapter.getForeignObject('enum.rooms.' + room, (err, obj) => { adapter.getForeignObject(`enum.rooms.${room}`, (err, obj) => {
if (!obj) { if (!obj) {
obj = { obj = {
_id: 'enum.rooms.' + room, _id: `enum.rooms.${room}`,
type: 'enum', type: 'enum',
common: { common: {
name: room, name: room,
@@ -146,7 +183,7 @@ function syncRoom(room, members, cb) {
}, },
native: {} native: {}
}; };
adapter.log.debug('Update "' + obj._id + '"'); adapter.log.debug(`Update "${obj._id}"`);
adapter.setForeignObject(obj._id, obj, err => { adapter.setForeignObject(obj._id, obj, err => {
err && adapter.log.error(err); err && adapter.log.error(err);
cb(); cb();
@@ -162,7 +199,7 @@ function syncRoom(room, members, cb) {
} }
} }
if (changed) { if (changed) {
adapter.log.debug('Update "' + obj._id + '"'); adapter.log.debug(`Update "${obj._id}"`);
adapter.setForeignObject(obj._id, obj, err => { adapter.setForeignObject(obj._id, obj, err => {
err && adapter.log.error(err); err && adapter.log.error(err);
cb(); cb();
@@ -268,8 +305,9 @@ function parseStates(entities, services, callback) {
common = {}; common = {};
} }
const attrId = attr.replace(adapter.FORBIDDEN_CHARS, '_').replace(/\.+$/, '_');
obj = { obj = {
_id: `${adapter.namespace}.entities.${entity.entity_id}.${attr}`, _id: `${adapter.namespace}.entities.${entity.entity_id}.${attrId}`,
type: 'state', type: 'state',
common: common, common: common,
native: { native: {
@@ -280,7 +318,7 @@ function parseStates(entities, services, callback) {
} }
}; };
if (!common.name) { if (!common.name) {
common.name = name + ' ' + attr.replace(/_/g, ' '); common.name = `${name} ${attr.replace(/_/g, ' ')}`;
} }
if (common.read === undefined) { if (common.read === undefined) {
common.read = true; common.read = true;
@@ -318,7 +356,8 @@ function parseStates(entities, services, callback) {
common: { common: {
desc: service[s].description, desc: service[s].description,
read: false, read: false,
write: true write: true,
type: 'mixed'
}, },
native: { native: {
object_id: entity.object_id, object_id: entity.object_id,
@@ -355,22 +394,35 @@ function main() {
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)}`);
const id = adapter.namespace + '.entities.' + entity.entity_id + '.'; if (!entity || typeof entity.entity_id !== 'string') {
return;
}
const id = `entities.${entity.entity_id}.`;
const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined; const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined;
const ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined; const ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined;
if (entity.state !== undefined) { if (entity.state !== undefined) {
adapter.setForeignState(id + 'state', {val: entity.state, ack: true, lc: lc, ts: ts}); if (hassObjects[`${adapter.namespace}.${id}state`]) {
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.`);
}
} }
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') { if (!entity.attributes.hasOwnProperty(attr) || attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon'|| !attr.length) {
continue; continue;
} }
let val = entity.attributes[attr]; let val = entity.attributes[attr];
if ((typeof val === 'object' && val !== null) || Array.isArray(val)) { if ((typeof val === 'object' && val !== null) || Array.isArray(val)) {
val = JSON.stringify(val); val = JSON.stringify(val);
} }
adapter.setForeignState(id + attr, {val, ack: true, lc, ts}); const attrId = attr.replace(adapter.FORBIDDEN_CHARS, '_').replace(/\.+$/, '_');
if (hassObjects[`${adapter.namespace}.${id}state`]) {
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.`);
}
} }
} }
}); });
@@ -382,20 +434,28 @@ function main() {
adapter.setState('info.connection', true, true); adapter.setState('info.connection', true, true);
hass.getConfig((err, config) => { hass.getConfig((err, config) => {
if (err) { if (err) {
adapter.log.error('Cannot read config: ' + err); adapter.log.error(`Cannot read config: ${err}`);
return; return;
} }
//adapter.log.debug(JSON.stringify(config)); //adapter.log.debug(JSON.stringify(config));
setTimeout(() => delayTimeout = setTimeout(() => {
hass.getStates((err, states) => { delayTimeout = null;
!stopped && hass.getStates((err, states) => {
if (stopped) {
return;
}
if (err) { if (err) {
return adapter.log.error('Cannot read states: ' + err); return adapter.log.error(`Cannot read states: ${err}`);
} }
//adapter.log.debug(JSON.stringify(states)); //adapter.log.debug(JSON.stringify(states));
setTimeout(() => delayTimeout = setTimeout(() => {
hass.getServices((err, services) => { delayTimeout = null;
!stopped && hass.getServices((err, services) => {
if (stopped) {
return;
}
if (err) { if (err) {
adapter.log.error('Cannot read states: ' + err); adapter.log.error(`Cannot read states: ${err}`);
} else { } else {
//adapter.log.debug(JSON.stringify(services)); //adapter.log.debug(JSON.stringify(services));
parseStates(states, services, () => { parseStates(states, services, () => {
@@ -403,8 +463,8 @@ function main() {
adapter.subscribeStates('*'); adapter.subscribeStates('*');
}); });
} }
}), 100); })}, 100);
}), 100); })}, 100);
}); });
} }
}); });
+6381
View File
File diff suppressed because it is too large Load Diff
+14 -8
View File
@@ -1,6 +1,6 @@
{ {
"name": "iobroker.hass", "name": "iobroker.hass",
"version": "1.0.1", "version": "1.4.0",
"description": "Home Assistant", "description": "Home Assistant",
"author": { "author": {
"name": "bluefox", "name": "bluefox",
@@ -26,19 +26,25 @@
}, },
"dependencies": { "dependencies": {
"websocket": "^1.0.34", "websocket": "^1.0.34",
"ws": "^8.2.1", "ws": "^8.11.0",
"@iobroker/adapter-core": "^2.5.1" "@iobroker/adapter-core": "^2.6.7"
}, },
"devDependencies": { "devDependencies": {
"@alcalzone/release-script": "^2.2.1", "@alcalzone/release-script": "^3.5.9",
"gulp": "^4.0.2", "@alcalzone/release-script-plugin-iobroker": "^3.5.9",
"mocha": "^9.1.1", "@alcalzone/release-script-plugin-license": "^3.5.9",
"chai": "^4.3.4" "@iobroker/adapter-dev": "^1.2.0",
"mocha": "^10.2.0",
"chai": "^4.3.7"
}, },
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"test": "node node_modules/mocha/bin/mocha --exit", "test": "node node_modules/mocha/bin/mocha --exit",
"release": "node node_modules/@alcalzone/release-script/bin/release.js" "release": "release-script",
"release-patch": "release-script patch --yes",
"release-minor": "release-script minor --yes",
"release-major": "release-script major --yes",
"translate": "translate-adapter"
}, },
"bugs": { "bugs": {
"url": "https://github.com/ioBroker/ioBroker.hass/issues" "url": "https://github.com/ioBroker/ioBroker.hass/issues"
+331 -92
View File
@@ -1,33 +1,52 @@
/* jshint -W097 */// jshint strict:false /* jshint -W097 */// jshint strict:false
/*jslint node: true */ /*jslint node: true */
// check if tmp directory exists // check if tmp directory exists
var fs = require('fs'); const fs = require('fs');
var path = require('path'); const path = require('path');
var child_process = require('child_process'); const child_process = require('child_process');
var rootDir = path.normalize(__dirname + '/../../'); const rootDir = path.normalize(__dirname + '/../../');
var pkg = require(rootDir + 'package.json'); const pkg = require(rootDir + 'package.json');
var debug = typeof v8debug === 'object'; const debug = typeof v8debug === 'object';
pkg.main = pkg.main || 'main.js'; pkg.main = pkg.main || 'main.js';
var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); let JSONLDB;
let adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
adapterName = adapterName[adapterName.length - 2]; adapterName = adapterName[adapterName.length - 2];
var adapterStarted = false; let adapterStarted = false;
function getAppName() { function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/'); const parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 3].split('.')[0]; return parts[parts.length - 3].split('.')[0];
} }
var appName = getAppName().toLowerCase(); 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);
}
}
}
var objects; const appName = getAppName().toLowerCase();
var states;
var pid = null; let objects;
let states;
let pid = null;
let systemConfig = null;
function copyFileSync(source, target) { function copyFileSync(source, target) {
var targetFile = target; let targetFile = target;
//if target is a directory a new file with the same name will be created //if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) { if (fs.existsSync(target)) {
@@ -40,19 +59,19 @@ function copyFileSync(source, target) {
fs.writeFileSync(targetFile, fs.readFileSync(source)); fs.writeFileSync(targetFile, fs.readFileSync(source));
} }
catch (err) { catch (err) {
console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); console.log('file copy error: ' +source +' -> ' + targetFile + ' (error ignored)');
} }
} }
function copyFolderRecursiveSync(source, target, ignore) { function copyFolderRecursiveSync(source, target, ignore) {
var files = []; let files = [];
var base = path.basename(source); let base = path.basename(source);
if (base === adapterName) { if (base === adapterName) {
base = pkg.name; base = pkg.name;
} }
//check if folder needs to be created or integrated //check if folder needs to be created or integrated
var targetFolder = path.join(target, base); const targetFolder = path.join(target, base);
if (!fs.existsSync(targetFolder)) { if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder); fs.mkdirSync(targetFolder);
} }
@@ -65,8 +84,8 @@ function copyFolderRecursiveSync(source, target, ignore) {
return; return;
} }
var curSource = path.join(source, file); const curSource = path.join(source, file);
var curTarget = path.join(targetFolder, file); const curTarget = path.join(targetFolder, file);
if (fs.lstatSync(curSource).isDirectory()) { if (fs.lstatSync(curSource).isDirectory()) {
// ignore grunt files // ignore grunt files
if (file.indexOf('grunt') !== -1) return; if (file.indexOf('grunt') !== -1) return;
@@ -84,12 +103,13 @@ if (!fs.existsSync(rootDir + 'tmp')) {
fs.mkdirSync(rootDir + 'tmp'); fs.mkdirSync(rootDir + 'tmp');
} }
function storeOriginalFiles() { async function storeOriginalFiles() {
console.log('Store original files...'); console.log('Store original files...');
var dataDir = rootDir + 'tmp/' + appName + '-data/'; const dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json'); if (fs.existsSync(dataDir + 'objects.json')) {
var objects = JSON.parse(f.toString()); 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) { if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
objects['system.adapter.admin.0'].common.enabled = false; objects['system.adapter.admin.0'].common.enabled = false;
} }
@@ -98,40 +118,86 @@ function storeOriginalFiles() {
} }
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
try { console.log('Store original objects.json');
f = fs.readFileSync(dataDir + 'states.json');
fs.writeFileSync(dataDir + 'states.json.original', f);
} }
catch (err) {
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'); 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() { function restoreOriginalFiles() {
console.log('restoreOriginalFiles...'); console.log('restoreOriginalFiles...');
var dataDir = rootDir + 'tmp/' + appName + '-data/'; const dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json.original'); if (fs.existsSync(dataDir + 'objects.json.original')) {
const f = fs.readFileSync(dataDir + 'objects.json.original');
fs.writeFileSync(dataDir + 'objects.json', f); fs.writeFileSync(dataDir + 'objects.json', f);
try { }
f = fs.readFileSync(dataDir + 'states.json.original'); if (fs.existsSync(dataDir + 'objects.json.original')) {
const f = fs.readFileSync(dataDir + 'states.json.original');
fs.writeFileSync(dataDir + 'states.json', f); fs.writeFileSync(dataDir + 'states.json', f);
} }
catch (err) {
console.log('no states.json.original found - ignore'); 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) {
function checkIsAdapterInstalled(cb, counter, customName) {
customName = customName || pkg.name.split('.').pop(); customName = customName || pkg.name.split('.').pop();
counter = counter || 0; counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/'; const dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsAdapterInstalled...'); console.log('checkIsAdapterInstalled...');
try { try {
var f = fs.readFileSync(dataDir + 'objects.json'); if (fs.existsSync(dataDir + 'objects.json')) {
var objects = JSON.parse(f.toString()); const f = fs.readFileSync(dataDir + 'objects.json');
const objects = JSON.parse(f.toString());
if (objects['system.adapter.' + customName + '.0']) { if (objects['system.adapter.' + customName + '.0']) {
console.log('checkIsAdapterInstalled: ready!'); console.log('checkIsAdapterInstalled: ready!');
setTimeout(function () { setTimeout(function () {
@@ -141,8 +207,36 @@ function checkIsAdapterInstalled(cb, counter, customName) {
} else { } else {
console.warn('checkIsAdapterInstalled: still not ready'); 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) { } 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) { if (counter > 20) {
@@ -156,14 +250,15 @@ function checkIsAdapterInstalled(cb, counter, customName) {
} }
} }
function checkIsControllerInstalled(cb, counter) { async function checkIsControllerInstalled(cb, counter) {
counter = counter || 0; counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/'; const dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsControllerInstalled...'); console.log('checkIsControllerInstalled...');
try { try {
var f = fs.readFileSync(dataDir + 'objects.json'); if (fs.existsSync(dataDir + 'objects.json')) {
var objects = JSON.parse(f.toString()); const f = fs.readFileSync(dataDir + 'objects.json');
const objects = JSON.parse(f.toString());
if (objects['system.certificates']) { if (objects['system.certificates']) {
console.log('checkIsControllerInstalled: installed!'); console.log('checkIsControllerInstalled: installed!');
setTimeout(function () { setTimeout(function () {
@@ -171,6 +266,32 @@ function checkIsControllerInstalled(cb, counter) {
}, 100); }, 100);
return; 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) { } catch (err) {
} }
@@ -193,7 +314,7 @@ function installAdapter(customName, cb) {
} }
customName = customName || pkg.name.split('.').pop(); customName = customName || pkg.name.split('.').pop();
console.log('Install adapter...'); console.log('Install adapter...');
var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; const startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
// make first install // make first install
if (debug) { if (debug) {
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
@@ -207,7 +328,7 @@ function installAdapter(customName, cb) {
}); });
} else { } else {
// add controller // add controller
var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { const _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
cwd: rootDir + 'tmp', cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc'] stdio: [0, 1, 2, 'ipc']
}); });
@@ -252,7 +373,7 @@ function installJsController(cb) {
// copy all // copy all
// stop controller // stop controller
console.log('Stop controller if running...'); console.log('Stop controller if running...');
var _pid; let _pid;
if (debug) { if (debug) {
// start controller // start controller
_pid = child_process.exec('node ' + appName + '.js stop', { _pid = child_process.exec('node ' + appName + '.js stop', {
@@ -277,7 +398,7 @@ function installJsController(cb) {
} }
console.log('Setup js-controller...'); console.log('Setup js-controller...');
var __pid; let __pid;
if (debug) { if (debug) {
// start controller // start controller
_pid = child_process.exec('node ' + appName + '.js setup first --console', { _pid = child_process.exec('node ' + appName + '.js setup first --console', {
@@ -293,16 +414,20 @@ function installJsController(cb) {
waitForEnd(__pid, function () { waitForEnd(__pid, function () {
checkIsControllerInstalled(function () { checkIsControllerInstalled(function () {
// change ports for object and state DBs // change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001; config.objects.port = 19001;
config.states.port = 19000; 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)); fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
console.log('Setup finished.'); console.log('Setup finished.');
copyAdapterToController(); copyAdapterToController();
installAdapter(function () { installAdapter(async function () {
storeOriginalFiles(); await storeOriginalFiles();
if (cb) cb(true); if (cb) cb(true);
}); });
}); });
@@ -310,7 +435,7 @@ function installJsController(cb) {
}); });
} else { } else {
// check if port 9000 is free, else admin adapter will be added to running instance // check if port 9000 is free, else admin adapter will be added to running instance
var client = new require('net').Socket(); const client = new require('net').Socket();
client.on('error', () => {}); client.on('error', () => {});
client.connect(9000, '127.0.0.1', function() { 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.'); console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
@@ -320,15 +445,15 @@ function installJsController(cb) {
setTimeout(function () { setTimeout(function () {
client.destroy(); client.destroy();
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => install from git'); console.log('installJsController: no js-controller => install dev build from npm');
child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { child_process.execSync('npm install ' + appName + '.js-controller@dev --prefix ./ --production', {
cwd: rootDir + 'tmp/', cwd: rootDir + 'tmp/',
stdio: [0, 1, 2] stdio: [0, 1, 2]
}); });
} else { } else {
console.log('Setup js-controller...'); console.log('Setup js-controller...');
var __pid; let __pid;
if (debug) { if (debug) {
// start controller // start controller
child_process.exec('node ' + appName + '.js setup first', { child_process.exec('node ' + appName + '.js setup first', {
@@ -345,7 +470,7 @@ function installJsController(cb) {
// let npm install admin and run setup // let npm install admin and run setup
checkIsControllerInstalled(function () { checkIsControllerInstalled(function () {
var _pid; let _pid;
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
_pid = child_process.fork(appName + '.js', ['stop'], { _pid = child_process.fork(appName + '.js', ['stop'], {
@@ -356,15 +481,19 @@ function installJsController(cb) {
waitForEnd(_pid, function () { waitForEnd(_pid, function () {
// change ports for object and state DBs // change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001; config.objects.port = 19001;
config.states.port = 19000; 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)); fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
copyAdapterToController(); copyAdapterToController();
installAdapter(function () { installAdapter(async function () {
storeOriginalFiles(); await storeOriginalFiles();
if (cb) cb(true); if (cb) cb(true);
}); });
}); });
@@ -387,8 +516,8 @@ function copyAdapterToController() {
} }
function clearControllerLog() { function clearControllerLog() {
var dirPath = rootDir + 'tmp/log'; const dirPath = rootDir + 'tmp/log';
var files; let files;
try { try {
if (fs.existsSync(dirPath)) { if (fs.existsSync(dirPath)) {
console.log('Clear controller log...'); console.log('Clear controller log...');
@@ -404,8 +533,8 @@ function clearControllerLog() {
} }
if (files.length > 0) { if (files.length > 0) {
try { try {
for (var i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i]; const filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
} }
console.log('Controller log cleared'); console.log('Controller log cleared');
@@ -416,8 +545,8 @@ function clearControllerLog() {
} }
function clearDB() { function clearDB() {
var dirPath = rootDir + 'tmp/iobroker-data/sqlite'; const dirPath = rootDir + 'tmp/iobroker-data/sqlite';
var files; let files;
try { try {
if (fs.existsSync(dirPath)) { if (fs.existsSync(dirPath)) {
console.log('Clear sqlite DB...'); console.log('Clear sqlite DB...');
@@ -433,8 +562,8 @@ function clearDB() {
} }
if (files.length > 0) { if (files.length > 0) {
try { try {
for (var i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i]; const filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
} }
console.log('Clear sqlite DB'); console.log('Clear sqlite DB');
@@ -445,7 +574,8 @@ function clearDB() {
} }
function setupController(cb) { function setupController(cb) {
installJsController(function (isInited) { installJsController(async function (isInited) {
try {
clearControllerLog(); clearControllerLog();
clearDB(); clearDB();
@@ -454,14 +584,14 @@ function setupController(cb) {
copyAdapterToController(); copyAdapterToController();
} }
// read system.config object // read system.config object
var dataDir = rootDir + 'tmp/' + appName + '-data/'; const dataDir = rootDir + 'tmp/' + appName + '-data/';
var objs; if (fs.existsSync(dataDir + 'objects.json')) {
let objs;
try { try {
objs = fs.readFileSync(dataDir + 'objects.json'); objs = fs.readFileSync(dataDir + 'objects.json');
objs = JSON.parse(objs); objs = JSON.parse(objs);
} } catch (e) {
catch (e) {
console.log('ERROR reading/parsing system configuration. Ignore'); console.log('ERROR reading/parsing system configuration. Ignore');
objs = {'system.config': {}}; objs = {'system.config': {}};
} }
@@ -469,10 +599,74 @@ function setupController(cb) {
objs = {'system.config': {}}; objs = {'system.config': {}};
} }
systemConfig = objs['system.config'];
if (cb) cb(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) { function startAdapter(objects, states, callback) {
if (adapterStarted) { if (adapterStarted) {
console.log('Adapter already started ...'); console.log('Adapter already started ...');
@@ -522,20 +716,28 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback
console.error('Controller is already started!'); console.error('Controller is already started!');
} else { } else {
console.log('startController...'); console.log('startController...');
adapterStarted = false; try {
var isObjectConnected; const config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
var isStatesConnected;
var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); 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({ objects = new Objects({
connection: { connection: {
"type" : "file", 'type': config.objects.type,
"host" : "127.0.0.1", 'host': '127.0.0.1',
"port" : 19001, 'port': 19001,
"user" : "", 'user': '',
"pass" : "", 'pass': '',
"noFileCache": false, 'noFileCache': false,
"connectTimeout": 2000 'connectTimeout': 2000
}, },
logger: { logger: {
silly: function (msg) { silly: function (msg) {
@@ -572,10 +774,14 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback
}); });
// Just open in memory DB itself // Just open in memory DB itself
var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); 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({ states = new States({
connection: { connection: {
type: 'file', type: config.states.type,
host: '127.0.0.1', host: '127.0.0.1',
port: 19000, port: 19000,
options: { options: {
@@ -616,6 +822,9 @@ function startController(isStartAdapter, onObjectChange, onStateChange, callback
}, },
change: onStateChange change: onStateChange
}); });
} catch (err) {
console.log(err);
}
} }
} }
@@ -660,7 +869,7 @@ function _stopController() {
} }
function stopController(cb) { function stopController(cb) {
var timeout; let timeout;
if (objects) { if (objects) {
console.log('Set system.adapter.' + pkg.name + '.0'); console.log('Set system.adapter.' + pkg.name + '.0');
objects.setObject('system.adapter.' + pkg.name + '.0', { objects.setObject('system.adapter.' + pkg.name + '.0', {
@@ -699,19 +908,47 @@ function stopController(cb) {
} }
// Setup the adapter // Setup the adapter
function setAdapterConfig(common, native, instance) { async function setAdapterConfig(common, native, instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
var 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 (common) objects[id].common = common;
if (native) objects[id].native = native; if (native) objects[id].native = native;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); 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 // Read config of the adapter
function getAdapterConfig(instance) { async function getAdapterConfig(instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); const id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
var 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]; 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) { if (typeof module !== undefined && module.parent) {
@@ -726,4 +963,6 @@ if (typeof module !== undefined && module.parent) {
module.exports.appName = appName; module.exports.appName = appName;
module.exports.adapterName = adapterName; module.exports.adapterName = adapterName;
module.exports.adapterStarted = adapterStarted; module.exports.adapterStarted = adapterStarted;
module.exports.getSecret = getSecret;
module.exports.encrypt = encrypt;
} }
+1
View File
@@ -0,0 +1 @@
process.on("unhandledRejection", (r) => { throw r; });
+3 -3
View File
@@ -77,15 +77,15 @@ describe('Test ' + adapterShortName + ' adapter', function() {
before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { 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(function () { setup.setupController(async function () {
var config = setup.getAdapterConfig(); var config = await setup.getAdapterConfig();
// enable adapter // enable adapter
config.common.enabled = true; config.common.enabled = true;
config.common.loglevel = 'debug'; config.common.loglevel = 'debug';
//config.native.dbtype = 'sqlite'; //config.native.dbtype = 'sqlite';
setup.setAdapterConfig(config.common, config.native); await setup.setAdapterConfig(config.common, config.native);
setup.startController(true, function(id, obj) {}, function (id, state) { setup.startController(true, function(id, obj) {}, function (id, state) {
if (onStateChanged) onStateChanged(id, state); if (onStateChanged) onStateChanged(id, state);