From d7adbddcc050096df37b15ecb3086fd61db7bb7f Mon Sep 17 00:00:00 2001 From: GermanBluefox Date: Sun, 13 Dec 2020 17:50:27 +0100 Subject: [PATCH] ### 1.0.0 (2020-12-13) * (bluefox) added the support of compact mode --- .github/ISSUE_TEMPLATE/bug_report.md | 32 +++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++ .github/stale.yml | 78 +++++++ .github/workflows/test-and-release.yml | 159 ++++++++++++++ .npmignore | 19 +- LICENSE | 2 +- README.md | 25 ++- admin/index_m.html | 3 +- gulpfile.js | 174 +++++++-------- io-package.json | 130 ++++++----- lib/hass.js | 87 ++++---- lib/utils.js | 64 ------ main.js | 255 +++++++++++----------- package.json | 17 +- 14 files changed, 658 insertions(+), 407 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/stale.yml create mode 100644 .github/workflows/test-and-release.yml delete mode 100644 lib/utils.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..8956139 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Something is not working as it should +title: '' +labels: '' +assignees: '' +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '...' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots & Logfiles** +If applicable, add screenshots and logfiles to help explain your problem. + +**Versions:** + - Adapter version: + - JS-Controller version: + - Node version: + - Operating system: + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..04f9e03 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,78 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 90 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: 7 + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: + - enhancement + - security + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: true + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: true + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: wontfix + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs within the next 7 days. + Please check if the issue is still relevant in the most current version of the adapter + and tell us. Also check that all relevant details, logs and reproduction steps + are included and update them if needed. + Thank you for your contributions. + + Dieses Problem wurde automatisch als veraltet markiert, da es in letzter Zeit keine Aktivitäten gab. + Es wird geschlossen, wenn nicht innerhalb der nächsten 7 Tage weitere Aktivitäten stattfinden. + Bitte überprüft, ob das Problem auch in der aktuellsten Version des Adapters noch relevant ist, + und teilt uns dies mit. Überprüft auch, ob alle relevanten Details, Logs und Reproduktionsschritte + enthalten sind bzw. aktualisiert diese. + Vielen Dank für Eure Unterstützung. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +closeComment: > + This issue has been automatically closed because of inactivity. Please open a new + issue if still relevant and make sure to include all relevant details, logs and + reproduction steps. + Thank you for your contributions. + + Dieses Problem wurde aufgrund von Inaktivität automatisch geschlossen. Bitte öffnet ein + neues Issue, falls dies noch relevant ist und stellt sicher das alle relevanten Details, + Logs und Reproduktionsschritte enthalten sind. + Vielen Dank für Eure Unterstützung. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed \ No newline at end of file diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml new file mode 100644 index 0000000..6cc88e0 --- /dev/null +++ b/.github/workflows/test-and-release.yml @@ -0,0 +1,159 @@ +# This is a composition of lint and test scripts +# Make sure to update this file along with the others + +name: Test and Release + +# Run this job on all pushes and pull requests +# as well as tags with a semantic version +on: + push: + branches: + - '*' + tags: + # normal versions + - "v?[0-9]+.[0-9]+.[0-9]+" + # pre-releases + - "v?[0-9]+.[0-9]+.[0-9]+-**" + pull_request: {} + +jobs: + # Performs quick checks before the expensive test runs + check-and-lint: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + + - name: Install Dependencies + run: npm install + +# - name: Perform a type check +# run: npm run check:ts +# env: +# CI: true + # - name: Lint TypeScript code + # run: npm run lint +# - name: Test package files +# run: npm run test:package + + # Runs adapter tests on all supported node versions and OSes + adapter-tests: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + needs: [check-and-lint] + + runs-on: ${{ matrix.os }} + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: npm install + + - name: Run local tests + run: npm test +# - name: Run unit tests +# run: npm run test:unit +# - name: Run integration tests # (linux/osx) +# if: startsWith(runner.OS, 'windows') == false +# run: DEBUG=testing:* npm run test:integration +# - name: Run integration tests # (windows) +# if: startsWith(runner.OS, 'windows') +# run: set DEBUG=testing:* & npm run test:integration + + # Deploys the final package to NPM + deploy: + needs: [adapter-tests] + + # Trigger this step only when a commit on master is tagged with a version number + if: | + contains(github.event.head_commit.message, '[skip ci]') == false && + github.event_name == 'push' && + github.event.base_ref == 'refs/heads/master' && + startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [12.x] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Extract the version and commit body from the tag + id: extract_release + # The body may be multiline, therefore we need to escape some characters + run: | + VERSION="${{ github.ref }}" + VERSION=${VERSION##*/} + VERSION=${VERSION##*v} + echo "::set-output name=VERSION::$VERSION" + BODY=$(git show -s --format=%b) + BODY="${BODY//'%'/'%25'}" + BODY="${BODY//$'\n'/'%0A'}" + BODY="${BODY//$'\r'/'%0D'}" + echo "::set-output name=BODY::$BODY" + + - name: Install Dependencies + run: npm install + +# - name: Create a clean build +# run: npm run build + - name: Publish package to npm + run: | + npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} + npm whoami + npm publish + + - name: Create Github Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release v${{ steps.extract_release.outputs.VERSION }} + draft: false + # Prerelease versions create prereleases on Github + prerelease: ${{ contains(steps.extract_release.outputs.VERSION, '-') }} + body: ${{ steps.extract_release.outputs.BODY }} + + #- name: Notify Sentry.io about the release + # run: | + # npm i -g @sentry/cli + # export SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + # export SENTRY_URL=https://sentry.iobroker.net + # export SENTRY_ORG=iobroker + # export SENTRY_PROJECT=iobroker-hass + # export SENTRY_VERSION=iobroker.hass@${{ steps.extract_release.outputs.VERSION }} + # sentry-cli releases new $SENTRY_VERSION + # sentry-cli releases 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 + # sentry-cli releases files $SENTRY_VERSION upload-sourcemaps build/ \ No newline at end of file diff --git a/.npmignore b/.npmignore index 10a99f6..73420df 100644 --- a/.npmignore +++ b/.npmignore @@ -1,9 +1,12 @@ +/**/* +/gulpfile.js gulpfile.js -tasks -tmp -test -.travis.yml -appveyor.yml -admin/i18n -iob_npm.done -package-lock.json \ No newline at end of file +!/admin/**/* +!/admin/* +!/lib/**/* +!/lib/* +!/io-package.json +!/package.json +!/LICENSE +!/main.js +!/README.md diff --git a/LICENSE b/LICENSE index 6112339..d3e1995 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 bluefox +Copyright (c) 2018-2020 bluefox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c82737c..1fce7f3 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,38 @@ ![Logo](admin/hass.png) # ioBroker.hass -================= [![NPM version](http://img.shields.io/npm/v/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) -[![NPM](https://nodei.co/npm/iobroker.hass.png?downloads=true)](https://nodei.co/npm/iobroker.hass/) +This adapter allows the connecting of Home Assistant to ioBroker. -This adapter allows connect Home Assistant to ioBroker. +## Usage +Create a long term token in HASS and use it as PW (copy it also in the repeat field). + +## Configuration +There is a good article about connection. + +Please check it https://www.smarthomejetzt.de/mit-iobroker-auf-eine-home-assistant-hass-io-installation-und-die-geraete-zugreifen/ + +**Unfortunately only in german, but the [google translate works rather good](https://translate.google.com/translate?hl=en&sl=de&tl=en&u=https%3A%2F%2Fwww.smarthomejetzt.de%2Fmit-iobroker-auf-eine-home-assistant-hass-io-installation-und-die-geraete-zugreifen%2F)** + + ## Changelog -#### 0.1.0 +### 1.0.0 (2020-12-13) +* (bluefox) added the support of compact mode + +### 0.1.0 * (bluefox) initial release ## License The MIT License (MIT) -Copyright (c) 2018 bluefox +Copyright (c) 2018-2020 bluefox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/admin/index_m.html b/admin/index_m.html index e2f39d7..251fc25 100644 --- a/admin/index_m.html +++ b/admin/index_m.html @@ -56,12 +56,11 @@ callback(obj); } -
- +
logo

Home assistant adapter settings

diff --git a/gulpfile.js b/gulpfile.js index fd48c48..cb67dc7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,10 +1,10 @@ 'use strict'; -var gulp = require('gulp'); -var fs = require('fs'); -var pkg = require('./package.json'); -var iopackage = require('./io-package.json'); -var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; +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() { @@ -13,7 +13,7 @@ function getAppName() { } */ const fileName = 'words.js'; -var languages = { +let languages = { en: {}, de: {}, ru: {}, @@ -22,24 +22,27 @@ var languages = { fr: {}, it: {}, es: {}, - pl: {} + pl: {}, + 'zh-cn': {} }; function lang2data(lang, isFlat) { - var str = isFlat ? '' : '{\n'; - var count = 0; - for (var w in lang) { + 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 { - var key = ' "' + w.replace(/"/g, '\\"') + '": '; + const key = ' "' + w.replace(/"/g, '\\"') + '": '; str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; } } } - if (!count) return isFlat ? '' : '{\n}'; + if (!count) { + return isFlat ? '' : '{\n}'; + } if (isFlat) { return str; } else { @@ -49,15 +52,15 @@ function lang2data(lang, isFlat) { function readWordJs(src) { try { - var words; + let words; if (fs.existsSync(src + 'js/' + fileName)) { words = fs.readFileSync(src + 'js/' + fileName).toString(); } else { words = fs.readFileSync(src + fileName).toString(); } - var lines = words.split(/\r\n|\r|\n/g); - var i = 0; + const lines = words.split(/\r\n|\r|\n/g); + let i = 0; while (!lines[i].match(/^systemDictionary = {/)) { i++; } @@ -75,7 +78,7 @@ function readWordJs(src) { lines[0] = lines[0].replace('systemDictionary = ', ''); lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}'); words = lines.join('\n'); - var resultFunc = new Function('return ' + words + ';'); + const resultFunc = new Function('return ' + words + ';'); return resultFunc(); } catch (e) { @@ -86,16 +89,16 @@ function padRight(text, totalLength) { return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); } function writeWordJs(data, src) { - var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n'; + 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 (var word in data) { + for (const word in data) { if (data.hasOwnProperty(word)) { text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); - var line = ''; - for (var lang in data[word]) { + let line = ''; + for (const lang in data[word]) { if (data[word].hasOwnProperty(lang)) { line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; } @@ -120,16 +123,16 @@ function writeWordJs(data, src) { const EMPTY = ''; function words2languages(src) { - var langs = Object.assign({}, languages); - var data = readWordJs(src); + const langs = Object.assign({}, languages); + const data = readWordJs(src); if (data) { - for (var word in data) { + for (const word in data) { if (data.hasOwnProperty(word)) { - for (var lang in data[word]) { + for (const lang in data[word]) { if (data[word].hasOwnProperty(lang)) { langs[lang][word] = data[word][lang]; // pre-fill all other languages - for (var j in langs) { + for (const j in langs) { if (langs.hasOwnProperty(j)) { langs[j][word] = langs[j][word] || EMPTY; } @@ -141,12 +144,12 @@ function words2languages(src) { if (!fs.existsSync(src + 'i18n/')) { fs.mkdirSync(src + 'i18n/'); } - for (var l in langs) { + for (const l in langs) { if (!langs.hasOwnProperty(l)) continue; - var keys = Object.keys(langs[l]); + const keys = Object.keys(langs[l]); //keys.sort(); - var obj = {}; - for (var k = 0; k < keys.length; k++) { + let obj = {}; + for (let k = 0; k < keys.length; k++) { obj[keys[k]] = langs[l][keys[k]]; } if (!fs.existsSync(src + 'i18n/' + l)) { @@ -160,16 +163,16 @@ function words2languages(src) { } } function words2languagesFlat(src) { - var langs = Object.assign({}, languages); - var data = readWordJs(src); + let langs = Object.assign({}, languages); + const data = readWordJs(src); if (data) { - for (var word in data) { + for (const word in data) { if (data.hasOwnProperty(word)) { - for (var lang in data[word]) { + for (const lang in data[word]) { if (data[word].hasOwnProperty(lang)) { langs[lang][word] = data[word][lang]; // pre-fill all other languages - for (var j in langs) { + for (const j in langs) { if (langs.hasOwnProperty(j)) { langs[j][word] = langs[j][word] || EMPTY; } @@ -178,12 +181,12 @@ function words2languagesFlat(src) { } } } - var keys = Object.keys(langs.en); + const keys = Object.keys(langs.en); //keys.sort(); - for (var l in langs) { + for (const l in langs) { if (!langs.hasOwnProperty(l)) continue; - var obj = {}; - for (var k = 0; k < keys.length; k++) { + let obj = {}; + for (let k = 0; k < keys.length; k++) { obj[keys[k]] = langs[l][keys[k]]; } langs[l] = obj; @@ -191,7 +194,7 @@ function words2languagesFlat(src) { if (!fs.existsSync(src + 'i18n/')) { fs.mkdirSync(src + 'i18n/'); } - for (var ll in langs) { + for (const ll in langs) { if (!langs.hasOwnProperty(ll)) continue; if (!fs.existsSync(src + 'i18n/' + ll)) { fs.mkdirSync(src + 'i18n/' + ll); @@ -205,13 +208,13 @@ function words2languagesFlat(src) { } } function languagesFlat2words(src) { - var dirs = fs.readdirSync(src + 'i18n/'); - var langs = {}; - var bigOne = {}; - var order = Object.keys(languages); - dirs.sort(function (a, b) { - var posA = order.indexOf(a); - var posB = order.indexOf(b); + 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; @@ -226,19 +229,19 @@ function languagesFlat2words(src) { return 0; } }); - var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); + const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); - for (var l = 0; l < dirs.length; l++) { + for (let l = 0; l < dirs.length; l++) { if (dirs[l] === 'flat.txt') continue; - var lang = dirs[l]; - var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); + 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, '').replace(/<\/ b>/g, '').replace(/<\/ span>/g, '').replace(/% s/g, ' %s'); }); - var words = langs[lang]; - for (var word in words) { + const words = langs[lang]; + for (const word in words) { if (words.hasOwnProperty(word)) { bigOne[word] = bigOne[word] || {}; if (words[word] !== EMPTY) { @@ -248,12 +251,12 @@ function languagesFlat2words(src) { } } // read actual words.js - var aWords = readWordJs(); + const aWords = readWordJs(); - var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt']; + const temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt']; if (aWords) { // Merge words together - for (var w in aWords) { + for (const w in aWords) { if (aWords.hasOwnProperty(w)) { if (!bigOne[w]) { console.warn('Take from actual words.js: ' + w); @@ -273,13 +276,13 @@ function languagesFlat2words(src) { writeWordJs(bigOne, src); } function languages2words(src) { - var dirs = fs.readdirSync(src + 'i18n/'); - var langs = {}; - var bigOne = {}; - var order = Object.keys(languages); - dirs.sort(function (a, b) { - var posA = order.indexOf(a); - var posB = order.indexOf(b); + 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; @@ -294,13 +297,13 @@ function languages2words(src) { return 0; } }); - for (var l = 0; l < dirs.length; l++) { + for (let l = 0; l < dirs.length; l++) { if (dirs[l] === 'flat.txt') continue; - var lang = dirs[l]; + const lang = dirs[l]; langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); langs[lang] = JSON.parse(langs[lang]); - var words = langs[lang]; - for (var word in words) { + const words = langs[lang]; + for (const word in words) { if (words.hasOwnProperty(word)) { bigOne[word] = bigOne[word] || {}; if (words[word] !== EMPTY) { @@ -310,12 +313,12 @@ function languages2words(src) { } } // read actual words.js - var aWords = readWordJs(); + const aWords = readWordJs(); - var temporaryIgnore = ['pt', 'fr', 'nl', 'it']; + const temporaryIgnore = ['pt', 'fr', 'nl', 'it']; if (aWords) { // Merge words together - for (var w in aWords) { + for (const w in aWords) { if (aWords.hasOwnProperty(w)) { if (!bigOne[w]) { console.warn('Take from actual words.js: ' + w); @@ -335,33 +338,32 @@ function languages2words(src) { writeWordJs(bigOne, src); } -gulp.task('adminWords2languages', function (done) { +gulp.task('admin (words.js => json)', done => { words2languages('./admin/'); done(); }); -gulp.task('adminWords2languagesFlat', function (done) { +gulp.task('admin (words.js => flat)', done => { words2languagesFlat('./admin/'); done(); }); -gulp.task('adminLanguagesFlat2words', function (done) { +gulp.task('admin (flat => words.js)', done => { languagesFlat2words('./admin/'); done(); }); -gulp.task('adminLanguages2words', function (done) { +gulp.task('admin (json => words.js)', done => { languages2words('./admin/'); done(); }); - -gulp.task('updatePackages', function (done) { +gulp.task('updatePackages', done => { iopackage.common.version = pkg.version; iopackage.common.news = iopackage.common.news || {}; if (!iopackage.common.news[pkg.version]) { - var news = iopackage.common.news; - var newNews = {}; + const news = iopackage.common.news; + let newNews = {}; newNews[pkg.version] = { en: 'news', @@ -374,20 +376,20 @@ gulp.task('updatePackages', function (done) { done(); }); -gulp.task('updateReadme', function (done) { - var readme = fs.readFileSync('README.md').toString(); - var pos = readme.indexOf('## Changelog\n'); +gulp.task('updateReadme', done => { + const readme = fs.readFileSync('README.md').toString(); + const pos = readme.indexOf('## Changelog\n'); if (pos !== -1) { - var readmeStart = readme.substring(0, pos + '## Changelog\n'.length); - var readmeEnd = readme.substring(pos + '## Changelog\n'.length); + let readmeStart = readme.substring(0, pos + '## Changelog\n'.length); + let readmeEnd = readme.substring(pos + '## Changelog\n'.length); if (readme.indexOf(version) === -1) { - var timestamp = new Date(); - var date = timestamp.getFullYear() + '-' + + const timestamp = new Date(); + const date = timestamp.getFullYear() + '-' + ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + ('0' + (timestamp.getDate()).toString(10)).slice(-2); - var news = ''; + let news = ''; if (iopackage.common.news && iopackage.common.news[pkg.version]) { news += '* ' + iopackage.common.news[pkg.version].en; } @@ -398,4 +400,4 @@ gulp.task('updateReadme', function (done) { done(); }); -gulp.task('default', ['updatePackages', 'updateReadme']); \ No newline at end of file +gulp.task('default', gulp.series('updatePackages', 'updateReadme')); \ No newline at end of file diff --git a/io-package.json b/io-package.json index 732146d..c1dbff5 100644 --- a/io-package.json +++ b/io-package.json @@ -1,58 +1,76 @@ { - "common": { - "name": "hass", - "version": "0.1.0", - "title": "Home Assistant", - "desc": { - "en": "Home Assistant connection for ioBroker", - "de": "Home Assistant Verbindung für ioBroker", - "ru": "Подключение Home Assistant для ioBroker", - "pt": "Conexão do Home Assistant para ioBroker", - "nl": "Home Assistant-verbinding voor ioBroker", - "fr": "Connexion Home Assistant pour ioBroker", - "it": "Connessione Home Assistant per ioBroker", - "es": "Conexión de Home Assistant para ioBroker", - "pl": "Połączenie Home Assistant dla ioBroker" - }, - "news": { - "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", - "platform": "Javascript/Node.js", - "mode": "daemon", - "icon": "hass.png", - "enabled": true, - "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.hass/master/admin/hass.png", - "keywords": ["HASS", "Home", "assistant"], - "readme": "https://github.com/ioBroker/ioBroker.hass/blob/master/README.md", - "loglevel": "info", - "type": "iot-systems", - "authors": [ - "bluefox " - ] + "common": { + "name": "hass", + "version": "1.0.0", + "title": "Home Assistant", + "desc": { + "en": "Home Assistant connection for ioBroker", + "de": "Home Assistant Verbindung für ioBroker", + "ru": "Подключение Home Assistant для ioBroker", + "pt": "Conexão do Home Assistant para ioBroker", + "nl": "Home Assistant-verbinding voor ioBroker", + "fr": "Connexion Home Assistant pour ioBroker", + "it": "Connessione Home Assistant per ioBroker", + "es": "Conexión de Home Assistant para ioBroker", + "pl": "Połączenie Home Assistant dla ioBroker" }, - "native": { - "host": "127.0.0.1", - "port": 8123, - "password": "", - "secure": false + "news": { + "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" + } }, - "objects": [ - + "localLink": "http://%host%:8123", + "platform": "Javascript/Node.js", + "mode": "daemon", + "icon": "hass.png", + "enabled": true, + "compact": true, + "materialize": true, + "license": "MIT", + "extIcon": "https://raw.githubusercontent.com/ioBroker/ioBroker.hass/master/admin/hass.png", + "keywords": [ + "HASS", + "Home", + "assistant" ], + "readme": "https://github.com/ioBroker/ioBroker.hass/blob/master/README.md", + "loglevel": "info", + "type": "iot-systems", + "authors": [ + "bluefox " + ] + }, + "native": { + "host": "127.0.0.1", + "port": 8123, + "password": "", + "secure": false + }, + "objects": [ + ], "instanceObjects": [ { - "_id": "info", + "_id": "info", "type": "channel", "common": { "name": "Information" @@ -60,20 +78,20 @@ "native": {} }, { - "_id": "info.connection", + "_id": "info.connection", "type": "state", "common": { - "role": "indicator.connected", - "name": "If connected to HASS server", - "type": "boolean", - "read": true, + "role": "indicator.connected", + "name": "If connected to HASS server", + "type": "boolean", + "read": true, "write": false, - "def": false + "def": false }, "native": {} }, { - "_id": "entities", + "_id": "entities", "type": "channel", "common": { "name": "HASS entities" diff --git a/lib/hass.js b/lib/hass.js index 9524834..af680eb 100644 --- a/lib/hass.js +++ b/lib/hass.js @@ -1,30 +1,32 @@ -var util = require('util'); -var EventEmitter = require('events').EventEmitter; -var WebSocket = require('ws'); +const util = require('util'); +const EventEmitter = require('events').EventEmitter; +const WebSocket = require('ws'); function HASS(options, log) { - if (!(this instanceof HASS)) return new HASS(options); + if (!(this instanceof HASS)) { + return new HASS(options); + } options = options || {}; options.host = options.host || '127.0.0.1'; options.port = parseInt(options.port, 10) || 8123; - var ERRORS = { + const ERRORS = { 1: 'ERR_CANNOT_CONNECT', 2: 'ERR_INVALID_AUTH', 3: 'ERR_CONNECTION_LOST' }; this.socket = null; - var that = this; - var currentId = 1; - var requests = {}; - var connected; - var connectTimeout = null; + const that = this; + let currentId = 1; + const requests = {}; + let connected; + let connectTimeout = null; function subscribeEvents(socket, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'subscribe_events', ts: Date.now(), cb: callback}; socket.send(JSON.stringify({ @@ -39,7 +41,7 @@ function HASS(options, log) { function getConfig(socket, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'get_config', cb: callback, ts: Date.now()}; socket.send(JSON.stringify({ id: id, @@ -52,7 +54,7 @@ function HASS(options, log) { function getStates(socket, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'get_states', cb: callback, ts: Date.now()}; socket.send(JSON.stringify({ id: id, @@ -65,7 +67,7 @@ function HASS(options, log) { function getPanels(socket, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'get_panels', cb: callback, ts: Date.now()}; socket.send(JSON.stringify({ id: id, @@ -78,7 +80,7 @@ function HASS(options, log) { function getServices(socket, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'get_services', cb: callback, ts: Date.now()}; socket.send(JSON.stringify({ id: id, @@ -91,7 +93,7 @@ function HASS(options, log) { function callService(socket, service, domain, serviceData, callback) { if (socket && typeof socket.send === 'function') { - var id = currentId++; + const id = currentId++; requests[id] = {type: 'call_service', cb: callback, ts: Date.now()}; socket.send(JSON.stringify({ id: id, @@ -115,10 +117,10 @@ function HASS(options, log) { } function initSocket(socket) { - socket.on('message', function (msg) { + socket.on('message', msg => { log.silly(msg); - var response = JSON.parse(msg); + const response = JSON.parse(msg); if (response.type === 'event') { if (response.event.data && response.event.event_type === 'system_log_event') { if (response.event.data.level === 'WARNING') { @@ -138,25 +140,22 @@ function HASS(options, log) { that.emit('error', 'Password required. Connection closed'); socket.terminate(); } else { - setTimeout(function () { - sendAuth(socket, options.password); - }, 50); + setTimeout(() => sendAuth(socket, options.password), 50); } } else if (response.type === 'auth_ok') { - setImmediate(function () { - subscribeEvents(socket, function (err) { + setImmediate(() => + subscribeEvents(socket, err => { if (!err) { connected = true; that.emit('connected'); } - }); - }); + })); } else if (response.id === undefined) { - log.error('Invalid answer: ' + msg); + log.error(`Invalid answer: ${msg}`); } else { if (response.type === 'result' && requests[response.id]) { - log.debug('got answer for ' + requests[response.id].type); + log.debug(`got answer for ${requests[response.id].type}`); if (typeof requests[response.id].cb === 'function') { requests[response.id].cb(!response.success, response.result); delete requests[response.id]; @@ -165,7 +164,7 @@ function HASS(options, log) { } }); - socket.on('error', function (err) { + socket.on('error', err => { socket = null; if (err && err.message.indexOf('RSV2 and RSV3 must be clear') !== -1) { // ignore deflate error @@ -173,12 +172,12 @@ function HASS(options, log) { log.error(err); } }); - socket.on('open', function () { + socket.on('open', () => { if (!connected) { } }); - socket.on('close', function () { + socket.on('close', () => { that.socket = null; if (connected) { connected = false; @@ -186,7 +185,7 @@ function HASS(options, log) { } if (!connectTimeout) { - setTimeout(function () { + setTimeout(() => { connectTimeout = null; that.connect(); }, 3000); @@ -194,15 +193,11 @@ function HASS(options, log) { }); } - this.isConnected = function () { - return connected; - }; + this.isConnected = () => connected; this.getConfig = function (callback) { if (!connected) { - if (typeof callback === 'function') { - callback('not connected'); - } + typeof callback === 'function' && callback('not connected'); } else { getConfig(this.socket, callback); } @@ -210,9 +205,7 @@ function HASS(options, log) { this.getStates = function (callback) { if (!connected) { - if (typeof callback === 'function') { - callback('not connected'); - } + typeof callback === 'function' && callback('not connected'); } else { getStates(this.socket, callback); } @@ -220,9 +213,7 @@ function HASS(options, log) { this.getServices = function (callback) { if (!connected) { - if (typeof callback === 'function') { - callback('not connected'); - } + typeof callback === 'function' && callback('not connected'); } else { getServices(this.socket, callback); } @@ -230,9 +221,7 @@ function HASS(options, log) { this.getPanels = function (callback) { if (!connected) { - if (typeof callback === 'function') { - callback('not connected'); - } + typeof callback === 'function' && callback('not connected'); } else { getPanels(this.socket, callback); } @@ -240,9 +229,7 @@ function HASS(options, log) { this.callService = function (service, domain, serviceData, callback) { if (!connected) { - if (typeof callback === 'function') { - callback('not connected'); - } + typeof callback === 'function' && callback('not connected'); } else { callService(this.socket, service, domain, serviceData, callback); } @@ -254,7 +241,7 @@ function HASS(options, log) { connectTimeout = null; } - this.socket = new WebSocket('ws' + (options.secure ? 's' : '') + '://' + options.host + ':' + options.port + '/api/websocket', { + this.socket = new WebSocket(`ws${options.secure ? 's' : ''}://${options.host}:${options.port}/api/websocket`, { perMessageDeflate: false }); diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index bcbe98a..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,64 +0,0 @@ -var controllerDir; -var appName; - -function getAppName() { - var parts = __dirname.replace(/\\/g, '/').split('/'); - return parts[parts.length - 2].split('.')[0]; -} - -// Get js-controller directory to load libs -function getControllerDir(isInstall) { - var fs = require('fs'); - // Find the js-controller location - var controllerDir = __dirname.replace(/\\/g, '/'); - controllerDir = controllerDir.split('/'); - if (controllerDir[controllerDir.length - 3] === 'adapter') { - controllerDir.splice(controllerDir.length - 3, 3); - controllerDir = controllerDir.join('/'); - } else if (controllerDir[controllerDir.length - 3] === 'node_modules') { - controllerDir.splice(controllerDir.length - 3, 3); - controllerDir = controllerDir.join('/'); - if (fs.existsSync(controllerDir + '/node_modules/' + appName + '.js-controller')) { - controllerDir += '/node_modules/' + appName + '.js-controller'; - } else if (fs.existsSync(controllerDir + '/node_modules/' + appName.toLowerCase() + '.js-controller')) { - controllerDir += '/node_modules/' + appName.toLowerCase() + '.js-controller'; - } else if (!fs.existsSync(controllerDir + '/controller.js')) { - if (!isInstall) { - console.log('Cannot find js-controller'); - process.exit(10); - } else { - process.exit(); - } - } - } else if (fs.existsSync(__dirname + '/../../node_modules/' + appName.toLowerCase() + '.js-controller')) { - controllerDir.splice(controllerDir.length - 2, 2); - return controllerDir.join('/') + '/node_modules/' + appName.toLowerCase() + '.js-controller'; - } else { - if (!isInstall) { - console.log('Cannot find js-controller'); - process.exit(10); - } else { - process.exit(); - } - } - return controllerDir; -} - -// Read controller configuration file -function getConfig() { - var fs = require('fs'); - if (fs.existsSync(controllerDir + '/conf/' + appName + '.json')) { - return JSON.parse(fs.readFileSync(controllerDir + '/conf/' + appName + '.json')); - } else if (fs.existsSync(controllerDir + '/conf/' + appName.toLowerCase() + '.json')) { - return JSON.parse(fs.readFileSync(controllerDir + '/conf/' + appName.toLowerCase() + '.json')); - } else { - throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json'); - } -} -appName = getAppName(); -controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1); - -exports.controllerDir = controllerDir; -exports.getConfig = getConfig; -exports.Adapter = require(controllerDir + '/lib/adapter.js'); -exports.appName = appName; diff --git a/main.js b/main.js index ef8d075..de220e9 100644 --- a/main.js +++ b/main.js @@ -1,73 +1,66 @@ /* jshint -W097 */ -/* jshint strict:false */ +/* jshint strict: false */ /* jslint node: true */ 'use strict'; -var utils = require(__dirname + '/lib/utils'); -var HASS = require(__dirname + '/lib/hass'); +const utils = require('@iobroker/adapter-core'); +const HASS = require('./lib/hass'); +const adapterName = require('./package.json').name.split('.').pop(); -var adapter = utils.Adapter('hass'); +let connected = false; +let hass; +let adapter; +const hassObjects = {}; -var connected = false; -var hass; -var hassObjects = {}; +function startAdapter(options) { + options = options || {}; + Object.assign(options, {name: adapterName, unload: stop}); + adapter = new utils.Adapter(options); -// is called if a subscribed state changes -adapter.on('stateChange', function (id, state) { - // you can use the ack flag to detect if it is status (true) or command (false) - if (state && !state.ack) { - if (!connected) { - adapter.log.warn('Cannot send command to "' + id + '", because not connected'); - return; - } - /*if (id === adapter.namespace + '.' + '.info.resync') { - queue.push({command: 'resync'}); - processQueue(); - } else */ - if (hassObjects[id]) { - if (!hassObjects[id].common.write) { - adapter.log.warn('Object ' + id + ' is not writable!'); - } else { - var serviceData = {}; - var fields = hassObjects[id].native.fields; + // is called if a subscribed state changes + adapter.on('stateChange', (id, state) => { + // you can use the ack flag to detect if it is status (true) or command (false) + if (state && !state.ack) { + if (!connected) { + return adapter.log.warn('Cannot send command to "' + id + '", because not connected'); + } + /*if (id === adapter.namespace + '.' + '.info.resync') { + queue.push({command: 'resync'}); + processQueue(); + } else */ + if (hassObjects[id]) { + if (!hassObjects[id].common.write) { + adapter.log.warn('Object ' + id + ' is not writable!'); + } else { + const serviceData = {}; + const fields = hassObjects[id].native.fields; - for (var field in fields) { - if (!fields.hasOwnProperty(field)) continue; + for (const field in fields) { + if (!fields.hasOwnProperty(field)) { + continue; + } - if (field === 'entity_id') { - serviceData.entity_id = hassObjects[id].native.entity_id - } else { - serviceData[field] = state.val; + if (field === 'entity_id') { + serviceData.entity_id = hassObjects[id].native.entity_id + } else { + serviceData[field] = state.val; + } } + + hass.callService(hassObjects[id].native.attr, hassObjects[id].native.domain || hassObjects[id].native.type, serviceData, err => + err && adapter.log.error('Cannot control ' + id + ': ' + err)); } - - hass.callService(hassObjects[id].native.attr, hassObjects[id].native.domain || hassObjects[id].native.type, serviceData, function (err) { - if (err) { - adapter.log.error('Cannot control ' + id + ': ' + err); - } - }); } } - } -}); + }); -// Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ... -adapter.on('message', function (obj) { - if (typeof obj === 'object' && obj.message) { - if (obj.command === 'send') { - // e.g. send email or pushover or whatever - console.log('send command'); + // is called when databases are connected and adapter received configuration. + // start here! + adapter.on('ready', main); - // Send response in callback if required - if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback); - } - } -}); - -// is called when databases are connected and adapter received configuration. -// start here! -adapter.on('ready', main); + return adapter; +} function getUnit(name) { name = name.toLowerCase(); @@ -87,37 +80,34 @@ function getUnit(name) { function syncStates(states, cb) { if (!states || !states.length) { - cb(); - return; + return cb(); } - var state = states.shift(); - var id = state.id; + const state = states.shift(); + const id = state.id; delete state.id; - adapter.setForeignState(id, state, function (err) { - if (err) adapter.log.error(err); + adapter.setForeignState(id, state, err => { + err && adapter.log.error(err); setImmediate(syncStates, states, cb); }); } function syncObjects(objects, cb) { if (!objects || !objects.length) { - cb(); - return; + return cb(); } - var obj = objects.shift(); + const obj = objects.shift(); hassObjects[obj._id] = obj; - adapter.getForeignObject(obj._id, function (err, oldObj) { + adapter.getForeignObject(obj._id, (err, oldObj) => { - if (err) adapter.log.error(err); + err && adapter.log.error(err); if (!oldObj) { adapter.log.debug('Create "' + obj._id + '"'); hassObjects[obj._id] = obj; - adapter.setForeignObject(obj._id, obj, function (err) { - if (err) adapter.log.error(err); - + adapter.setForeignObject(obj._id, obj, err => { + err && adapter.log.error(err); setImmediate(syncObjects, objects, cb); }); } else { @@ -126,8 +116,8 @@ function syncObjects(objects, cb) { oldObj.native = obj.native; adapter.log.debug('Update "' + obj._id + '"'); - adapter.setForeignObject(obj._id, oldObj, function (err) { - if (err) adapter.log.error(err); + adapter.setForeignObject(obj._id, oldObj, err => { + err => adapter.log.error(err); setImmediate(syncObjects, objects, cb); }); } else { @@ -138,7 +128,7 @@ function syncObjects(objects, cb) { } function syncRoom(room, members, cb) { - adapter.getForeignObject('enum.rooms.' + room, function (err, obj) { + adapter.getForeignObject('enum.rooms.' + room, (err, obj) => { if (!obj) { obj = { _id: 'enum.rooms.' + room, @@ -150,15 +140,15 @@ function syncRoom(room, members, cb) { native: {} }; adapter.log.debug('Update "' + obj._id + '"'); - adapter.setForeignObject(obj._id, obj, function (err) { - if (err) adapter.log.error(err); + adapter.setForeignObject(obj._id, obj, err => { + err && adapter.log.error(err); cb(); }); } else { obj.common = obj.common || {}; obj.common.members = obj.common.members || []; - var changed = false; - for (var m = 0; m < members.length; m++) { + let changed = false; + for (let m = 0; m < members.length; m++) { if (obj.common.members.indexOf(members[m]) === -1) { changed = true; obj.common.members.push(members[m]); @@ -166,8 +156,8 @@ function syncRoom(room, members, cb) { } if (changed) { adapter.log.debug('Update "' + obj._id + '"'); - adapter.setForeignObject(obj._id, obj, function (err) { - if (err) adapter.log.error(err); + adapter.setForeignObject(obj._id, obj, err => { + err && adapter.log.error(err); cb(); }); } else { @@ -177,41 +167,41 @@ function syncRoom(room, members, cb) { }); } -var knownAttributes = { - azimuth: {write: false, read: true, unit: '°'}, +const knownAttributes = { + azimuth: {write: false, read: true, unit: '°'}, elevation: {write: false, read: true, unit: '°'} }; -var ERRORS = { +const ERRORS = { 1: 'ERR_CANNOT_CONNECT', 2: 'ERR_INVALID_AUTH', 3: 'ERR_CONNECTION_LOST' }; -var mapTypes = { +const mapTypes = { 'string': 'string', 'number': 'number', 'object': 'mixed', 'boolean': 'boolean' }; -var skipServices = [ +const skipServices = [ 'persistent_notification' ]; function parseStates(entities, services, callback) { - var objs = []; - var states = []; - var obj; - var channel; - for (var e = 0; e < entities.length; e++) { - var entity = entities[e]; + const objs = []; + const states = []; + let obj; + let channel; + for (let e = 0; e < entities.length; e++) { + const entity = entities[e]; if (!entity) continue; - var name = entity.name || (entity.attributes && entity.attributes.friendly_name ? entity.attributes.friendly_name : entity.entity_id); - var desc = entity.attributes && entity.attributes.attribution ? entity.attributes.attribution : undefined; + const name = entity.name || (entity.attributes && entity.attributes.friendly_name ? entity.attributes.friendly_name : entity.entity_id); + const desc = entity.attributes && entity.attributes.attribution ? entity.attributes.attribution : undefined; channel = { - _id: adapter.namespace + '.entities.' + entity.entity_id, + _id: `${adapter.namespace}.entities.${entity.entity_id}`, common: { name: name }, @@ -224,15 +214,15 @@ function parseStates(entities, services, callback) { if (desc) channel.common.desc = desc; objs.push(channel); - var lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined; - var ts = entity.last_updated ? new Date(entity.last_updated).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; if (entity.state !== undefined) { obj = { - _id: adapter.namespace + '.entities.' + entity.entity_id + '.state', + _id: `${adapter.namespace}.entities.${entity.entity_id}.state`, type: 'state', common: { - name: name + ' STATE', + name: `${name} STATE`, read: true, write: false }, @@ -250,11 +240,13 @@ function parseStates(entities, services, callback) { } if (entity.attributes) { - for (var attr in entity.attributes) { + for (const attr in entity.attributes) { if (entity.attributes.hasOwnProperty(attr)) { - if (attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') continue; + if (attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') { + continue; + } - var common; + let common; if (knownAttributes[attr]) { common = Object.assign({}, knownAttributes[attr]); } else { @@ -262,7 +254,7 @@ function parseStates(entities, services, callback) { } obj = { - _id: adapter.namespace + '.entities.' + entity.entity_id + '.' + attr, + _id: `${adapter.namespace}.entities.${entity.entity_id}.${attr}`, type: 'state', common: common, native: { @@ -291,14 +283,14 @@ function parseStates(entities, services, callback) { } } - var serviceType = entity.entity_id.split('.')[0]; + const serviceType = entity.entity_id.split('.')[0]; - if (services[serviceType] && skipServices.indexOf(serviceType) === -1) { - var service = services[serviceType]; - for (var s in service) { + if (services[serviceType] && !skipServices.includes(serviceType)) { + const service = services[serviceType]; + for (const s in service) { if (service.hasOwnProperty(s)) { obj = { - _id: adapter.namespace + '.entities.' + entity.entity_id + '.' + s, + _id: `${adapter.namespace}.entities.${entity.entity_id}.${s}`, type: 'state', common: { desc: service[s].description, @@ -320,9 +312,8 @@ function parseStates(entities, services, callback) { } } - syncObjects(objs, function () { - syncStates(states, callback); - }); + syncObjects(objs, () => + syncStates(states, callback)); } function main() { @@ -336,60 +327,60 @@ function main() { hass = new HASS(adapter.config, adapter.log); - hass.on('error', function (err) { - adapter.log.error(err); - }); - hass.on('state_changed', function (entity) { - var id = adapter.namespace + '.entities.' + entity.entity_id + '.'; - var lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined; - var ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined; + hass.on('error', err => + adapter.log.error(err)); + + hass.on('state_changed', entity => { + const id = adapter.namespace + '.entities.' + entity.entity_id + '.'; + const lc = entity.last_changed ? new Date(entity.last_changed).getTime() : undefined; + const ts = entity.last_updated ? new Date(entity.last_updated).getTime() : undefined; if (entity.state !== undefined) { adapter.setForeignState(id + 'state', {val: entity.state, ack: true, lc: lc, ts: ts}); } if (entity.attributes) { - for (var attr in entity.attributes) { - if (!entity.attributes.hasOwnProperty(attr) || attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') continue; + for (const attr in entity.attributes) { + if (!entity.attributes.hasOwnProperty(attr) || attr === 'friendly_name' || attr === 'unit_of_measurement' || attr === 'icon') { + continue; + } adapter.setForeignState(id + attr, {val: entity.attributes[attr], ack: true, lc: lc, ts: ts}); } } }); - hass.on('connected', function () { + hass.on('connected', () => { if (!connected) { adapter.log.debug('Connected'); connected = true; adapter.setState('info.connection', true, true); - hass.getConfig(function (err, config) { + hass.getConfig((err, config) => { if (err) { adapter.log.error('Cannot read config: ' + err); return; } //adapter.log.debug(JSON.stringify(config)); - setTimeout(function () { - hass.getStates(function (err, states) { + setTimeout(() => + hass.getStates((err, states) => { if (err) { return adapter.log.error('Cannot read states: ' + err); } //adapter.log.debug(JSON.stringify(states)); - setTimeout(function () { - hass.getServices(function (err, services) { + setTimeout(() => + hass.getServices((err, services) => { if (err) { adapter.log.error('Cannot read states: ' + err); } else { //adapter.log.debug(JSON.stringify(services)); - parseStates(states, services, function () { + parseStates(states, services, () => { }); } - }); - }, 100); - }); - }, 100); + }), 100); + }), 100); }); } }); - hass.on('disconnected', function () { + hass.on('disconnected', () => { if (connected) { adapter.log.debug('Disconnected'); connected = false; @@ -399,3 +390,11 @@ function main() { hass.connect(); } + +// If started as allInOne/compact mode => return function to create instance +if (module && module.parent) { + module.exports = startAdapter; +} else { + // or start the instance directly + startAdapter(); +} diff --git a/package.json b/package.json index a1d5e28..2186e72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.hass", - "version": "0.1.0", + "version": "1.0.0", "description": "Home Assistant", "author": { "name": "bluefox", @@ -26,17 +26,20 @@ }, "dependencies": { "home-assistant-js-websocket": "^1.1.4", - "websocket": "^1.0.25", - "ws": "^5.0.0" + "websocket": "^1.0.33", + "ws": "^5.0.0", + "@iobroker/adapter-core": "^2.4.0" }, "devDependencies": { - "gulp": "^3.9.1", - "mocha": "^4.1.0", - "chai": "^4.1.2" + "@alcalzone/release-script": "^1.8.1", + "gulp": "^4.0.2", + "mocha": "^8.2.1", + "chai": "^4.2.0" }, "main": "main.js", "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" }, "bugs": { "url": "https://github.com/ioBroker/ioBroker.hass/issues"