UNPKG

iobroker.coronavirus-statistics

Version:

Adapter to show Global Corona Virus information and current reports

1,055 lines (899 loc) 42.4 kB
'use strict'; const HospitalService = require('./lib/services/hospital.service'); const VaccinationService = require('./lib/services/vaccination.service'); const HospitalIndexService = require('./lib/services/hospital-index.service'); // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const utils = require('@iobroker/adapter-core'); const {default: axios} = require('axios'); const adapterName = require('./package.json').name.split('.').pop(); const stateAttr = require('./lib/stateAttr.js'); // State attribute definitions const {wait} = require('./lib/tools'); const countryJs = require('country-list-js'); const allCountrys = []; // Array for all countrys to store in object const warnMessages = {}; // For Germany, arrays to store federal states, city and counties to store in object let allGermanyFederalStates = [], allGermanCountyDetails = [], allGermanyCounties = [], allGermanyCities = []; let allGermanyFederalStatesLoaded = null, allGermanyCountiesLoaded = null, allGermanyCitiesLoaded = null; let vaccinationData$; let germanHospitalData$; let germanHospitalIndexData$; // Translator if country names are not iso conform const countryTranslator = require('./lib/countryTranslator'); const {allSpaces, allPointAndCommas, modifyFloatRegex, americaRegex} = require('./lib/regex'); class Covid19 extends utils.Adapter { /** * @param {Partial<ioBroker.AdapterOptions>} [options={}] */ constructor(options) { // @ts-ignore super({ ...options, name: adapterName || 'coronavirus-statistics', }); this.on('ready', this.onReady.bind(this)); } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { try { // Needed to solve incorrect object typ for existing states (normally created during adapter install) // Fixes issue #340 & #347 await this.extendObjectAsync(`countryTranslator`, { type: 'state', common: { 'role': '', 'name': 'Translator for unknown country names', 'type': 'number', 'read': true, 'write': true, 'def': 0 }, 'native': {} }); // Load configuration const selectedCountries = this.config.countries || []; const selectedGermanyFederalStates = this.config.selectedGermanyFederalStates || []; const selectedGermanyCities = this.config.selectedGermanyCities || []; const selectedGermanyCounties = this.config.selectedGermanyCounties || []; this.log.debug(`Configuration object before config load : ${JSON.stringify(this.config)}`); // Determine if routine must run to get data for tables const loadedArrays = await this.getObjectAsync(`${this.namespace}.countryTranslator`); if (!loadedArrays) { allGermanyCitiesLoaded = false; } else { allGermanyCitiesLoaded = loadedArrays.native.allGermanyCities || false; allGermanyCountiesLoaded = loadedArrays.native.allGermanyCounties || false; allGermanyFederalStatesLoaded = loadedArrays.native.allGermanyFederalStates || false; } const loadAll = async () => { // Try to call API and get global information let apiResult = null; try { // Try to reach API and receive data apiResult = await axios.get('https://disease.sh/v3/covid-19/all'); } catch (error) { this.log.warn(`[loadAll] Unable to contact COVID-19 API : ${error}`); return; } this.log.debug(`Data from COVID-19 API received : ${apiResult.data}`); const values = apiResult.data; await this.extendObjectAsync(`global_totals`, { type: 'device', common: { name: 'Total values of all countries togehter', }, native: {}, }); for (const i of Object.keys(values)) { await this.localCreateState(`global_totals.${i}`, i, values[i]); } }; const loadCountries = async () => { try { let apiResult = null; const continentsStats = {}; continentsStats['America'] = {}; continentsStats['World_Sum'] = {}; // Try to call API and get country information try { apiResult = await axios.get('https://disease.sh/v3/covid-19/countries?sort=cases') .then(response => response.data); this.log.debug(`Data from COVID-19 API received : ${JSON.stringify(apiResult)}`); this.log.debug(`load all country's : ${this.config.loadAllCountrys} as ${typeof this.config.loadAllCountrys}`); } catch (error) { this.log.warn(`[loadCountries] Unable to contact COVID-19 API : ${error}`); return; } // Write all country states depending on filter for (const countryData of apiResult) { if (!countryData.country) continue; let countryName = countryData.country; let continentName = this.characterReplace(countryData.continent); const countryObject = this.getCountryObject(countryData.country, countryData['countryInfo']); // if set use iso country values if (countryObject) { if (countryObject.name) { countryName = countryObject.name; } if (countryObject.continent) { continentName = this.characterReplace(countryObject.continent); } } allCountrys.push(countryName); const country = this.characterReplace(countryName); this.log.debug(`api name: ${countryData.country}, converted name: ${countryName}, dp name: ${country}, continent: ${continentName}`); const countryIsChecked = this.config.loadAllCountrys || selectedCountries.includes(countryName); // Only write values if country is selected if (countryIsChecked) { try { await this.createFolderStructure(country); await this.writeVaccinationDataForCountry(country, await VaccinationService.getVaccinationDataByIsoCode(vaccinationData$, countryObject.code.iso3)); if (country === 'Germany') { await this.writeHospitalDataForId(country, await HospitalService.getGermanOverallHospitalData(germanHospitalData$)); await this.writeHospitalIndexDataForId(country, await HospitalIndexService.getGermanHospitalIndexByFederalState(germanHospitalIndexData$, 'Bundesgebiet')); } } catch (error) { this.log.debug(`Cannot write data for ${country}: ${error}`); } } else { if (this.config.deleteUnused === true) { await this.localDeleteState(country) .catch(() => void 0); // ignore error } } await this.localDeleteState(`${country}.countryInfo`); if (!continentName) continue; continentsStats[continentName] = continentsStats[continentName] || {}; continentsStats[continentName]['countries'] = continentsStats[continentName]['countries'] || []; // collect all countries of continent continentsStats[continentName]['inhabitants'] = continentsStats[continentName]['inhabitants'] || 0; // inhabitants to calculate relative values continentsStats['America']['countries'] = continentsStats['America']['countries'] || []; continentsStats['America']['inhabitants'] = continentsStats['America']['inhabitants'] || 0; continentsStats['World_Sum']['inhabitants'] = continentsStats['World_Sum']['inhabitants'] || 0; continentsStats[continentName]['countries'].push(countryName); continentsStats[continentName]['inhabitants'] = continentsStats[continentName]['inhabitants'] + (countryData['cases'] / countryData['casesPerOneMillion']); continentsStats['World_Sum']['inhabitants'] = continentsStats['World_Sum']['inhabitants'] + (countryData['cases'] / countryData['casesPerOneMillion']); if (continentName && continentName.match(americaRegex)) { continentsStats['America']['countries'].push(countryName); continentsStats['America']['inhabitants'] = continentsStats['America']['inhabitants'] + (countryData['cases'] / countryData['casesPerOneMillion']); } // Write states for all country's in API for (const property of Object.keys(countryData)) { // Don't create a state for the country if (property === 'country') continue; if (countryIsChecked) { if (property !== 'countryInfo') { await this.localCreateState(`${country}.${property}`, property, countryData[property]); this.log.debug(`${country} written`); } else { // Only take the flag from country info await this.localCreateState(`${country}.flag`, 'flag', countryData[property].flag); } } if (property === 'countryInfo') continue; continentsStats[continentName][property] = continentsStats[continentName][property] || 0; continentsStats['America'][property] = continentsStats['America'][property] || 0; continentsStats['World_Sum'][property] = continentsStats['World_Sum'][property] || 0; switch (property) { case 'continent': continentsStats[continentName][property] = countryData[property]; break; case 'updated': // for continents: updated is newest of all included countries if (countryData[property] > continentsStats[continentName][property]) { continentsStats[continentName][property] = countryData[property]; continentsStats['World_Sum'][property] = countryData[property]; } if (continentName.match(americaRegex)) { if (countryData[property] > continentsStats['America'][property]) { continentsStats['America'][property] = countryData[property]; } } break; default: continentsStats[continentName][property] += countryData[property]; continentsStats['World_Sum'][property] += countryData[property]; if (continentName === 'North_America' || continentName === 'South_America') { continentsStats['America'][property] += countryData[property]; } } } } await this.extendObjectAsync(`country_Top_5`, { type: 'device', common: { name: 'country Top 5', }, native: {}, }); // Write Top 5 this.log.debug(`Top 5 Countries : ${JSON.stringify(apiResult.slice(0, 5))}`); for (let position = 1; position <= 5; position++) { const dataset = apiResult[position - 1]; // start at 0 let country = dataset.country; const channelName = `country_Top_5.${position}`; await this.extendObjectAsync(channelName, { type: 'channel', common: { name: `Rank ${position} : ${country}`, }, native: {}, }); country = this.characterReplace(country); this.log.debug(`Country loop rank : ${position} ${JSON.stringify(country)}`); for (const property of Object.keys(dataset)) { if (property !== 'countryInfo') { await this.localCreateState(`${channelName}.${property}`, property, dataset[property]); } else { // Only take the flag from country info await this.localCreateState(`${channelName}.flag`, 'flag', dataset[property].flag); } } } if (this.config.getContinents) { // Write continent information await this.extendObjectAsync(`global_continents`, { type: 'device', common: { name: 'Global totals for each continent', }, native: {}, }); } else { await this.localDeleteState('global_continents'); } for (const continentsStatsKey in continentsStats) { this.log.debug(`${continentsStatsKey}: ${JSON.stringify(continentsStats[continentsStatsKey])}`); await this.setObjectNotExistsAsync(`global_continents.${continentsStatsKey}`, { type: 'channel', common: { name: continentsStatsKey, }, native: {}, }); for (const val in continentsStats[continentsStatsKey]) { if (val === 'countryInfo') await this.localDeleteState(`global_continents.${continentsStatsKey}.${val}`); if ((continentsStats[continentsStatsKey].hasOwnProperty(val) && val !== 'countryInfo' && val !== 'inhabitants' && this.config.getContinents === true)) { if (val !== 'countries' && val !== 'casesPerOneMillion' && val !== 'deathsPerOneMillion') { await this.localCreateState(`global_continents.${continentsStatsKey}.${val}`, val, continentsStats[continentsStatsKey][val]); } else if (val === 'casesPerOneMillion') { await this.localCreateState(`global_continents.${continentsStatsKey}.${val}`, val, (continentsStats[continentsStatsKey]['cases'] / continentsStats[continentsStatsKey]['inhabitants']).toFixed(2)); } else if (val === 'deathsPerOneMillion') { await this.localCreateState(`global_continents.${continentsStatsKey}.${val}`, val, (continentsStats[continentsStatsKey]['deaths'] / continentsStats[continentsStatsKey]['inhabitants']).toFixed(2)); } else { await this.localCreateState(`global_continents.${continentsStatsKey}.${val}`, val, continentsStats[continentsStatsKey][val].join()); } } else if ((continentsStats[continentsStatsKey].hasOwnProperty(val) && val !== 'countryInfo') && this.config.getContinents === false) { await this.localDeleteState(`global_continents.${continentsStatsKey}.${val}`); } } } // add user defined country translation to countryTranslator await this.addUserCountriesTranslator(); await this.extendObjectAsync('countryTranslator', { native: { allCountrys, }, }); } catch (error) { this.errorHandling('loadCountries', error); } }; const germanyBundesland = async () => { // Try to call API and get global information try { // RKI Corona Bundesländer : https://npgeo-corona-npgeo-de.hub.arcgis.com/datasets/ef4b445a53c1406892257fe63129a8ea_0/geoservice?geometry=-23.491%2C46.270%2C39.746%2C55.886 // DataSource too build query https://npgeo-corona-npgeo-de.hub.arcgis.com/datasets/ef4b445a53c1406892257fe63129a8ea_0?geometry=-23.491%2C46.270%2C39.746%2C55.886 // const result = await request('https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Coronaf%C3%A4lle_in_den_Bundesl%C3%A4ndern/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=false&outSR=4326&f=json'); // Try to call API and get germanyBundesland let apiResult = null; try { apiResult = await axios.get('https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/Coronaf%C3%A4lle_in_den_Bundesl%C3%A4ndern/FeatureServer/0/query?where=1%3D1&outFields=*&returnGeometry=false&outSR=4326&f=json') .then(response => response.data); this.log.debug(`Data from RKI Corona Bundesländer API received : ${JSON.stringify(apiResult)}`); this.log.debug(`load all country's : ${this.config.loadAllCountrys} as ${typeof this.config.loadAllCountrys}`); } catch (error) { this.log.warn(`[germanyBundesland] Unable to contact Corona Bundesländer API : ${error}`); return; } // Cancel operation in case wrong information is received if (!apiResult || typeof apiResult !== 'object' || !apiResult.features) { this.log.warn(`Incorrect data received from API Corona Bundesländer, values not updated`); return; } // Get Vaccination Data from api.corona-zahlen.org/ let vaccDataGermany = null; try { vaccDataGermany = await axios.get('https://api.corona-zahlen.org/vaccinations') .then(response => response.data); this.log.debug(`Data from api.corona-zahlen.org received : ${vaccDataGermany}`); } catch (error) { this.log.warn(`[germanyBundesland] Unable to contact api.corona-zahlen.org : ${error}`); } // Cancel operation in case wrong information is received if (!vaccDataGermany || typeof vaccDataGermany !== 'object' || !vaccDataGermany.data || !vaccDataGermany.data.states) { this.log.warn(`Incorrect data received from api.corona-zahlen.org received, values not updated`); return; } const germanyVaccinationData = {}; // Structure API result to workable format for (const vaccStates in vaccDataGermany.data.states) { germanyVaccinationData[vaccDataGermany.data.states[vaccStates].name] = { 'allVacc': vaccDataGermany.data.states[vaccStates].administeredVaccinations, 'firstVacc': vaccDataGermany.data.states[vaccStates].vaccinated, 'secondVacc': vaccDataGermany.data.states[vaccStates].secondVaccination.vaccinated, 'firstVaccQuote': vaccDataGermany.data.states[vaccStates].quote * 100, 'secondVaccQuote': vaccDataGermany.data.states[vaccStates].secondVaccination.quote * 100, }; } for (const feature of apiResult.features) { this.log.debug(`Getting data for Federal State : ${JSON.stringify(feature.attributes.LAN_ew_GEN)}`); const federalStateName = feature.attributes.LAN_ew_GEN; const channelName = `Germany.Bundesland.${federalStateName}`; allGermanyFederalStates.push(federalStateName); //ToDo: clean // Delete unused states of previous excel data await this.localDeleteState(`${channelName}._Impfungen.rkiImpfungenProTausend`); await this.localDeleteState(`${channelName}._Impfungen.rkiDifferenzVortag`); await this.localDeleteState(`${channelName}._Impfungen.rkiIndikationAlter`); await this.localDeleteState(`${channelName}._Impfungen.rkiIndikationBeruf`); await this.localDeleteState(`${channelName}._Impfungen.rkiIndikationMedizinisch`); await this.localDeleteState(`${channelName}._Impfungen.rkiImpfungePflegeheim`); if (this.config.getAllGermanyFederalStates || selectedGermanyFederalStates.includes(federalStateName)) { // Create Channel for each Federal State await this.extendObjectAsync(channelName, { type: 'channel', common: { name: federalStateName, }, native: {}, }); try { await this.writeHospitalDataForId(channelName, await HospitalService.getGermanHospitalDataByFederalState(germanHospitalData$, federalStateName)); } catch (error) { this.log.debug(`Cannot write hospital data for ${channelName}: ${error}`); } try { await this.writeHospitalIndexDataForId(channelName, await HospitalIndexService.getGermanHospitalIndexByFederalState(germanHospitalIndexData$, federalStateName)); } catch (error) { this.log.debug(`Cannot write hospital index data for ${channelName}: ${error}`); } if (vaccDataGermany != null) { if (germanyVaccinationData[federalStateName]) { await this.extendObjectAsync(`${channelName}._Impfungen`, { type: 'channel', common: { name: `Impfungen data by RKI`, }, native: {}, }); // Handle vaccination data based new Excel layout await this.localCreateState(`${channelName}._Impfungen.rkiImpfungenGesamtVerabreicht`, 'Gesamtzahl bisher verabreichter Impfungen', germanyVaccinationData[federalStateName].allVacc); await this.localCreateState(`${channelName}._Impfungen.rkiErstimpfungenKumulativ`, 'Erstimpfungen Kumulativ', germanyVaccinationData[federalStateName].firstVacc); await this.localCreateState(`${channelName}._Impfungen.rkiZweitimpfungenKumulativ`, 'Zweitimpfungen Kumulativ', germanyVaccinationData[federalStateName].secondVacc); await this.localCreateState(`${channelName}._Impfungen.rkiErstimpfungenImpfquote`, 'Erstimpfungen Impfquote', await this.modify(`round(2)`, germanyVaccinationData[federalStateName].firstVaccQuote)); await this.localCreateState(`${channelName}._Impfungen.rkiZweitimpfungenImpfquote`, 'Zweitimpfungen Impfquote', await this.modify(`round(2)`, germanyVaccinationData[federalStateName].secondVaccQuote)); // Delete unused states from previous RKI version await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenBioNTech`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenModerna`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenAstraZeneca`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenDifferenzVortag`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenKumulativ`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenBioNTech`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenModerna`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenAstraZeneca`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenDifferenzVortag`); } } for (const attributeName of Object.keys(feature.attributes)) { switch (attributeName) { case 'Aktualisierung': // Last refresh date await this.localCreateState(`${channelName}.updated`, 'updated', feature.attributes[attributeName]); break; case 'Death': // Current reported deaths await this.localCreateState(`${channelName}.deaths`, 'deaths', feature.attributes[attributeName]); break; case 'Fallzahl': // Current reported cases await this.localCreateState(`${channelName}.cases`, 'cases', feature.attributes[attributeName]); break; case 'faelle_100000_EW': // reported cases per 100k await this.localCreateState(`${channelName}.cases_per_100k`, 'cases_per_100k', feature.attributes[attributeName]); break; case 'cases7_bl_per_100k': // reported cases per 100k during the last 7 days await this.localCreateState(`${channelName}.cases7_per_100k`, 'cases7_per_100k', feature.attributes[attributeName]); break; default: this.log.debug(`Data \\"${attributeName}\\" from API ignored having values : ${feature.attributes[attributeName]}`); } } } else { for (const attributeName of Object.keys(feature.attributes)) { // Delete vaccination states await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenKumulativ`); await this.localDeleteState(`${channelName}._Impfungen.rkiImpfungenGesamtVerabreicht`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenBioNTech`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenModerna`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenAstraZeneca`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenDifferenzVortag`); await this.localDeleteState(`${channelName}._Impfungen.rkiErstimpfungenImpfquote`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenKumulativ`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenBioNTech`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenModerna`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenAstraZeneca`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenDifferenzVortag`); await this.localDeleteState(`${channelName}._Impfungen.rkiZweitimpfungenImpfquote`); switch (attributeName) { case 'Aktualisierung': // Last refresh date await this.localDeleteState(`${channelName}.updated`); break; case 'Death': // Current reportet deaths await this.localDeleteState(`${channelName}.deaths`); break; case 'Fallzahl': // Current reportet cases await this.localDeleteState(`${channelName}.cases`); break; case 'faelle_100000_EW': // reported cases per 100k await this.localDeleteState(`${channelName}.cases_per_100k`); break; case 'cases7_bl_per_100k': // reported cases per 100k during the last 7 days await this.localDeleteState(`${channelName}.cases7_per_100k`); break; default: this.log.debug(`Data \\"${attributeName}\\" from API ignored having values : ${feature.attributes[attributeName]}`); await this.localDeleteState(`${channelName}.${attributeName}`); } } } } await this.localDeleteState(`Germany._Impfungen`); allGermanyFederalStates = allGermanyFederalStates.sort(); this.log.debug(`allGermanyFederalStates : ${JSON.stringify(allGermanyFederalStates)}`); await this.extendObjectAsync('countryTranslator', { native: { allGermanyFederalStates, }, }); } catch (error) { this.errorHandling('germanyFederalStates', error); } }; const germanyCounties = async () => { // Try to call API and get global information try { // RKI Corona Landkreise : https://npgeo-corona-npgeo-de.hub.arcgis.com/datasets/917fc37a709542548cc3be077a786c17_0/geoservice?selectedAttribute=BSG // Try to call API and get germanyBundesland let apiResult = null; try { apiResult = await axios.get('https://services7.arcgis.com/mOBPykOjAyBO2ZKk/arcgis/rest/services/RKI_Landkreisdaten/FeatureServer/0/query?where=1%3D1&outFields=OBJECTID,GEN,BEZ,death_rate,cases,deaths,cases_per_100k,cases7_per_100k,cases_per_population,BL,county,last_update&returnGeometry=false&outSR=4326&f=json'); this.log.debug(`Data from RKI Corona Landkreise API received : ${apiResult.data}`); this.log.debug(`load all country's : ${this.config.loadAllCountrys} as ${typeof this.config.loadAllCountrys}`); } catch (error) { this.log.warn(`[germanyBundesland] Unable to contact RKI Corona Bundesländer API : ${error}`); return; } const values = apiResult.data; // Cancel operation in case wrong information is received if (typeof values !== 'object') { this.log.warn(`Incorrect data received from API RKI Corona Bundesländer, values not updated`); return; } for (const feature of values.features) { if (!feature) continue; this.log.debug(`Getting data for Landkreise : ${JSON.stringify(feature.attributes.county)} containing ${JSON.stringify(feature.attributes)}`); let countyName = feature.attributes.GEN; let countiesType = feature.attributes.BEZ; countyName = this.characterReplace(countyName); allGermanCountyDetails.push({ [feature.attributes.county]: { GEN: feature.attributes.GEN, county: feature.attributes.county, BEZ: feature.attributes.BEZ, }, }); // Distinguish between Kreisfreie Stadt & Landkreis if (countiesType === 'Kreisfreie Stadt') { allGermanyCities.push(countyName); countiesType = 'Stadt'; } else if (countiesType === 'Kreis') { allGermanyCounties.push(countyName); countiesType = 'Kreis'; } else if (countiesType === 'Landkreis') { allGermanyCounties.push(countyName); countiesType = 'Kreis'; } else if (countiesType === 'Stadtkreis') { allGermanyCities.push(countyName); countiesType = 'Stadt'; } else if (countiesType === 'Bezirk') { allGermanyCities.push(countyName); countiesType = 'Stadt'; } else { this.log.error(`Unknown ${countiesType} received containing ${JSON.stringify(feature)}`); } const folderStructure = async () => { await this.extendObjectAsync(`Germany.${countiesType}`, { type: 'channel', common: { name: countiesType, }, native: {}, }); // await this.createChannelAsync(`Germany`, countiesType); // Create Vaccination Channel await this.extendObjectAsync(`Germany.${countiesType}.${countyName}`, { type: 'channel', common: { name: countyName, }, native: {}, }); }; // Create proper folder structure if (countiesType == 'Kreis' && (this.config.getAllGermanyCounties || selectedGermanyCounties.includes(countyName))) { await folderStructure(); } else if (countiesType == 'Kreis') { await this.localDeleteState(`Germany.${countiesType}.${countyName}`); } if (countiesType == 'Stadt' && (this.config.getAllGermanyCities || selectedGermanyCities.includes(countyName))) { await folderStructure(); } else if (countiesType == 'Stadt') { await this.localDeleteState(`Germany.${countiesType}.${countyName}`); } // Run truth all states and write information for (const attributeName of Object.keys(feature.attributes)) { this.log.debug(`Statename will be : Germany.${countiesType}.${countyName} containing ${JSON.stringify(feature.attributes)}`); if (attributeName !== 'county' && attributeName !== 'GEN' && attributeName !== 'BEZ' && attributeName !== 'OBJECTID') { switch (countiesType) { case 'Stadt': if (this.config.getAllGermanyCities || selectedGermanyCities.includes(countyName)) { this.log.debug(`Create city : ${countyName}`); // Create State for each Landkreis await this.localCreateState(`Germany.${countiesType}.${countyName}.${attributeName}`, attributeName, feature.attributes[attributeName]); } break; case 'Kreis': if (this.config.getAllGermanyCounties || selectedGermanyCounties.includes(countyName)) { this.log.debug(`Create Landkreis : ${countyName}`); // Create State for each Landkreis await this.localCreateState(`Germany.${countiesType}.${countyName}.${attributeName}`, attributeName, feature.attributes[attributeName]); } break; default: await this.localDeleteState(`Germany.county.${countyName}.${attributeName}`); } } } } allGermanCountyDetails = allGermanCountyDetails.sort(); this.log.debug(`allGermanCountyDetails : ${JSON.stringify(allGermanCountyDetails)}`); allGermanyCities = allGermanyCities.sort(); this.log.debug(`allGermanyCities : ${JSON.stringify(allGermanyCities)}`); allGermanyCounties = allGermanyCounties.sort(); this.log.debug(`allGermanyCounties : ${JSON.stringify(allGermanyCounties)}`); await this.extendObjectAsync('countryTranslator', { native: { allGermanyCounties, }, }); await this.extendObjectAsync('countryTranslator', { native: { allGermanyCities, }, }); } catch (error) { this.errorHandling('germanyCounties', error); } }; // Random number generator to avoid all ioBroker instances calling the API at the same time await wait(Math.floor(Math.random() * 30 * 1000)); vaccinationData$ = VaccinationService.refreshVaccinationData() .catch(error => this.log.warn(`Vaccination Data Warning: ${error}`)); // load all vaccination data // Functionality removed in version 0.9, API not available anymore // germanHospitalData$ = HospitalService.refreshGermanHospitalData() // .catch(error => this.log.warn(`Hospital Data Warning: ${error}`)); // load german hospital data germanHospitalIndexData$ = HospitalIndexService.refreshGermanHospitalIndexData() .catch(error => this.log.warn(`Hospital Index Data Warning: ${error}`)); // load german hospital index await loadAll(); // Global Worldwide statistics await loadCountries(); // Detailed Worldwide statistics by country // Functionality removed in version 0.9, API not available anymore // if (this.config.getGermanyFederalStates || !allGermanyFederalStatesLoaded) { // await this.extendObjectAsync(`Germany.Bundesland`, { // type: 'channel', // common: { // name: 'Bundesland', // }, // native: {}, // }); // await germanyBundesland(); // Detailed Federal state statistics for germany // } else { await this.localDeleteState(`Germany.Bundesland`); // } // Functionality removed in version 0.9, API not available anymore // Get data for cities and counties of Germany, ensure tables always have values to load // if (this.config.getGermanyCities || this.config.getGermanyCounties || !allGermanyCitiesLoaded || !allGermanyCountiesLoaded) { // await germanyCounties(); // Detailed city state statistics for germany // } // Delete potential unused data for germany if (!this.config.getGermanyCities) { await this.localDeleteState(`Germany.Stadt`); } if (!this.config.getGermanyCounties) { await this.localDeleteState(`Germany.Kreis`); } // Always terminate at the end this.terminate ? this.terminate('All data handled, adapter stopped until next scheduled moment') : process.exit(); } catch (error) { this.errorHandling('onReady', error); // Ensure termination at error this.terminate ? this.terminate('Adapter closed unexpectedly, not all data processed') : process.exit(); } } async localCreateState(state, name, value) { this.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; this.log.debug(warnMessage); this.errorHandling('localCreateState', warnMessage, true); } } 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 = typeof (value); const unit = stateAttr[name] !== undefined ? stateAttr[name].unit || '' : ''; this.log.debug(`Write value : ${writable}`); await this.setObjectNotExistsAsync(state, { type: 'state', common: { name: state_name, role: role, type: type, unit: unit, read: true, write: writable, }, native: {}, }); // Ensure attribute changes are propagated await this.extendObjectAsync(state, { type: 'state', common: { name: state_name, type: type, unit: unit, }, }); // Only set value if input != null if (value !== null) { await this.setState(state, {val: value, ack: true}); } // Subscribe on state changes if writable // writable && this.subscribeStates(state); } catch (error) { this.errorHandling('localCreateState', error); } } async localDeleteState(state) { try { if (this.config.deleteUnused === true) { const obj = await this.getObjectAsync(state); if (obj) { await this.delObjectAsync(state, {recursive: true}); } } } catch (error) { // do nothing } } /** * @param {string} country * @param {Object} countryInfo */ getCountryObject(country, countryInfo) { try { let countryObj = undefined; if (countryInfo.iso3) { // Country Objekt über iso3 suchen return countryJs.findByIso3(countryInfo.iso3); } if (countryInfo.iso2) { // Country Objekt über iso2 suchen return countryJs.findByIso2(countryInfo.iso2); } // kein iso info vorhanden, über Name suchen countryObj = countryJs.findByName(country.replace(/_/g, ' ').replace(/é/g, 'e').replace(/ç/g, 'c')); if (countryObj && countryObj.continent) { return countryObj; } countryObj = countryJs.findByName(countryTranslator[country]); if (countryObj && countryObj.continent) { return countryObj; } if (country !== 'Diamond Princess' && country !== 'MS Zaandam') { this.log.warn(`${country} (iso2: ${countryInfo.iso2}, iso3: ${countryInfo.iso3}) not found in lib! Must be added to the country name translator.`); } } catch (error) { this.errorHandling('getIsoCountry', error); } return undefined; } async addUserCountriesTranslator() { try { const userCountryTranslator = await this.getStateAsync('countryTranslator'); if (userCountryTranslator && userCountryTranslator.val) { // add user defined country translation to countryTranslator try { const userCountries = JSON.parse(userCountryTranslator.val); Object.keys(userCountries).forEach(countryId => { if (!countryTranslator.hasOwnProperty(countryId)) { countryTranslator[countryId] = userCountries[countryId]; this.log.info(`user defined country translation added: ${countryId} -> ${userCountries[countryId]}`); } }); } catch (parseError) { this.log.error(`Can not parse json string for user defined country translation! Check input of datapoint '.countryTranslator'. error: ${parseError.message}`); } } } catch (error) { this.errorHandling('addUserCountriesTranslator', error); } } characterReplace(inputString) { if (!inputString) return; return inputString.replace(allSpaces, '_').replace(allPointAndCommas, ''); } /** * writes vaccination data to folder of country * * @param country "Germany" * @param data Object to write */ async writeVaccinationDataForCountry(country, data) { if (data) { for (const key of Object.keys(data)) { await this.localCreateState(`${country}.Vaccination.${key}`, key, data[key]); } } else { this.log.debug(`Cannot write vaccination data for ${country}, if this error continues please report a bug to the developer! Totals: ${JSON.stringify(data)}`); } } /** * @param id "Germany" * @param data Object to write */ async writeHospitalDataForId(id, data) { if (!id || !data) { this.log.debug(`Cannot write hospital data for ${id}, if this error continues please report a bug to the developer! Totals: ${JSON.stringify(data)}`); return; } await this.extendObjectAsync(`${id}.Hospital`, { type: 'channel', common: { name: `Hospital`, }, native: {}, }); for (const key of Object.keys(data)) { await this.localCreateState(`${id}.Hospital.${key}`, key, data[key]); } } /** * @param id "Germany" * @param data Object to write */ async writeHospitalIndexDataForId(id, data) { if (!id || !data) { this.log.debug(`Cannot write hospital index data for ${id}, if this error continues please report a bug to the developer! Totals: ${JSON.stringify(data)}`); return; } await this.extendObjectAsync(`${id}.Hospital_Index`, { type: 'channel', common: { name: `Hospital Index`, }, native: {}, }); for (const key of Object.keys(data)) { await this.localCreateState(`${id}.Hospital_Index.${key}`, key, data[key]); } } async createFolderStructure(country) { // Create country folder await this.extendObjectAsync(country, { type: 'device', common: { name: country, }, native: {}, }); // Create Vaccination Channel await this.extendObjectAsync(`${country}.Vaccination`, { type: 'channel', common: { name: 'Vaccination Data', }, native: {}, }); } /** * Analysis modify element in stateAttr.js and executes command * @param {string} method defines the method to be executed (e.g. round()) * @param {string | number | boolean} value value to be executed */ modify(method, value) { this.log.debug(`Function modify with method "${method}" and value "${value}"`); let result = null; try { if (method.match(/^custom:/gi) != null) { //check if starts with "custom:" value = eval(method.replace(/^custom:/gi, '')); //get value without "custom:" } else if (method.match(/^multiply\(/gi) != null) { //check if starts with "multiply(" const inBracket = parseFloat(method.match(modifyFloatRegex)); //get value in brackets value = value * inBracket; } else if (method.match(/^divide\(/gi) != null) { //check if starts with "divide(" const inBracket = parseFloat(method.match(modifyFloatRegex)); //get value in brackets value = value / inBracket; } else if (method.match(/^round\(/gi) != null) { //check if starts with "round(" const inBracket = parseInt(method.match(modifyFloatRegex)); //get value in brackets value = Math.round(value * Math.pow(10, inBracket)) / Math.pow(10, inBracket); } else if (method.match(/^add\(/gi) != null) { //check if starts with "add(" const inBracket = parseFloat(method.match(modifyFloatRegex)); //get value in brackets value = parseFloat(value) + inBracket; } else if (method.match(/^substract\(/gi) != null) { //check if starts with "substract(" const inBracket = parseFloat(method.match(modifyFloatRegex)); //get value in brackets value = parseFloat(value) - inBracket; } else { const methodUC = method.toUpperCase(); switch (methodUC) { case 'UPPERCASE': if (typeof value == 'string') result = value.toUpperCase(); break; case 'LOWERCASE': if (typeof value == 'string') result = value.toLowerCase(); break; case 'UCFIRST': if (typeof value == 'string') result = value.substring(0, 1).toUpperCase() + value.substring(1).toLowerCase(); break; default: result = value; } } if (!result) return value; return result; } catch (e) { this.errorHandling(`[modify]`, `${e}`); return value; } } errorHandling(codePart, error, suppressFrontendLog) { if (!suppressFrontendLog) { this.log.error(`[${codePart}] error: ${error.message}, stack: ${error.stack}`); } if (this.supportsFeature && this.supportsFeature('PLUGINS')) { const sentryInstance = this.getPluginInstance('sentry'); if (sentryInstance) { sentryInstance.getSentryObject().captureException(error); } } } } // @ts-ignore parent is a valid property on module if (module.parent) { // Export the constructor in compact mode /** * @param {Partial<ioBroker.AdapterOptions>} [options={}] */ module.exports = (options) => new Covid19(options); } else { // otherwise start the instance directly new Covid19(); }