diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index 3fc8bcd..19e85dd 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: - node-version: [18.x] + node-version: [20.x] steps: - uses: actions/checkout@v3 @@ -56,7 +56,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - node-version: [18.x, 20.x,22.x] + node-version: [20.x,22.x] os: [ubuntu-latest, windows-latest, macos-latest] steps: @@ -92,7 +92,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x] + node-version: [20.x] steps: - name: Checkout code diff --git a/LICENSE b/LICENSE index f0cb55c..7717ecc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2023 jack-blackson blacksonj7@gmail.com +Copyright (c) 2019-2024 jack-blackson blacksonj7@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9a3c046..d6e20fb 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,11 @@ The goal of the adapter is to provide you a possibility to run countdowns for fu ## Changelog +### 2.3.0 (2024-09-20) +* (jack-blackson) Compatibility for js-controller 7 +* (jack-blackson/bagsik) Added new object fullJSON with all objects included - thanks to bagsik who had the idea and created the code! + + ### 2.2.1 (2024-09-14) * (jack-blackson) Additional check to avoid not allowed signs in countdown name * (jack-blackson) Updated dependencies @@ -145,7 +150,7 @@ The goal of the adapter is to provide you a possibility to run countdowns for fu ## License The MIT License (MIT) -Copyright (c) 2019-2023 jack-blackson +Copyright (c) 2019-2024 jack-blackson 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/docs/de/countdown.md b/docs/de/countdown.md index c38c209..4bc7c5c 100755 --- a/docs/de/countdown.md +++ b/docs/de/countdown.md @@ -64,4 +64,7 @@ sendTo("countdown.0", "send", { |totalYears|Gesamtanzahl Anzahl Jahre bis zum Enddatum| |reached|Boolean Feld, das definiert, ob das Enddatum erreicht wurde oder nicht| -|repeatEvery|Countdown wird nach Erreichen des Enddatums um diesen Zeitraum wiederholt| \ No newline at end of file +|repeatEvery|Countdown wird nach Erreichen des Enddatums um diesen Zeitraum wiederholt| + +|totalsJson|JSON mit Objekten für die Summe der Stunden, Tage, Wochen, Monate und Jahre| +|fullJson|JSON mit allen Objekten| diff --git a/docs/en/countdown.md b/docs/en/countdown.md index d5e6baf..0d21c60 100755 --- a/docs/en/countdown.md +++ b/docs/en/countdown.md @@ -65,3 +65,6 @@ sendTo("countdown.0", "send", { |reached|Boolean field defining if the end date was reached or not| |repeatEvery|Countdown is repeted by this period after reaching the enddate| + +|totalsJson|JSON with the total objects for hours, days, weeks, months and years| +|fullJson|JSON with all objects| diff --git a/io-package.json b/io-package.json index 35f521e..0dff5f2 100644 --- a/io-package.json +++ b/io-package.json @@ -1,8 +1,21 @@ { "common": { "name": "countdown", - "version": "2.2.1", + "version": "2.3.0", "news": { + "2.3.0": { + "en": "Compatibility for js-controller 7\n", + "de": "Kompatibilität für js-controller 7\n", + "ru": "Совместимость js-контроллера 7\n", + "pt": "Compatibilidade para js-controller 7\n", + "nl": "Compatibiliteit voor js-controller 7\n", + "fr": "Compatibilité pour js-controller 7\n", + "it": "Compatibilità per js-controller 7\n", + "es": "Compatibilidad para js-controller 7\n", + "pl": "Zgodność dla kontrolera js- 7\n", + "uk": "Сумісність для js-controller 7 хв\n", + "zh-cn": "Js 控制器的兼容性 页:1\n" + }, "2.2.1": { "en": "Additional check to avoid not allowed signs in countdown name", "de": "Zusätzliche Überprüfung nicht erlaubt Zeichen im Countdown-Namen zu vermeiden", diff --git a/lib/stateAttr.js b/lib/stateAttr.js new file mode 100644 index 0000000..2479778 --- /dev/null +++ b/lib/stateAttr.js @@ -0,0 +1,99 @@ +const stateAttrb = { + 'name' : { + name: 'Name', + type: 'string', + role: 'value', + }, + 'reached' : { + name: 'Reached', + type: 'boolean', + role: 'value', + }, + 'countUp' : { + name: 'CountUp', + type: 'boolean', + role: 'value', + }, + 'years' : { + name: 'Years', + type: 'number', + role: 'value', + }, + 'months' : { + name: 'Months', + type: 'number', + role: 'value', + }, + 'days' : { + name: 'Days', + type: 'number', + role: 'value', + }, + 'hours' : { + name: 'Hours', + type: 'number', + role: 'value', + }, + 'minutes' : { + name: 'Minutes', + type: 'number', + role: 'value', + }, + 'totalDays' : { + name: 'Total no. of days', + type: 'number', + role: 'value', + }, + 'totalHours' : { + name: 'Total no. of hours', + type: 'number', + role: 'value', + }, + 'totalWeeks' : { + name: 'Total no. of weeks', + type: 'number', + role: 'value', + }, + 'totalMonths' : { + name: 'Total no. of months', + type: 'number', + role: 'value', + }, + 'totalYears' : { + name: 'Total no. of years', + type: 'number', + role: 'value', + }, + 'inWordsLong' : { + name: 'Result in words long', + type: 'string', + role: 'value', + }, + 'inWordsShort' : { + name: 'Result in words short', + type: 'string', + role: 'value', + }, + 'totalJson' : { + name: 'Totals as JSON', + type: 'string', + role: 'value', + }, + 'fullJson' : { + name: 'All values as JSON', + type: 'string', + role: 'value', + }, + 'endDate' : { + name: 'Enddate', + type: 'string', + role: 'value', + }, + 'repeatEvery' : { + name: 'Period when the Countdown should be repeated', + type: 'string', + role: 'value', + } +}; + +module.exports = stateAttrb; diff --git a/main.js b/main.js index d0604b9..830e8d4 100644 --- a/main.js +++ b/main.js @@ -13,6 +13,8 @@ const translateJS = require('./translate.js'); const a = new translateJS(); const utils = require('@iobroker/adapter-core'); +const stateAttr = require('./lib/stateAttr.js'); // State attribute definitions + const moment = require('moment'); const tableify = require(`tableify`); var AdapterStarted; @@ -21,6 +23,9 @@ var translateObject = {} var storagename = '' var countdownData +const warnMessages = {}; + + let objects = null; @@ -98,12 +103,8 @@ async function processCountdowns(){ let cleanedAtAtart = await deleteAllCountdowns() AdapterStarted = true } - - - - adapter.log.debug('1 Loop setup') - + let loop = await loopsetup() adapter.log.debug('1.9 after loop setup') @@ -114,7 +115,7 @@ async function processCountdowns(){ let updated = await updateCountdownTable() adapter.log.debug('3.2 Updated HTML and JSON table') - + adapter.log.debug('4. Done') //adapter.terminate ? adapter.terminate('5. Stopping adapter') : process.exit(0); @@ -124,9 +125,8 @@ async function processCountdowns(){ async function deleteAllCountdowns(){ const promises = await Promise.all([ - adapter.deleteDeviceAsync('countdowns') - //adapter.deleteChannelAsync('tomorrow'), - //adapter.deleteStateAsync('weatherMapCountry') + //adapter.deleteDeviceAsync('countdowns') + adapter.delObjectAsync('countdowns', { recursive: true }) ]) @@ -426,6 +426,7 @@ async function createCountdownData(CountName, CountDate){ adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalMonths'}, {val: 0, ack: true}); adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalYears'}, {val: 0, ack: true}); adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalJson'}, {val: '', ack: true}); + adapter.setState({device: 'countdowns' , channel: storagename, state: 'fullJson'}, {val: '', ack: true}); @@ -562,6 +563,7 @@ async function createCountdownData(CountName, CountDate){ async function updateObjects(years,months,days,hours,minutes,CountDowninWordsShort,CountDowninWordsLong,totalDays,totalHours,totalWeeks,totalMonths, totalYears, repeatCycle,countUp){ var totalJsonData = []; + var tempTable = {} tempTable[translateObject.textYears] = totalYears @@ -571,8 +573,31 @@ async function updateObjects(years,months,days,hours,minutes,CountDowninWordsSho tempTable[translateObject.textHours] = totalHours totalJsonData.push(tempTable) - var totalJson = JSON.stringify(totalJsonData) + + + + var fullJsonData = {'years': years, + 'months': months, + 'days': days, + 'hours': hours, + 'minutes': minutes, + 'inWords': { + 'short': CountDowninWordsShort, + 'long': CountDowninWordsLong + }, + 'total': { + 'days': totalDays, + 'hours': totalHours, + 'weeks': totalWeeks, + 'months': totalMonths, + 'years': totalYears + }, + 'repeatCycle': repeatCycle, + 'countUp': countUp + } + var fullJson = JSON.stringify(fullJsonData) + adapter.log.debug('TOTAL YEARS1: ' + totalYears) const promises = await Promise.all([ @@ -590,6 +615,7 @@ async function updateObjects(years,months,days,hours,minutes,CountDowninWordsSho adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalMonths'}, {val: totalMonths, ack: true}), adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalYears'}, {val: totalYears, ack: true}), adapter.setState({device: 'countdowns' , channel: storagename, state: 'totalJson'}, {val: totalJson, ack: true}), + adapter.setState({device: 'countdowns' , channel: storagename, state: 'fullJson'}, {val: fullJson, ack: true}), adapter.setState({device: 'countdowns' , channel: storagename, state: 'repeatEvery'}, {val: repeatCycle, ack: true}), adapter.setState({device: 'countdowns' , channel: storagename, state: 'countUp'}, {val: countUp, ack: true}) ]) @@ -1049,169 +1075,28 @@ async function createObjects(CountName){ 'native' : {} }), - adapter.createStateAsync('countdowns', CountName, 'name', { - read: true, - write: true, - name: "Name", - type: 'string', - def: CountName, - role: 'value' - }), - - - adapter.createStateAsync('countdowns', CountName, 'reached', { - read: true, - write: true, - name: "Reached", - type: "boolean", - def: false, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'countUp', { - read: true, - write: true, - name: "CountUp", - type: "boolean", - def: false, - role: 'value' -}), - - adapter.createStateAsync('countdowns', CountName, 'years', { - read: true, - write: true, - name: "Years", - type: "number", - def: 0, - role: 'value' - - }), - - adapter.createStateAsync('countdowns', CountName, 'months', { - read: true, - write: true, - name: "Months", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'days', { - read: true, - write: true, - name: "Days", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'hours', { - read: true, - write: true, - name: "Hours", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'minutes', { - read: true, - write: true, - name: "Minutes", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'inWordsLong', { - read: true, - write: true, - name: "Result in Words Long", - type: "string", - def: '', - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'inWordsShort', { - read: true, - write: true, - name: "Result in Words Short", - type: "string", - def: '', - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'endDate', { - read: true, - write: true, - name: "Enddate", - type: "string", - def: '', - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'totalDays', { - read: true, - write: true, - name: "Total no. of days", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'totalHours', { - read: true, - write: true, - name: "Total no. of hours", - type: "number", - def: 0, - role: 'value' - }), - - adapter.createStateAsync('countdowns', CountName, 'totalWeeks', { - read: true, - write: true, - name: "Total no. of weeks", - type: "number", - def: 0, - role: 'value' - }), - adapter.createStateAsync('countdowns', CountName, 'totalMonths', { - read: true, - write: true, - name: "Total no. of months", - type: "number", - def: 0, - role: 'value' - }), - adapter.createStateAsync('countdowns', CountName, 'totalYears', { - read: true, - write: true, - name: "Total no. of years", - type: "number", - def: 0, - role: 'value' - }), - adapter.createStateAsync('countdowns', CountName, 'totalJson', { - read: true, - write: true, - name: "Totals as JSON", - type: "string", - def: '', - role: 'json' - }), - - adapter.createStateAsync('countdowns', CountName, 'repeatEvery', { - read: true, - write: true, - name: "Period when the Countdown should be repeated", - type: "string", - def: '', - role: 'value' - }) + await localCreateState('countdowns' + '.' + CountName + '.name', 'name', CountName), + await localCreateState('countdowns' + '.' + CountName + '.reached', 'reached', false), + await localCreateState('countdowns' + '.' + CountName + '.countUp', 'countUp', false), + await localCreateState('countdowns' + '.' + CountName + '.years', 'years', 0), + await localCreateState('countdowns' + '.' + CountName + '.months', 'months', 0), + await localCreateState('countdowns' + '.' + CountName + '.days', 'days', 0), + await localCreateState('countdowns' + '.' + CountName + '.hours', 'hours', 0), + await localCreateState('countdowns' + '.' + CountName + '.minutes', 'minutes', 0), + await localCreateState('countdowns' + '.' + CountName + '.inWordsLong', 'inWordsLong', ''), + await localCreateState('countdowns' + '.' + CountName + '.inWordsShort', 'inWordsShort', ''), + await localCreateState('countdowns' + '.' + CountName + '.endDate', 'endDate', ''), + await localCreateState('countdowns' + '.' + CountName + '.totalDays', 'totalDays', 0), + await localCreateState('countdowns' + '.' + CountName + '.totalHours', 'totalHours', 0), + await localCreateState('countdowns' + '.' + CountName + '.totalWeeks', 'totalWeeks', 0), + await localCreateState('countdowns' + '.' + CountName + '.totalMonths', 'totalMonths', 0), + await localCreateState('countdowns' + '.' + CountName + '.totalYears', 'totalYears', 0), + await localCreateState('countdowns' + '.' + CountName + '.totalJson', 'totalJson', ''), + await localCreateState('countdowns' + '.' + CountName + '.fullJson', 'fullJson', ''), + await localCreateState('countdowns' + '.' + CountName + '.repeatEvery', 'repeatEvery', ''), ]) - //adapter.log.info('all states created') + adapter.log.debug('1.3.4: All states created') } @@ -1227,6 +1112,71 @@ function countProperties(obj) { return count; } +async function localCreateState(state, name, value) { + //adapter.log.debug(`Create_state called for : ${state} with value : ${value}`); + + try { + // Try to get details from state lib, if not use defaults. throw warning if states is not known in attribute list + if (stateAttr[name] === undefined) { + const warnMessage = `State attribute definition missing for ${name}`; + if (warnMessages[name] !== warnMessage) { + warnMessages[name] = warnMessage; + adapter.log.warn(`State attribute definition missing for ${name}`); + } + } + const writable = stateAttr[name] !== undefined ? stateAttr[name].write || false : false; + const state_name = stateAttr[name] !== undefined ? stateAttr[name].name || name : name; + const role = stateAttr[name] !== undefined ? stateAttr[name].role || 'state' : 'state'; + const type = stateAttr[name] !== undefined ? stateAttr[name].type || 'mixed' : 'mixed'; + const unit = stateAttr[name] !== undefined ? stateAttr[name].unit || '' : ''; + //adapter.log.debug(`Write value : ${writable}`); + + await adapter.setObjectNotExistsAsync(state, { + type: 'state', + common: { + name: state_name, + role: role, + type: type, + unit: unit, + read: true, + write: writable + }, + native: {}, + }); + + // Ensure name changes are propagated + await adapter.extendObjectAsync(state, { + type: 'state', + common: { + name: state_name, + type: type, // Also update types t solve log error's and attribute changes + }, + }); + + // Only set value if input != null + if (value !== null) { + await adapter.setState(state, {val: value, ack: true}); + } + + // Subscribe on state changes if writable + // writable && this.subscribeStates(state); + } catch (error) { + errorHandling('localCreateState', error); + } +} + +function errorHandling(codePart, error, suppressFrontendLog) { + if (!suppressFrontendLog) { + adapter.log.error(`[${codePart}] error: ${error.message}, stack: ${error.stack}`); + } + if (adapter.supportsFeature && adapter.supportsFeature('PLUGINS')) { + const sentryInstance = adapter.getPluginInstance('sentry'); + if (sentryInstance) { + sentryInstance.getSentryObject().captureException(error); + } + } +} + function mydiff(date1,date2,interval) { var second=1000, minute=second*60, hour=minute*60, day=hour*24, week=day*7; date1 = new Date(date1); diff --git a/package-lock.json b/package-lock.json index 06b1fca..4e5321a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iobroker.countdown", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iobroker.countdown", - "version": "2.2.1", + "version": "2.3.0", "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.1.6", @@ -16,7 +16,7 @@ }, "devDependencies": { "@iobroker/adapter-dev": "^1.2.0", - "@iobroker/testing": "5.0.0", + "@iobroker/testing": "^4.1.3", "axios": ">=0.21.1", "chai": "^4.2.0", "gulp": "^4.0.2", diff --git a/package.json b/package.json index eb73a92..09fdb8d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.countdown", - "version": "2.2.1", + "version": "2.3.0", "engines": { "node": ">=18.0.0" }, @@ -39,7 +39,7 @@ }, "devDependencies": { "@iobroker/adapter-dev": "^1.2.0", - "@iobroker/testing":"4.1.3", + "@iobroker/testing":"^4.1.3", "axios": ">=0.21.1", "chai": "^4.2.0", "gulp": "^4.0.2",