diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..93234e55 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,163 @@ +module.exports = { + env: { + es6: true, + node: true + }, + extends: ['eslint:recommended', 'plugin:prettier/recommended'], + plugins: [], + ignorePatterns: ['**/doc', '**/build', 'admin/'], + reportUnusedDisableDirectives: true, + rules: { + /* pretier takes care of these two rules + indent: [ + 'error', + 4, + { + SwitchCase: 1 + } + ], + 'array-element-newline': [ + 'error', + { + ArrayExpression: 'consistent', + ArrayPattern: { minItems: 3 } + } + ],*/ + curly: 'error', + 'brace-style': 'error', + 'arrow-parens': ['error', 'as-needed'], + 'no-console': 'off', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'all' }], + 'no-useless-escape': 'warn', + 'no-constant-condition': 'off', + 'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 1 }], + 'no-var': 'error', + 'prefer-const': 'error', + 'no-throw-literal': 'error', + 'prefer-promise-reject-errors': 'error', + 'require-await': 'error', + 'no-return-await': 'error', + eqeqeq: ['error', 'always'], + quotes: [ + 'error', + 'single', + { + avoidEscape: true, + allowTemplateLiterals: true + } + ], + semi: ['error', 'always'], + 'comma-dangle': [ + 'error', + { + arrays: 'never', + objects: 'never', + imports: 'never', + exports: 'never', + functions: 'ignore' + } + ], + 'no-trailing-spaces': 'error', + 'prettier/prettier': 'error', + 'no-nested-ternary': 'off', // maybe turn this on later + 'no-unneeded-ternary': 'error' + }, + parserOptions: { + ecmaVersion: 2019 + }, + overrides: [ + // we need ts parser for ts files + { + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2019, + sourceType: 'module', + project: './tsconfig.json' + }, + files: ['**/*.ts', '**/*.tsx'], + extends: ['plugin:@typescript-eslint/recommended'], + rules: { + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-use-before-define': [ + 'error', + { + functions: false, + typedefs: false, + classes: false + } + ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + ignoreRestSiblings: true, + argsIgnorePattern: '^_' + } + ], + '@typescript-eslint/no-object-literal-type-assertion': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', // This is necessary for Map.has()/get()! + '@typescript-eslint/no-inferrable-types': [ + 'error', + { + ignoreProperties: true, + ignoreParameters: true + } + ], + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-expect-error': false, + 'ts-ignore': true, + 'ts-nocheck': true, + 'ts-check': false + } + ], + '@typescript-eslint/restrict-template-expressions': [ + 'error', + { + allowNumber: true, + allowBoolean: true, + // This is necessary to log errors + // TODO: Consider switching to false when we may annotate catch clauses + allowAny: true, + allowNullish: true + } + ], + '@typescript-eslint/no-misused-promises': [ + 'error', + { + checksVoidReturn: false + } + ], + // We can turn this on from time to time but in general these rules + // make our lives harder instead of easier + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + + // Although this rule makes sense, it takes about a second to execute (and we don't need it) + '@typescript-eslint/no-implied-eval': 'off', + + '@typescript-eslint/explicit-module-boundary-types': [ + 'warn', + { allowArgumentsExplicitlyTypedAsAny: true } + ], + '@typescript-eslint/no-this-alias': 'off', + + // Prefer simple property access and declaration without quotes + 'dot-notation': 'off', + '@typescript-eslint/dot-notation': [ + 'error', + { + allowPrivateClassPropertyAccess: true, + allowProtectedClassPropertyAccess: true + } + ], + 'quote-props': ['error', 'as-needed'] + } + } + ] +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index d230a27d..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "env": { - "es6": true, - "node": true - }, - "extends": "eslint:recommended", - "rules": { - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "curly": "error", - "brace-style": "error", - "arrow-parens": ["error", "as-needed"], - "no-console": "off", - "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "all" }], - "no-useless-escape": "warn", - "no-constant-condition": "off", - "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }], - "no-var": "error", - "prefer-const": "error", - "no-throw-literal": "error", - "prefer-promise-reject-errors": "error", - "require-await": "error", - "no-return-await": "error", - "eqeqeq": ["error", "always"], - "quotes": [ - "error", - "single", - { - "avoidEscape": true, - "allowTemplateLiterals": true - } - ], - "semi": [ - "error", - "always" - ], - "comma-dangle": ["error", { - "arrays": "never", - "objects": "never", - "imports": "never", - "exports": "never", - "functions": "ignore" - }], - "no-trailing-spaces": "error" - }, - "parserOptions": { - "ecmaVersion": 2019 - } -} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..f45187bd --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,12 @@ +{ + "printWidth": 120, + "semi": true, + "tabWidth": 4, + "useTabs": false, + "trailingComma": "none", + "singleQuote": true, + "endOfLine": "lf", + "bracketSpacing": true, + "arrowParens": "avoid", + "quoteProps": "as-needed" +} diff --git a/LICENSE b/LICENSE index 1c7d7247..0e7be23c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2021 bluefox +Copyright (c) 2014-2022 bluefox Copyright (c) 2014 hobbyquaker diff --git a/README.md b/README.md index afcc0f23..ea0daaba 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,9 @@ via or/and operator. The then clause of the program can remain empty. Now your s Placeholder for the next version (at the beginning of the line): ### __WORK IN PROGRESS__ --> +### 1.15.1 (2022-02-02) +* (foxriver76) we fixxed type of default value and min/max of heating groups (closes #443) + ### 1.15.0 (2021-12-26) * (foxriver76) added image for `HmIP-STE2-PCB` * (foxriver76) we now handle `replaceDevice` requests by deleting old device and creating new one (closes #420) @@ -345,7 +348,7 @@ of this approach (more requests to CCU on first setup) The MIT License (MIT) -Copyright (c) 2014-2021 bluefox +Copyright (c) 2014-2022 bluefox Copyright (c) 2014 hobbyquaker diff --git a/admin/icons/denon.png b/admin/icons/denon.png new file mode 100644 index 00000000..1b3b48bb Binary files /dev/null and b/admin/icons/denon.png differ diff --git a/gulpfile.js b/gulpfile.js index a6a29cfc..abbc9bcf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -8,7 +8,7 @@ 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; +const version = pkg && pkg.version ? pkg.version : iopackage.common.version; const fileName = 'words.js'; const EMPTY = ''; const translate = require('./lib/gulptools').translateText; @@ -26,14 +26,12 @@ const languages = { }; function lang2data(lang) { - let str ='{\n'; + let str = '{\n'; let count = 0; - for (const w in lang) { - if (lang.hasOwnProperty(w)) { - count++; - const key = ' "' + w.replace(/"/g, '\\"') + '": '; - str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; - } + for (const w of Object.keys(lang)) { + count++; + const key = ' "' + w.replace(/"/g, '\\"') + '": '; + str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; } if (!count) { return '{\n}'; @@ -56,7 +54,7 @@ function readWordJs(src) { const resultFunc = new Function('return ' + words + ';'); return resultFunc(); - } catch (e) { + } catch { return null; } } @@ -70,21 +68,17 @@ function writeWordJs(data, src) { 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'; + for (const word of Object.keys(data)) { + text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); + let line = ''; + for (const lang of Object.keys(data[word])) { + 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 += '};'; if (fs.existsSync(src + 'js/' + fileName)) { @@ -98,27 +92,19 @@ 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; - } - } - } + for (const word of Object.keys(data)) { + for (const lang of Object.keys(data[word])) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j of Object.keys(langs)) { + 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; + for (const l of Object.keys(langs)) { const keys = Object.keys(langs[l]); keys.sort(); const obj = {}; @@ -145,35 +131,38 @@ function languages2words(src) { const posA = order.indexOf(a); const posB = order.indexOf(b); if (posA === -1 && posB === -1) { - if (a > b) + if (a > b) { return 1; - if (a < b) + } + if (a < b) { return -1; + } return 0; } else if (posA === -1) { return -1; } else if (posB === -1) { return 1; } else { - if (posA > posB) + if (posA > posB) { return 1; - if (posA < posB) + } + if (posA < posB) { return -1; + } return 0; } }); for (const lang of dirs) { - if (lang === 'flat.txt') + if (lang === 'flat.txt') { continue; + } 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]; - } + for (const word of Object.keys(words)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; } } } @@ -183,22 +172,20 @@ function languages2words(src) { const temporaryIgnore = ['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); - } - }); + for (const w of Object.keys(aWords)) { + 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); @@ -211,7 +198,7 @@ async function translateNotExisting(obj, baseText, yandex) { } if (t) { - for (let l in languages) { + for (const l in languages) { if (!obj[l]) { const time = new Date().getTime(); obj[l] = await translate(t, l, yandex); @@ -267,23 +254,28 @@ gulp.task('updateReadme', function (done) { 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); + 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); + fs.writeFileSync( + 'README.md', + readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd + ); } } done(); }); -gulp.task('translate', async function (done) { - +gulp.task('translate', async function () { let yandex; const i = process.argv.indexOf('--yandex'); if (i > -1) { @@ -293,9 +285,9 @@ gulp.task('translate', async function (done) { if (iopackage && iopackage.common) { if (iopackage.common.news) { console.log('Translate News'); - for (let k in iopackage.common.news) { + for (const k in iopackage.common.news) { console.log('News: ' + k); - let nw = iopackage.common.news[k]; + const nw = iopackage.common.news[k]; await translateNotExisting(nw, null, yandex); } } @@ -309,14 +301,14 @@ gulp.task('translate', async function (done) { } if (fs.existsSync('./admin/i18n/en/translations.json')) { - let enTranslations = require('./admin/i18n/en/translations.json'); - for (let l in languages) { + const enTranslations = require('./admin/i18n/en/translations.json'); + for (const l in languages) { console.log('Translate Text: ' + l); let existing = {}; if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { existing = require('./admin/i18n/' + l + '/translations.json'); } - for (let t in enTranslations) { + for (const t in enTranslations) { if (!existing[t]) { existing[t] = await translate(enTranslations[t], l, yandex); } @@ -327,11 +319,10 @@ gulp.task('translate', async function (done) { fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); } } - } fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); }); gulp.task('translateAndUpdateWordsJS', gulp.series('translate', 'adminLanguages2words', 'adminWords2languages')); -gulp.task('default', gulp.series('updatePackages', 'updateReadme')); \ No newline at end of file +gulp.task('default', gulp.series('updatePackages', 'updateReadme')); diff --git a/io-package.json b/io-package.json index ef058157..482cc61b 100644 --- a/io-package.json +++ b/io-package.json @@ -14,8 +14,20 @@ "pl": "Łączy procesy HomeMatic-Interface (BidCos-Services, Homegear i CUxD) za pośrednictwem XML-RPC lub BIN-RPC z ioBroker", "zh-cn": "通过 XML-RPC 或 BIN-RPC 将 HomeMatic 接口进程(BidCos-Services、Homegear 和 CUxD)连接到 ioBroker" }, - "version": "1.15.0", + "version": "1.15.1", "news": { + "1.15.1": { + "en": "we fixxed type of default value and min/max of heating groups (closes #443)", + "de": "Wir haben die Art des Standardwerts und das Minimum/Maximum der Heizgruppen korrigiert (schließt #443)", + "ru": "мы зафиксировали тип значения по умолчанию и мин/макс группы нагрева (закрывает #443)", + "pt": "fixamos o tipo de valor padrão e min/max de grupos de aquecimento (fecha #443)", + "nl": "we hebben het type standaardwaarde en min/max van verwarmingsgroepen vastgelegd (sluit #443)", + "fr": "nous fixons le type de valeur par défaut et le min/max des groupes de chauffage (ferme #443)", + "it": "abbiamo fissato il tipo di valore predefinito e min/max dei gruppi di riscaldamento (chiude #443)", + "es": "fijamos el tipo de valor predeterminado y min/max de grupos de calefacción (cierra #443)", + "pl": "ustaliliśmy typ wartości domyślnej oraz min/max grup grzewczych (zamyka #443)", + "zh-cn": "我们固定了默认值的类型和加热组的最小值/最大值(关闭 #443)" + }, "1.15.0": { "en": "added image for `HmIP-STE2-PCB`\nwe now handle `replaceDevice` requests by deleting old device and creating new one (closes #420)", "de": "Bild hinzugefügt für `HmIP-STE2-PCB`\nWir verarbeiten jetzt `replaceDevice`-Anfragen, indem wir das alte Gerät löschen und ein neues erstellen (schließt #420)", @@ -87,18 +99,6 @@ "es": "ahora mapeamos correctamente el papel de los detectores de humo (cierra el n. ° 354)", "pl": "teraz poprawnie mapujemy rolę czujek dymu (zamyka #354)", "zh-cn": "我们现在正确地映射了烟雾探测器的作用(关闭 #354)" - }, - "1.14.42": { - "en": "Added the roles to thermostat states\nAdded the roles for switch\nApply new roles to existing states", - "de": "Die Rollen zu den Thermostatzuständen hinzugefügt\nDie Rollen für den Schalter hinzugefügt\nAnwenden neuer Rollen auf bestehende Zustände", - "ru": "Добавлены роли в состояния термостата.\nДобавлены роли для переключателя\nПрименить новые роли к существующим состояниям", - "pt": "Adicionadas as funções aos estados do termostato\nAdicionadas as funções para troca\nAplicar novas funções aos estados existentes", - "nl": "De rollen toegevoegd aan thermostaatstatussen\nDe rollen voor switch toegevoegd\nPas nieuwe rollen toe op bestaande statussen", - "fr": "Ajout des rôles aux états du thermostat\nAjout des rôles pour le commutateur\nAppliquer de nouveaux rôles aux états existants", - "it": "Aggiunti i ruoli agli stati del termostato\nAggiunti i ruoli per switch\nApplicare nuovi ruoli agli stati esistenti", - "es": "Se agregaron los roles a los estados del termostato.\nSe agregaron los roles para el cambio.\nAplicar nuevos roles a estados existentes", - "pl": "Dodano role do stanów termostatu\nDodano role dla przełącznika\nZastosuj nowe role do istniejących stanów", - "zh-cn": "将角色添加到恒温器状态\n添加切换角色\n将新角色应用于现有状态" } }, "authors": [ diff --git a/lib/gulptools.js b/lib/gulptools.js index ec0ad329..39666bd8 100644 --- a/lib/gulptools.js +++ b/lib/gulptools.js @@ -19,7 +19,9 @@ function isObject(it) { * @returns {it is any[]} */ function isArray(it) { - if (typeof Array.isArray === 'function') return Array.isArray(it); + if (typeof Array.isArray === 'function') { + return Array.isArray(it); + } return Object.prototype.toString.call(it) === '[object Array]'; } @@ -30,7 +32,7 @@ function isArray(it) { * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers * @returns {Promise} */ -async function translateText(text, targetLang, yandexApiKey) { +function translateText(text, targetLang, yandexApiKey) { if (targetLang === 'en') { return text; } else if (!text) { @@ -55,8 +57,10 @@ async function translateYandex(text, targetLang, apiKey) { targetLang = 'zh'; } try { - const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; - const response = await axios({url, timeout: 15000}); + const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent( + text + )}&lang=en-${targetLang}`; + const response = await axios({ url, timeout: 15000 }); if (response.data && response.data.text && isArray(response.data.text)) { return response.data.text[0]; } @@ -74,8 +78,10 @@ async function translateYandex(text, targetLang, apiKey) { */ async function translateGoogle(text, targetLang) { try { - const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; - const response = await axios({url, timeout: 15000}); + const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent( + text + )}&ie=UTF-8&oe=UTF-8`; + const response = await axios({ url, timeout: 15000 }); if (isArray(response.data)) { // we got a valid response return response.data[0][0][0]; @@ -83,9 +89,7 @@ async function translateGoogle(text, targetLang) { throw new Error('Invalid response for translate request'); } catch (e) { if (e.response && e.response.status === 429) { - throw new Error( - `Could not translate to "${targetLang}": Rate-limited by Google Translate` - ); + throw new Error(`Could not translate to "${targetLang}": Rate-limited by Google Translate`); } else { throw new Error(`Could not translate to "${targetLang}": ${e}`); } diff --git a/lib/images.js b/lib/images.js index 5433191b..2f39608a 100644 --- a/lib/images.js +++ b/lib/images.js @@ -1,4 +1,4 @@ -module.exports = { +module.exports = { 'ZEL STG RM HS 4': '18_hm-rc-4_thumb.png', 'HM-LC-Sw1-Ba-PCB': '77_hm-lc-sw1-ba-pcb_thumb.png', 'HM-LC-Sw2-PB-FM': 'PushButton-4ch-wm_thumb.png', @@ -61,7 +61,7 @@ module.exports = { 'HmIP-BWTH24': '121_hmip-wth_thumb.png', 'HM-LC-Sw4-DR-2': '68_hm-lc-sw4-dr_thumb.png', 'HmIP-DRBLI4': '206_hmip-drbli4_thumb.png', - 'DEVICE': 'unknown_device_thumb.png', + DEVICE: 'unknown_device_thumb.png', 'HM-Sec-MDIR': '124_hm-sec-mdir_thumb.png', 'VIR-LG-RGBW': 'hm-coupling-rgbw.png', 'HM-PBI-4-FM': '38_hm-pbi-4-fm_thumb.png', @@ -121,7 +121,7 @@ module.exports = { 'HmIP-WTH-B': '200_hmip-wth-b_thumb.png', 'HM-Sen-MDIR-WM55': '103_hm-sen-mdir-wm55_thumb.png', 'HMIP-eTRV': '120_hmip-etrv_thumb.png', - 'WS550': '9_hm-ws550-us_thumb.png', + WS550: '9_hm-ws550-us_thumb.png', 'HM-OU-CFM-Pl': '60_hm-ou-cf-pl_thumb.png', 'HmIP-MOD-RC8': '159_hmip-mod-rc8_thumb.png', 'HM-LC-Bl1-FM': '7_hm-lc-bl1-fm_thumb.png', @@ -140,7 +140,7 @@ module.exports = { 'HM-LC-Sw2-DR': '69_hm-lc-sw2-dr_thumb.png', 'HmIP-PCBS-BAT': '151_hmip-pcbs-bat_thumb.png', 'HM-LC-RGBW-WM': '111_hm-lc-rgbw-wm_thumb.png', - 'WS888': '9_hm-ws550-us_thumb.png', + WS888: '9_hm-ws550-us_thumb.png', 'HM-LC-Sw4-SM-2': '3_hm-lc-sw4-sm_thumb.png', 'HM-PB-4-WM': 'PushButton-4ch-wm_thumb.png', 'HM-RC-19-B': '20_hm-rc-19_thumb.png', @@ -300,7 +300,7 @@ module.exports = { 'HM-Sec-Sir-WM': '117_hm-sec-sir-wm_thumb.png', 'HmIP-BSM': 'PushButton-2ch-wm_thumb.png', 'HmIP-DRDI3': '204_hmip-drdi3_thumb.png', - 'KS550': 'WeatherCombiSensor_thumb.png', + KS550: 'WeatherCombiSensor_thumb.png', 'HM-LC-Sw4-SM-ATmega168': '3_hm-lc-sw4-sm_thumb.png', 'HmIP-BSL': '173_hmip-bsl_thumb.png', 'HmIP-SWO-B': '171_hmip-swo-b_thumb.png', @@ -373,7 +373,7 @@ module.exports = { 'HMIP-WRC2': '112_hmip-wrc2_thumb.png', 'HmIP-STHD': '147_hmip-sthd_thumb.png', 'HM-Sec-SD-2-Team': '105_hm-sec-sd-2-team_thumb.png', - 'atent': '73_hm-atent_thumb.png', + atent: '73_hm-atent_thumb.png', 'HM-ES-PMSw1-SM': '115_hm-es-pmsw1-sm_thumb.png', 'HmIP-SFD': '212_hmip-sfd-1.png', 'RPI-RF-MOD': 'RPI-RF-MOD.png', diff --git a/lib/roles.js b/lib/roles.js index b50f938f..67d2f4aa 100644 --- a/lib/roles.js +++ b/lib/roles.js @@ -1,19 +1,19 @@ module.exports = { - 'chTYPE': { - 'DIMMER': 'light.dimmer', - 'BLIND': 'blind', - 'SWITCH': 'switch', - 'KEY': 'button', - 'SHUTTER_CONTACT': 'sensor' + chTYPE: { + DIMMER: 'light.dimmer', + BLIND: 'blind', + SWITCH: 'switch', + KEY: 'button', + SHUTTER_CONTACT: 'sensor' }, - 'dpCONTROL': { + dpCONTROL: { 'DIMMER.LEVEL': 'level.dimmer', 'BLIND.LEVEL': 'level.blind', 'LOCK.STATE': 'switch.lock', 'DOOR_SENSOR.STATE': 'value.window', 'DANGER.STATE': 'sensor.alarm.fire' }, - 'chTYPE_dpNAME': { + chTYPE_dpNAME: { 'DIMMER.LEVEL': 'level.dimmer', 'DIMMER.OLD_LEVEL': 'value.dimmer', 'DIMMER.LEVEL_REAL': 'value.dimmer', @@ -21,71 +21,71 @@ module.exports = { 'VIRTUAL_DIMMER.OLD_LEVEL': 'value.dimmer', 'VIRTUAL_DIMMER.LEVEL_REAL': 'value.dimmer' }, - 'dpNAME': { - 'BATTERY_STATE': 'value.voltage', - 'BOOST_STATE': 'level.boost', - 'BOOST_MODE': 'switch.mode.boost', - 'PARTY_MODE': 'switch.mode.party', - 'FROST_PROTECTION': 'indicator', - 'CONTROL_MODE': 'indicator', - 'FAULT_REPORTING': 'indicator', - 'MANU_MODE': 'level.temperature', - 'ACTUAL_TEMPERATURE': 'value.temperature', - 'ACTUAL_TEMPERATURE_STATUS': 'value', - 'TEMPERATURE': 'value.temperature', - 'SETPOINT': 'level.temperature', - 'SET_TEMPERATURE': 'level.temperature', - 'SET_POINT_TEMPERATURE': 'level.temperature', - 'SET_POINT_MODE': 'level.mode.thermostat', - 'HUMIDITY': 'value.humidity', - 'STATE': 'state', - 'PRESS_SHORT': 'button', - 'PRESS_LONG': 'button.long', - 'PRESS_LONG_RELEASE': 'button.release', - 'PRESS_CONT': 'button.continuous', - 'LEVEL': 'level', - 'LOWBAT': 'indicator.lowbat', - 'LOW_BAT': 'indicator.lowbat', - 'INSTALL_TEST': 'indicator', - 'UNREACH': 'indicator.unreach', - 'WINDOW_STATE': 'value.window', - 'WORKING': 'indicator.working', - 'DIRECTION': 'indicator.direction', - 'CONFIG_PENDING': 'indicator', - 'UPDATE_PENDING': 'indicator', - 'INSTALL_MODE': 'indicator', - 'RSSI_PEER': 'value.rssi', - 'RSSI_DEVICE': 'value.rssi', - 'AES_KEY': 'value', - 'STICKY_UNREACH': 'indicator.unreach', - 'ERROR': 'indicator.error', - 'ERROR_OVERHEAT': 'indicator.error.overheat', - 'ERROR_OVERLOAD': 'indicator.error.overload', - 'ERROR_REDUCED': 'indicator.error', - 'MOTION': 'sensor.motion', - 'PRESENCE_DETECTION_STATE': 'sensor.motion', - 'INHIBIT': 'state.inhibit', - 'VALVE_STATE': 'value.valve', - 'BRIGHTNESS': 'value.brightness', - 'ILLUMINATION': 'value.brightness', - 'RAMP_TIME': 'timer.ramp', - 'RAMP_STOP': 'button.stop', - 'STOP': 'button.stop', - 'ON_TIME': 'timer.off', - 'DUTYCYCLE': 'value', - 'DUTY_CYCLE': 'value', - 'COLOR': 'level.color.hue', - 'OPERATING_VOLTAGE': 'value.voltage', - 'OPERATING_VOLTAGE_STATUS': 'value', - 'SABOTAGE': 'indicator', - 'CURRENT': 'value.current', - 'ENERGY_COUNTER': 'value.power.consumption', - 'FREQUENCY': 'value.frequency', - 'POWER': 'value.power', - 'VOLTAGE': 'value.voltage', - 'ERROR_CODE': 'value' + dpNAME: { + BATTERY_STATE: 'value.voltage', + BOOST_STATE: 'level.boost', + BOOST_MODE: 'switch.mode.boost', + PARTY_MODE: 'switch.mode.party', + FROST_PROTECTION: 'indicator', + CONTROL_MODE: 'indicator', + FAULT_REPORTING: 'indicator', + MANU_MODE: 'level.temperature', + ACTUAL_TEMPERATURE: 'value.temperature', + ACTUAL_TEMPERATURE_STATUS: 'value', + TEMPERATURE: 'value.temperature', + SETPOINT: 'level.temperature', + SET_TEMPERATURE: 'level.temperature', + SET_POINT_TEMPERATURE: 'level.temperature', + SET_POINT_MODE: 'level.mode.thermostat', + HUMIDITY: 'value.humidity', + STATE: 'state', + PRESS_SHORT: 'button', + PRESS_LONG: 'button.long', + PRESS_LONG_RELEASE: 'button.release', + PRESS_CONT: 'button.continuous', + LEVEL: 'level', + LOWBAT: 'indicator.lowbat', + LOW_BAT: 'indicator.lowbat', + INSTALL_TEST: 'indicator', + UNREACH: 'indicator.unreach', + WINDOW_STATE: 'value.window', + WORKING: 'indicator.working', + DIRECTION: 'indicator.direction', + CONFIG_PENDING: 'indicator', + UPDATE_PENDING: 'indicator', + INSTALL_MODE: 'indicator', + RSSI_PEER: 'value.rssi', + RSSI_DEVICE: 'value.rssi', + AES_KEY: 'value', + STICKY_UNREACH: 'indicator.unreach', + ERROR: 'indicator.error', + ERROR_OVERHEAT: 'indicator.error.overheat', + ERROR_OVERLOAD: 'indicator.error.overload', + ERROR_REDUCED: 'indicator.error', + MOTION: 'sensor.motion', + PRESENCE_DETECTION_STATE: 'sensor.motion', + INHIBIT: 'state.inhibit', + VALVE_STATE: 'value.valve', + BRIGHTNESS: 'value.brightness', + ILLUMINATION: 'value.brightness', + RAMP_TIME: 'timer.ramp', + RAMP_STOP: 'button.stop', + STOP: 'button.stop', + ON_TIME: 'timer.off', + DUTYCYCLE: 'value', + DUTY_CYCLE: 'value', + COLOR: 'level.color.hue', + OPERATING_VOLTAGE: 'value.voltage', + OPERATING_VOLTAGE_STATUS: 'value', + SABOTAGE: 'indicator', + CURRENT: 'value.current', + ENERGY_COUNTER: 'value.power.consumption', + FREQUENCY: 'value.frequency', + POWER: 'value.power', + VOLTAGE: 'value.voltage', + ERROR_CODE: 'value' }, - 'dvTYPE': { + dvTYPE: { 'HM-Sec-SD': 'sensor.alarm.fire', 'HM-Sec-SD-2': 'sensor.alarm.fire', 'HM-Sec-SD-Team': 'sensor.alarm.fire', diff --git a/lib/tools.js b/lib/tools.js index 8d62c07c..1fc5173f 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -38,13 +38,13 @@ function replaceSpecialChars(text) { '-': '\x2D', '.': '\x2E', '/': '\x2F', - 'Ä': '\x5B', - 'Ö': '\x23', - 'Ü': '\x24', - 'ä': '\x7B', - 'ö': '\x7C', - 'ü': '\x7D', - 'ß': '\x5F', + Ä: '\x5B', + Ö: '\x23', + Ü: '\x24', + ä: '\x7B', + ö: '\x7C', + ü: '\x7D', + ß: '\x5F', ':': '\x3A', ';': '\x3B', '@': '\x40', diff --git a/main.js b/main.js index 4177dbde..43fb9be6 100644 --- a/main.js +++ b/main.js @@ -22,7 +22,7 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. -*/ + */ /* jshint -W097 */ /* jshint strict: false */ /* jslint node: true */ @@ -85,68 +85,68 @@ function combineEPaperCommand(lines, signal, ton, repeats, offset) { signal = number2hex(signal || '0xF0'); ton = number2hex(ton || '0xC0'); const substitutions = { - 'A': '0x41', - 'B': '0x42', - 'C': '0x43', - 'D': '0x44', - 'E': '0x45', - 'F': '0x46', - 'G': '0x47', - 'H': '0x48', - 'I': '0x49', - 'J': '0x4A', - 'K': '0x4B', - 'L': '0x4C', - 'M': '0x4D', - 'N': '0x4E', - 'O': '0x4F', - 'P': '0x50', - 'Q': '0x51', - 'R': '0x52', - 'S': '0x53', - 'T': '0x54', - 'U': '0x55', - 'V': '0x56', - 'W': '0x57', - 'X': '0x58', - 'Y': '0x59', - 'Z': '0x5A', - 'a': '0x61', - 'b': '0x62', - 'c': '0x63', - 'd': '0x64', - 'e': '0x65', - 'f': '0x66', - 'g': '0x67', - 'h': '0x68', - 'i': '0x69', - 'j': '0x6A', - 'k': '0x6B', - 'l': '0x6C', - 'm': '0x6D', - 'n': '0x6E', - 'o': '0x6F', - 'p': '0x70', - 'q': '0x71', - 'r': '0x72', - 's': '0x73', - 't': '0x74', - 'u': '0x75', - 'v': '0x76', - 'w': '0x77', - 'x': '0x78', - 'y': '0x79', - 'z': '0x7A', - '0': '0x30', - '1': '0x31', - '2': '0x32', - '3': '0x33', - '4': '0x34', - '5': '0x35', - '6': '0x36', - '7': '0x37', - '8': '0x38', - '9': '0x39', + A: '0x41', + B: '0x42', + C: '0x43', + D: '0x44', + E: '0x45', + F: '0x46', + G: '0x47', + H: '0x48', + I: '0x49', + J: '0x4A', + K: '0x4B', + L: '0x4C', + M: '0x4D', + N: '0x4E', + O: '0x4F', + P: '0x50', + Q: '0x51', + R: '0x52', + S: '0x53', + T: '0x54', + U: '0x55', + V: '0x56', + W: '0x57', + X: '0x58', + Y: '0x59', + Z: '0x5A', + a: '0x61', + b: '0x62', + c: '0x63', + d: '0x64', + e: '0x65', + f: '0x66', + g: '0x67', + h: '0x68', + i: '0x69', + j: '0x6A', + k: '0x6B', + l: '0x6C', + m: '0x6D', + n: '0x6E', + o: '0x6F', + p: '0x70', + q: '0x71', + r: '0x72', + s: '0x73', + t: '0x74', + u: '0x75', + v: '0x76', + w: '0x77', + x: '0x78', + y: '0x79', + z: '0x7A', + 0: '0x30', + 1: '0x31', + 2: '0x32', + 3: '0x33', + 4: '0x34', + 5: '0x35', + 6: '0x36', + 7: '0x37', + 8: '0x38', + 9: '0x39', ' ': '0x20', '!': '0x21', '"': '0x22', @@ -161,13 +161,13 @@ function combineEPaperCommand(lines, signal, ton, repeats, offset) { '-': '0x2D', '.': '0x2E', '/': '0x2F', - 'Ä': '0x5B', - 'Ö': '0x23', - 'Ü': '0x24', - 'ä': '0x7B', - 'ö': '0x7C', - 'ü': '0x7D', - 'ß': '0x5F', + Ä: '0x5B', + Ö: '0x23', + Ü: '0x24', + ä: '0x7B', + ö: '0x7C', + ü: '0x7D', + ß: '0x5F', ':': '0x3A', ';': '0x3B', '@': '0x40', @@ -180,13 +180,13 @@ function combineEPaperCommand(lines, signal, ton, repeats, offset) { const line = li.line.toString(); command = `${command},0x12`; let i; - if ((line.substring(0, 2) === '0x') && (line.length === 4)) { + if (line.substring(0, 2) === '0x' && line.length === 4) { command = `${command},${line}`; i = 12; } else { i = 0; } - while ((i < line.length) && (i < 12)) { + while (i < line.length && i < 12) { command += `,${substitutions[line[i]]}` || '0x2A'; i++; } @@ -250,7 +250,9 @@ async function controlEPaper(id, data) { adapter.log.warn(`Cannot setValue "${id}", because not connected.`); } } catch (e) { - adapter.log.error(`${adapter.config.type}rpc -> setValue ${JSON.stringify([`${tmp[2]}:${tmp[3]}`, tmp[4], val])}`); + adapter.log.error( + `${adapter.config.type}rpc -> setValue ${JSON.stringify([`${tmp[2]}:${tmp[3]}`, tmp[4], val])}` + ); adapter.log.error(`Cannot call setValue: ${e.message}`); } } @@ -265,75 +267,95 @@ async function readSignals(id) { const promises = []; - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE2`, (err, state) => { - data.lines[0].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON2`, (err, state) => { - data.lines[0].icon = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE3`, (err, state) => { - data.lines[1].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON3`, (err, state) => { - data.lines[1].icon = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE4`, (err, state) => { - data.lines[2].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON4`, (err, state) => { - data.lines[2].icon = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_SIGNAL`, (err, state) => { - data.signal = state ? state.val || '0xF0' : '0xF0'; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_TONE`, (err, state) => { - data.tone = state ? state.val || '0xC0' : '0xC0'; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_TONE_INTERVAL`, (err, state) => { - data.offset = state ? state.val : 10; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_TONE_REPETITIONS`, (err, state) => { - data.repeats = state ? state.val : 1; - resolve(); - }); - })); + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE2`, (err, state) => { + data.lines[0].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON2`, (err, state) => { + data.lines[0].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE3`, (err, state) => { + data.lines[1].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON3`, (err, state) => { + data.lines[1].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE4`, (err, state) => { + data.lines[2].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON4`, (err, state) => { + data.lines[2].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_SIGNAL`, (err, state) => { + data.signal = state ? state.val || '0xF0' : '0xF0'; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_TONE`, (err, state) => { + data.tone = state ? state.val || '0xC0' : '0xC0'; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_TONE_INTERVAL`, (err, state) => { + data.offset = state ? state.val : 10; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_TONE_REPETITIONS`, (err, state) => { + data.repeats = state ? state.val : 1; + resolve(); + }); + }) + ); await Promise.all(promises); controlEPaper(id, data); @@ -349,47 +371,59 @@ async function readSettings(id) { const promises = []; - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE2`, (err, state) => { - data.lines[0].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON2`, (err, state) => { - data.lines[0].icon = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE3`, (err, state) => { - data.lines[1].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON3`, (err, state) => { - data.lines[1].icon = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_LINE4`, (err, state) => { - data.lines[2].line = state ? state.val || '' : ''; - resolve(); - }); - })); - - promises.push(new Promise(resolve => { - adapter.getForeignState(`${id}.0.EPAPER_ICON4`, (err, state) => { - data.lines[2].icon = state ? state.val || '' : ''; - resolve(); - }); - })); + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE2`, (err, state) => { + data.lines[0].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON2`, (err, state) => { + data.lines[0].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE3`, (err, state) => { + data.lines[1].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON3`, (err, state) => { + data.lines[1].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_LINE4`, (err, state) => { + data.lines[2].line = state ? state.val || '' : ''; + resolve(); + }); + }) + ); + + promises.push( + new Promise(resolve => { + adapter.getForeignState(`${id}.0.EPAPER_ICON4`, (err, state) => { + data.lines[2].icon = state ? state.val || '' : ''; + resolve(); + }); + }) + ); await Promise.all(promises); controlEPaper(id, data); @@ -399,11 +433,12 @@ function startAdapter(options) { options = options || {}; Object.assign(options, { - name: adapterName, error: e => { if (e.code === 'EADDRNOTAVAIL') { - adapter.log.error(`Address ${adapter.config.adapterAddress} not available, maybe your HOST IP has changed due to migration`); + adapter.log.error( + `Address ${adapter.config.adapterAddress} not available, maybe your HOST IP has changed due to migration` + ); // doesn't work in that case, so let it correctly be handled by controller at least we can log // return true; } @@ -442,7 +477,7 @@ function startAdapter(options) { state.val = Math.round(state.val * 1000) / 1000; } else */ if (dpTypes[id].UNIT === '100%') { - state.val = Math.round(state.val / 100 * 1000) / 1000; + state.val = Math.round((state.val / 100) * 1000) / 1000; } const type = dpTypes[id].TYPE; @@ -472,18 +507,18 @@ function startAdapter(options) { if (displays[_id] && displays[_id].timer) { clearTimeout(displays[_id].timer); if (displays[_id].withTone) { - displays[_id] = {timer: setTimeout(readSignals, 300, _id), withTone: true}; + displays[_id] = { timer: setTimeout(readSignals, 300, _id), withTone: true }; return; } } - displays[_id] = {timer: setTimeout(readSettings, 300, _id), withTone: false}; + displays[_id] = { timer: setTimeout(readSettings, 300, _id), withTone: false }; return; } else if (type === 'EPAPER_SIGNAL' || type === 'EPAPER_TONE') { const _id = `${tmp[0]}.${tmp[1]}.${tmp[2]}`; if (displays[_id] && displays[_id].timer) { clearTimeout(displays[_id].timer); } - displays[_id] = {timer: setTimeout(readSignals, 300, _id), withTone: true}; + displays[_id] = { timer: setTimeout(readSignals, 300, _id), withTone: true }; return; } else if (tmp[4] === 'DISPLAY_DATA_STRING') { // new EPAPER HMIP-WRCD has own states but needs to encode special chars by DIN_66003 @@ -509,10 +544,10 @@ function startAdapter(options) { } else { switch (type) { case 'BOOL': - val = (state.val === 'false' || state.val === '0') ? false : !!state.val; + val = state.val === 'false' || state.val === '0' ? false : !!state.val; break; case 'FLOAT': - val = {explicitDouble: state.val}; + val = { explicitDouble: state.val }; break; default: val = state.val; @@ -528,7 +563,13 @@ function startAdapter(options) { adapter.log.warn(`Cannot setValue "${id}", because not connected.`); } } catch (e) { - adapter.log.error(`${adapter.config.type}rpc -> setValue ${JSON.stringify([`${tmp[2]}:${tmp[3]}`, tmp[4], state.val])} ${type}`); + adapter.log.error( + `${adapter.config.type}rpc -> setValue ${JSON.stringify([ + `${tmp[2]}:${tmp[3]}`, + tmp[4], + state.val + ])} ${type}` + ); adapter.log.error(`Cannot call setValue: ${e.message}`); } }, @@ -536,10 +577,17 @@ function startAdapter(options) { message: async obj => { adapter.log.debug(`[MSSG] Received: ${JSON.stringify(obj)}`); - if (obj.command === undefined || obj.command === null || obj.message === undefined || obj.message === null) { - adapter.log.warn(`Received invalid command via message "${obj.command}" "${obj.message}" from ${obj.from}`); + if ( + obj.command === undefined || + obj.command === null || + obj.message === undefined || + obj.message === null + ) { + adapter.log.warn( + `Received invalid command via message "${obj.command}" "${obj.message}" from ${obj.from}` + ); if (obj.callback) { - adapter.sendTo(obj.from, obj.command, {error: 'Invalid command'}, obj.callback); + adapter.sendTo(obj.from, obj.command, { error: 'Invalid command' }, obj.callback); } return; } @@ -548,42 +596,59 @@ function startAdapter(options) { try { if (rpcClient && connected) { // if device specific command, send it's ID and paramType - const data = await rpcMethodCallAsync(obj.command, obj.message.ID !== undefined ? [obj.message.ID, obj.message.paramType] : []); + const data = await rpcMethodCallAsync( + obj.command, + obj.message.ID !== undefined ? [obj.message.ID, obj.message.paramType] : [] + ); if (obj.callback) { - adapter.sendTo(obj.from, obj.command, { - result: data, - error: null - }, obj.callback); + adapter.sendTo( + obj.from, + obj.command, + { + result: data, + error: null + }, + obj.callback + ); } } else { adapter.log.warn(`Cannot send "${obj.command}" "${obj.message.ID}": because not connected`); if (obj.callback) { - adapter.sendTo(obj.from, obj.command, {error: 'not connected'}, obj.callback); + adapter.sendTo(obj.from, obj.command, { error: 'not connected' }, obj.callback); } } } catch (e) { adapter.log.error(`Cannot call ${obj.command}: ${e.message}`); - adapter.sendTo(obj.from, obj.command, {error: e}, obj.callback); + adapter.sendTo(obj.from, obj.command, { error: e }, obj.callback); } } else { try { if (rpcClient && connected) { - const data = await rpcMethodCallAsync(obj.command, [obj.message.ID, obj.message.paramType, obj.message.params]); + const data = await rpcMethodCallAsync(obj.command, [ + obj.message.ID, + obj.message.paramType, + obj.message.params + ]); if (obj.callback) { - adapter.sendTo(obj.from, obj.command, { - result: data, - error: null - }, obj.callback); + adapter.sendTo( + obj.from, + obj.command, + { + result: data, + error: null + }, + obj.callback + ); } } else { adapter.log.warn(`Cannot send "${obj.command}" "${obj.message.ID}": because not connected`); if (obj.callback) { - adapter.sendTo(obj.from, obj.command, {error: 'not connected'}, obj.callback); + adapter.sendTo(obj.from, obj.command, { error: 'not connected' }, obj.callback); } } } catch (e) { adapter.log.error(`Cannot call ${obj.command}: ${e.message}`); - adapter.sendTo(obj.from, obj.command, {error: e}, obj.callback); + adapter.sendTo(obj.from, obj.command, { error: e }, obj.callback); } } }, @@ -605,7 +670,11 @@ function startAdapter(options) { } if (adapter.config && rpcClient) { - adapter.log.info(`${adapter.config.type}rpc -> ${adapter.config.homematicAddress}:${adapter.config.homematicPort}${homematicPath} init ${JSON.stringify([daemonURL, ''])}`); + adapter.log.info( + `${adapter.config.type}rpc -> ${adapter.config.homematicAddress}:${ + adapter.config.homematicPort + }${homematicPath} init ${JSON.stringify([daemonURL, ''])}` + ); try { // tell CCU that we are no longer the client under this URL - legacy idk if necessary await rpcMethodCallAsync('init', [daemonURL, '']); @@ -650,7 +719,6 @@ function startAdapter(options) { } callback = null; } - } else { if (typeof callback === 'function') { callback(); @@ -763,7 +831,7 @@ async function main() { if (!obj || !obj.native) { adapter.log.warn(`State ${row.id} does not have native.`); - dpTypes[row.id] = {UNIT: '', TYPE: ''}; + dpTypes[row.id] = { UNIT: '', TYPE: '' }; } else { dpTypes[row.id] = { UNIT: obj.native.UNIT, @@ -813,7 +881,11 @@ async function main() { async function sendInit() { try { if (rpcClient && (rpcClient.connected === undefined || rpcClient.connected)) { - adapter.log.debug(`${adapter.config.type}rpc -> ${adapter.config.homematicAddress}:${adapter.config.homematicPort}${homematicPath} init ${JSON.stringify([daemonURL, clientId])}`); + adapter.log.debug( + `${adapter.config.type}rpc -> ${adapter.config.homematicAddress}:${ + adapter.config.homematicPort + }${homematicPath} init ${JSON.stringify([daemonURL, clientId])}` + ); await rpcMethodCallAsync('init', [daemonURL, clientId]); if (adapter.config.daemon === 'CUxD') { try { @@ -900,8 +972,14 @@ async function initRpcServer() { adapter.log.warn(`Could not get hostname, using default id "${clientId}" to register: ${e.message}`); } - adapter.log.info(`${adapter.config.type}rpc server is trying to listen on ${adapter.config.adapterAddress}:${port}`); - adapter.log.info(`${adapter.config.type}rpc client is trying to connect to ${adapter.config.homematicAddress}:${adapter.config.homematicPort}${homematicPath} with ${JSON.stringify([daemonURL, clientId])}`); + adapter.log.info( + `${adapter.config.type}rpc server is trying to listen on ${adapter.config.adapterAddress}:${port}` + ); + adapter.log.info( + `${adapter.config.type}rpc client is trying to connect to ${adapter.config.homematicAddress}:${ + adapter.config.homematicPort + }${homematicPath} with ${JSON.stringify([daemonURL, clientId])}` + ); connect(true); @@ -923,7 +1001,11 @@ async function initRpcServer() { await createDevices([res]); } else { - adapter.log.warn(`${adapter.config.type}rpc <- undefined method ${method} with parameters ${typeof params === 'object' ? JSON.stringify(params).slice(0, 80) : params}`); + adapter.log.warn( + `${adapter.config.type}rpc <- undefined method ${method} with parameters ${ + typeof params === 'object' ? JSON.stringify(params).slice(0, 80) : params + }` + ); } // endElse }); @@ -946,7 +1028,15 @@ async function initRpcServer() { adapter.log.warn(` Error on system.listMethods: ${err}`); } adapter.log.info(`${adapter.config.type}rpc <- system.listMethods ${JSON.stringify(params)}`); - callback(null, ['event', 'deleteDevices', 'listDevices', 'newDevices', 'system.listMethods', 'system.multicall', 'setReadyConfig']); + callback(null, [ + 'event', + 'deleteDevices', + 'listDevices', + 'newDevices', + 'system.listMethods', + 'system.multicall', + 'setReadyConfig' + ]); }); rpcServer.on('event', (err, params, callback) => { @@ -977,7 +1067,10 @@ async function initRpcServer() { // for a HmIP-adapter (and virtual-devices) we have to filter out the devices that // are already present if forceReinit is not set - if (adapter.config.forceReInit === false && (adapter.config.daemon === 'HMIP' || adapter.config.daemon === 'virtual-devices')) { + if ( + adapter.config.forceReInit === false && + (adapter.config.daemon === 'HMIP' || adapter.config.daemon === 'virtual-devices') + ) { let doc; try { doc = await adapter.getObjectViewAsync('hm-rpc', 'listDevices', { @@ -1022,7 +1115,11 @@ async function initRpcServer() { await adapter.deleteChannelAsync(parts[parts.length - 2], parts[parts.length - 1]); adapter.log.info(`obsolete channel ${address} ${JSON.stringify(address)} deleted`); } catch (e) { - adapter.log.error(`Could not delete obsolete channel ${address} ${JSON.stringify(address)}: ${e.message}`); + adapter.log.error( + `Could not delete obsolete channel ${address} ${JSON.stringify(address)}: ${ + e.message + }` + ); } } else { try { @@ -1077,7 +1174,7 @@ async function initRpcServer() { const val = row.value; if (val.ADDRESS) { - response.push({ADDRESS: val.ADDRESS, VERSION: val.VERSION}); + response.push({ ADDRESS: val.ADDRESS, VERSION: val.VERSION }); } } } @@ -1135,7 +1232,6 @@ async function initRpcServer() { } // endInitRPCServer const methods = { - event: (err, params) => { if (err) { adapter.log.error(`${adapter.config.type}rpc <- received error event: ${err}`); @@ -1163,7 +1259,8 @@ const methods = { if (dpTypes[name]) { // it shouldn't be necessary to scale on % values, see https://github.com/ioBroker/ioBroker.hm-rpc/issues/263 // backward compatibility -> max===1 unit===% - if (dpTypes[name].UNIT === '100%') {// || (dpTypes[name].UNIT === '%' && dpTypes[name].MAX === 1)) { + if (dpTypes[name].UNIT === '100%') { + // || (dpTypes[name].UNIT === '%' && dpTypes[name].MAX === 1)) { val = Math.round(params[3] * 1000) / 10; } else { val = params[3]; @@ -1174,9 +1271,13 @@ const methods = { adapter.log.debug(`${adapter.config.type}rpc <- event: ${name}:${params[3]} discarded, no matching device`); return ''; } - adapter.log.debug(`${name} ==> UNIT: "${dpTypes[name] ? dpTypes[name].UNIT : 'none'}" (min: ${dpTypes[name] ? dpTypes[name].MIN : 'none'}, max: ${dpTypes[name] ? dpTypes[name].MAX : 'none'}) From "${params[3]}" => "${val}"`); + adapter.log.debug( + `${name} ==> UNIT: "${dpTypes[name] ? dpTypes[name].UNIT : 'none'}" (min: ${ + dpTypes[name] ? dpTypes[name].MIN : 'none' + }, max: ${dpTypes[name] ? dpTypes[name].MAX : 'none'}) From "${params[3]}" => "${val}"` + ); - adapter.setState(`${channel}.${params[2]}`, {val: val, ack: true}); + adapter.setState(`${channel}.${params[2]}`, { val: val, ack: true }); // unfortunately this is necessary return ''; } @@ -1218,9 +1319,13 @@ async function addParamsetObjects(channel, paramset) { native: paramset[key] }; + if (typeof obj.common.def === 'string' && obj.common.type === 'number') { + obj.common.def = parseFloat(obj.common.def); + } + if (obj.common.type === 'number') { - obj.common.min = paramset[key].MIN; - obj.common.max = paramset[key].MAX; + obj.common.min = typeof paramset[key].MIN === 'string' ? parseFloat(paramset[key].MIN) : paramset[key].MIN; + obj.common.max = typeof paramset[key].MAX === 'string' ? parseFloat(paramset[key].MAX) : paramset[key].MAX; if (paramset[key].TYPE === 'ENUM') { obj.common.states = {}; @@ -1244,7 +1349,12 @@ async function addParamsetObjects(channel, paramset) { } // temporary fix for https://github.com/eq-3/occu/issues/105 and LEVEL w. o. % - if (key === 'LEVEL' && typeof paramset[key].MIN === 'number' && typeof paramset[key].MAX === 'number' && paramset[key].UNIT === undefined) { + if ( + key === 'LEVEL' && + typeof paramset[key].MIN === 'number' && + typeof paramset[key].MAX === 'number' && + paramset[key].UNIT === undefined + ) { paramset[key].UNIT = '%'; } // endIf @@ -1369,7 +1479,9 @@ async function getValueParamsets(valueParamsets) { addEPaperToMeta(); } - adapter.log.info(`${adapter.config.type}rpc -> getParamsetDescription ${JSON.stringify([obj.native.ADDRESS, 'VALUES'])}`); + adapter.log.info( + `${adapter.config.type}rpc -> getParamsetDescription ${JSON.stringify([obj.native.ADDRESS, 'VALUES'])}` + ); metaValues[cid] = await rpcMethodCallAsync('getParamsetDescription', [obj.native.ADDRESS, 'VALUES']); if (obj.native && obj.native.PARENT_TYPE === 'HM-Dis-EP-WM55' && obj.native.TYPE === 'MAINTENANCE') { @@ -1394,7 +1506,9 @@ async function getValueParamsets(valueParamsets) { if (adapter.config.forceReInit) { adapter.log.info('Restarting now, because we had a forced reinitialization run'); try { - await adapter.extendForeignObjectAsync(`system.adapter.${adapter.namespace}`, {native: {forceReInit: false}}); + await adapter.extendForeignObjectAsync(`system.adapter.${adapter.namespace}`, { + native: { forceReInit: false } + }); } catch (e) { adapter.log.error(`Could not restart and set forceReinit to false: ${e.message}`); } @@ -1546,14 +1660,17 @@ async function createDevices(deviceArr) { if (device.PARENT) { type = 'channel'; - role = metaRoles.chTYPE && metaRoles.chTYPE[device.TYPE] ? metaRoles.chTYPE && metaRoles.chTYPE[device.TYPE] : undefined; + role = + metaRoles.chTYPE && metaRoles.chTYPE[device.TYPE] + ? metaRoles.chTYPE && metaRoles.chTYPE[device.TYPE] + : undefined; } else { type = 'device'; if (!images[device.TYPE]) { adapter.log.warn(`No image for "${device.TYPE}" found.`); } - icon = images[device.TYPE] ? (`/icons/${images[device.TYPE]}`) : ''; + icon = images[device.TYPE] ? `/icons/${images[device.TYPE]}` : ''; } const obj = { @@ -1716,7 +1833,7 @@ function updateConnection() { // Virtual Devices API does now also support PING (tested with 3.55.5.20201226 - see #308) if (!eventInterval) { adapter.log.debug('start ping interval'); - eventInterval = setInterval(keepAlive, adapter.config.checkInitInterval * 1000 / 2); + eventInterval = setInterval(keepAlive, (adapter.config.checkInitInterval * 1000) / 2); } } @@ -1759,7 +1876,7 @@ function connect(isFirst) { port: adapter.config.homematicPort, path: homematicPath, reconnectTimeout: adapter.config.reconnectInterval * 1000, - basic_auth: {user: username, pass: password}, + basic_auth: { user: username, pass: password }, rejectUnauthorized: false }); } catch (e) { @@ -1810,7 +1927,7 @@ function keepAlive() { const _now = Date.now(); // Check last event time. If timeout => send init again - if (!lastEvent || (_now - lastEvent) >= adapter.config.checkInitInterval * 1000) { + if (!lastEvent || _now - lastEvent >= adapter.config.checkInitInterval * 1000) { adapter.log.debug('[KEEPALIVE] Connection timed out, initializing new connection'); connect(); } else { diff --git a/package-lock.json b/package-lock.json index 70d60a95..62400896 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iobroker.hm-rpc", - "version": "1.15.0", + "version": "1.15.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -318,6 +318,32 @@ } } }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@sinonjs/commons": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", @@ -361,11 +387,225 @@ "@types/node": "*" } }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, "@types/node": { "version": "16.4.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.1.tgz", "integrity": "sha512-UW7cbLqf/Wu5XH2RKKY1cHwUNLicIDRLMraYKz+HHAerJ0ZffUEk+fMnd8qU2JaS6cAy0r8tsaf7yqHASf/Y0Q==" }, + "@typescript-eslint/eslint-plugin": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.10.2.tgz", + "integrity": "sha512-4W/9lLuE+v27O/oe7hXJKjNtBLnZE8tQAFpapdxwSVHqtmIoPB1gph3+ahNwVuNL37BX7YQHyGF9Xv6XCnIX2Q==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.10.2", + "@typescript-eslint/type-utils": "5.10.2", + "@typescript-eslint/utils": "5.10.2", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.10.2.tgz", + "integrity": "sha512-JaNYGkaQVhP6HNF+lkdOr2cAs2wdSZBoalE22uYWq8IEv/OVH0RksSGydk+sW8cLoSeYmC+OHvRyv2i4AQ7Czg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.10.2", + "@typescript-eslint/types": "5.10.2", + "@typescript-eslint/typescript-estree": "5.10.2", + "debug": "^4.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz", + "integrity": "sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.10.2", + "@typescript-eslint/visitor-keys": "5.10.2" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.10.2.tgz", + "integrity": "sha512-uRKSvw/Ccs5FYEoXW04Z5VfzF2iiZcx8Fu7DGIB7RHozuP0VbKNzP1KfZkHBTM75pCpsWxIthEH1B33dmGBKHw==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.10.2", + "debug": "^4.3.2", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@typescript-eslint/types": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.10.2.tgz", + "integrity": "sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz", + "integrity": "sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.10.2", + "@typescript-eslint/visitor-keys": "5.10.2", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.10.2.tgz", + "integrity": "sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.10.2", + "@typescript-eslint/types": "5.10.2", + "@typescript-eslint/typescript-estree": "5.10.2", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.10.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz", + "integrity": "sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.10.2", + "eslint-visitor-keys": "^3.0.0" + } + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -590,6 +830,12 @@ } } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -1422,6 +1668,23 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1665,6 +1928,21 @@ } } }, + "eslint-config-prettier": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", + "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", + "dev": true + }, + "eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", @@ -2015,6 +2293,37 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -2027,6 +2336,15 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2590,6 +2908,20 @@ "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", "dev": true }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, "globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -3533,6 +3865,12 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4297,6 +4635,21 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -4363,6 +4716,12 @@ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4641,6 +5000,12 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4650,6 +5015,15 @@ "glob": "^7.1.3" } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -4828,6 +5202,12 @@ "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -5322,6 +5702,21 @@ "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5380,6 +5775,12 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", + "dev": true + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", diff --git a/package.json b/package.json index 18917f00..bc441705 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.hm-rpc", - "version": "1.15.0", + "version": "1.15.1", "description": "Connects HomeMatic Interface-Processes (BidCos-Services, Homegear and CUxD) via XML-RPC or BIN-RPC to ioBroker", "author": { "name": "hobbyquaker", @@ -42,20 +42,35 @@ "homematic-xmlrpc": "^1.0.2" }, "devDependencies": { - "@alcalzone/release-script": "^3.4.1", - "@alcalzone/release-script-plugin-iobroker": "^3.4.1", - "@alcalzone/release-script-plugin-license": "^3.4.1", + "@alcalzone/release-script": "^3.5.0", + "@alcalzone/release-script-plugin-iobroker": "^3.5.0", + "@alcalzone/release-script-plugin-license": "^3.5.0", "@iobroker/testing": "^2.5.2", + "@typescript-eslint/eslint-plugin": "^5.10.2", + "@typescript-eslint/parser": "^5.10.2", "axios": "^0.25.0", "chai": "^4.3.4", - "eslint": "^8.4.1", + "eslint": "^8.8.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", "gulp": "^4.0.2", "hm-simulator": "^0.1.1", - "mocha": "^9.1.3" + "mocha": "^9.1.3", + "prettier": "^2.5.1", + "typescript": "^4.5.5" }, "bugs": { "url": "https://github.com/ioBroker/ioBroker.hm-rpc/issues" }, + "files": [ + "admin{,/!(src)/**}/!(tsconfig|tsconfig.*).json", + "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}", + "main.js", + "lib/", + "www/", + "io-package.json", + "LICENSE" + ], "main": "main.js", "scripts": { "release": "release-script", diff --git a/test/integrationAdapter.js b/test/integrationAdapter.js index e689116f..7211a840 100644 --- a/test/integrationAdapter.js +++ b/test/integrationAdapter.js @@ -1,14 +1,16 @@ 'use strict'; const path = require('path'); -const {tests} = require('@iobroker/testing'); +const { tests } = require('@iobroker/testing'); // TODO: activate after https://github.com/hobbyquaker/hm-simulator/pull/1 // const hmSim = require('hm-simulator'); // Run tests tests.integration(path.join(__dirname, '..'), { defineAdditionalTests(getHarness) { + // eslint-disable-next-line no-undef describe('Test sendTo()', () => { + // eslint-disable-next-line no-undef it('Should work', async () => { // Create a fresh harness instance each test! const harness = getHarness(); diff --git a/test/packageFiles.js b/test/packageFiles.js index 9806de35..6523fc0d 100644 --- a/test/packageFiles.js +++ b/test/packageFiles.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const {tests} = require('@iobroker/testing'); +const { tests } = require('@iobroker/testing'); // Run tests -tests.packageFiles(path.join(__dirname, '..')); \ No newline at end of file +tests.packageFiles(path.join(__dirname, '..')); diff --git a/test/unitAdapter.js b/test/unitAdapter.js index 3d9f90cb..c27d1cf8 100644 --- a/test/unitAdapter.js +++ b/test/unitAdapter.js @@ -1,10 +1,10 @@ -'use strict'; -const path = require('path'); -const {tests} = require('@iobroker/testing'); - -// Run tests -tests.unit(path.join(__dirname, '..'), { - defineMockBehavior(db, adapter) { - adapter.getObjectView.returns({rows: []}); - } -}); +'use strict'; +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Run tests +tests.unit(path.join(__dirname, '..'), { + defineMockBehavior(db, adapter) { + adapter.getObjectView.returns({ rows: [] }); + } +}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..c768affa --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +// Specialized tsconfig to only compile .ts-files in the src dir +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false, + "noEmit": false, + "declaration": false + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.test.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..78dc7251 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,51 @@ +// Root tsconfig to set the settings and power editor support for all TS files +{ + "compileOnSave": true, + "compilerOptions": { + // do not compile anything, this file is just to configure type checking + // the compilation is configured in tsconfig.build.json + "noEmit": true, + + // check JS files, but do not compile them => tsconfig.build.json + "allowJs": true, + "checkJs": true, + + "skipLibCheck": true, // Don't report errors in 3rd party definitions + "noEmitOnError": true, + "outDir": "./build/", + "removeComments": false, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + // this is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, + + // Set this to false if you want to disable the very strict rules (not recommended) + "strict": true, + // Or enable some of those features for more fine-grained control + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + // "noImplicitAny": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // Uncomment this if you want the old behavior of catch variables being `any` + // "useUnknownInCatchVariables": false, + + // Consider targetting es2019 or higher if you only support Node.js 12+ + "target": "es2018", + + "sourceMap": true, + "inlineSourceMap": false, + "watch": false + }, + "include": [ + "src/**/*.ts", + "admin/**/*.ts", + "admin/**/*.tsx" + ], + "exclude": [ + "build/**", + "node_modules/**" + ] +}