UNPKG

iobroker.weather-warnings

Version:
1,359 lines 51.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var provider_exports = {}; __export(provider_exports, { BaseProvider: () => BaseProvider, DIV: () => DIV, DWDProvider: () => DWDProvider, METROProvider: () => METROProvider, NINAProvider: () => NINAProvider, ProviderController: () => ProviderController, UWZProvider: () => UWZProvider, ZAMGProvider: () => ZAMGProvider }); module.exports = __toCommonJS(provider_exports); var import_axios = __toESM(require("axios")); var definitionen = __toESM(require("./def/definition")); var import_library = require("./library"); var import_messages = require("./messages"); var NotificationClass = __toESM(require("./notification")); var import_test_warnings = require("./test-warnings"); var NotificationType = __toESM(require("./def/notificationService-def")); var messagesDef = __toESM(require("./def/messages-def")); const DIV = "-"; import_axios.default.defaults.timeout = 5e3; class BaseProvider extends import_library.BaseClass { service; url = ""; warncellId = ""; rawData = null; library; messages = []; noMessage; providerController; filter; customName = ""; warncellIdString; /** * Initializes the provider. * * @param adapter The adapter instance. * @param options The options for the provider. * @param name The name of the provider. */ constructor(adapter, options, name) { let warncell = typeof options.warncellId == "object" && Array.isArray(options.warncellId) ? options.warncellId.join(DIV) : options.warncellId; warncell = warncell.replaceAll(".", "_"); super(adapter, `provider.${name}.${warncell}`); this.warncellIdString = warncell; this.service = options.service; this.library = this.adapter.library; this.providerController = options.providerController; this.setService(options.service); this.log.setLogPrefix(`${name}-${options.warncellId}`); this.filter = options.filter; this.customName = options.customName; this.noMessage = new import_messages.MessagesClass(this.adapter, "noMessage", null, {}, this.providerController, this); this.noMessage.updateFormated().catch(() => { }); const temp = this.library.cloneGenericObject( //@ts-expect-error ist vorhanden definitionen.statesObjectsWarnings[this.service]._channel ); temp.common.name = name.toUpperCase(); this.library.writedp(`provider.${name}`, void 0, temp).catch(() => { }); this.init().catch(() => { }); } /** * Initializes the provider. * * Writes the channel and device objects, and sets the connection status to false. * * @returns A promise that resolves when initialization is complete. */ async init() { const temp = this.library.cloneGenericObject(definitionen.defaultChannel); temp.common.name = this.customName; await this.library.writedp(`${this.name}`, void 0, temp); await this.adapter.extendObjectAsync(`${this.name}`, { common: { name: this.customName } }); await this.library.writedp(`${this.name}.info`, void 0, definitionen.genericStateObjects.info._channel); await this.library.writedp( `${this.name}.formatedKeys`, void 0, definitionen.genericStateObjects.formatedKeysDevice ); this.setConnected(false).catch(() => { }); } /** * Deletes the provider. * * Calls the parent's delete function, deletes all messages, and sets the connection status to false. * * @returns A promise that resolves when deletion is complete. */ async delete() { await super.delete(); this.rawData = null; await this.library.memberDeleteAsync(this.messages); this.messages = []; await this.setConnected(false); } /** * Returns the provider service of the provider. * * @throws if service is invalid * @returns the provider service */ getService() { if (!this.service) { throw new Error(`baseProvider.getService service is ${this.service == "" ? `''` : `undefined`}`); } return this.service; } /*getStatesObjectsWarnings(key: string): { [key: string]: ioBroker.Object } { return statesObjectsWarnings[this.service][key]; }*/ /** * @param service providerDef.providerServices to set * @returns true on success * @throws if service is invalid * @description Sets the provider service. Checks if service is valid. */ setService(service) { if (!service || ["dwdService", "zamgService", "uwzService", "ninaService", "metroService"].indexOf(service) === -1) { throw new Error(`baseProvider.setService service ${service} is unknowed!`); } this.service = service; return true; } /** * Returns the URL for the provider, replacing placeholders with keys. * If `url` is not given, the default URL for the provider is used. * The placeholder is '# #' and is replaced with each key in `keys`. * The placeholder is then changed to '#+# #'#'. * * @param url the URL to use (default is the default URL for the provider) * @param keys the keys to replace in the URL * @param service the provider service * @returns the resulting URL */ static getUrl(url = "", keys, service) { let result = ""; if (!url) { result = definitionen.PROVIDER_OPTIONS[service].url; } else { result = url; } let placeholder = "# #"; for (const k of keys) { result = result.replace(placeholder, k); placeholder = `${placeholder.slice(0, 1)}+${placeholder.slice(1, -1)}+${placeholder.slice(-1)}`; } return result; } /** * Set the connection status for the provider. * * @param status - the status to set * @returns a promise that resolves when the status is set */ async setConnected(status) { this.providerController.connection = this.providerController.connection || status; await this.library.writedp( `${this.name}.info.connection`, !!status, definitionen.genericStateObjects.info.connection ); } /** * Write the alerts to statesObjectsWarnings and returns the alerts as a * genericWarntypeAlertJsonType. * * @param allReplys - the alerts to write, if not given, a new one is created * @returns the alerts as a genericWarntypeAlertJsonType * @throws if error occurs */ async getAlertsAndWrite(allReplys = void 0) { const reply = { asList: "" }; for (const t in messagesDef.genericWarntyp) { if (t == "0") { continue; } reply[messagesDef.genericWarntyp[Number(t)].id] = { level: -1, start: 1, end: 1, headline: "", active: false, type: -1 }; } if (!reply) { throw new Error("error(234) reply not definied"); } if (allReplys === void 0) { allReplys = JSON.parse(JSON.stringify(reply)); } [reply, allReplys].forEach((reply2) => { let warntypeArray = []; for (const m of this.messages) { if (!m) { continue; } const name = messagesDef.genericWarntyp[m.genericType].id; if (name == "none") { continue; } if (reply2[name] === void 0) { continue; } if (m.endtime < Date.now()) { continue; } if (m.starttime <= Date.now() && reply2[name].level < m.level || m.starttime > Date.now() && (reply2[name].start === 1 || reply2[name].start > m.starttime)) { warntypeArray.push(this.library.getTranslation(messagesDef.genericWarntyp[m.genericType].name)); reply2[name] = { level: m.level, start: m.starttime, end: m.endtime, headline: m.formatedData !== void 0 ? String(m.formatedData.headline) : "", active: m.starttime <= Date.now() && m.endtime >= Date.now(), type: m.genericType }; if (reply2 === allReplys) { reply2[name].provider = m.formatedData ? m.formatedData.provider : ""; } } } if (reply2.asList !== void 0) { warntypeArray = warntypeArray.concat(reply2.asList.split(", ")); } reply2.asList = warntypeArray.filter((item, pos, arr) => item && arr.indexOf(item) == pos).join(", "); }); await this.library.writeFromJson( `${this.name}.alerts`, "allService.alerts", definitionen.statesObjectsWarnings, reply, false ); return allReplys; } /** * Downloads data from the selected provider. * * Downloads data from the provider selected via service and warncellId. * If useTestWarnings is set, test data will be returned. * If the provider is unavailable, errors will be logged and the function will return null. * If the provider does not send data, the lastUpdate state will not be updated. * * @returns The data from the provider as a generic object. */ async getDataFromProvider() { if (!this.url || !this.warncellId) { this.log.debug( `Warn (31) this.url: ${this.url} this.warncellid: ${this.warncellId} this.service: ${this.getService()}` ); } try { if (this.unload) { return; } await this.library.writedp( `${this.name}.info.testMode`, this.adapter.config.useTestWarnings, definitionen.genericStateObjects.info.testMode ); if (this.adapter.config.useTestWarnings) { return this.library.cloneGenericObject( (0, import_test_warnings.getTestData)(this.service, this.adapter) ); } const data = await import_axios.default.get(this.url); if (this.unload) { return null; } if (data.status == 200) { await this.setConnected(true); const result = typeof data.data == "object" ? data.data : JSON.parse(data.data); await this.library.writedp( `${this.name}.warning.warning_json`, JSON.stringify(result), definitionen.genericStateObjects.warnings_json ); if (this.adapter.config.useJsonHistory) { const dp = `${this.name}.warning.jsonHistory`; const state = this.library.readdp(dp); let history = []; if (state && state.val && typeof state.val == "string") { history = JSON.parse(state.val); } history.unshift(result); await this.library.writedp( dp, JSON.stringify(history), definitionen.genericStateObjects.jsonHistory ); } await this.library.writedp( `${this.name}.lastUpdate`, Date.now(), definitionen.genericStateObjects.lastUpdate ); return result; } this.log.warn(`Warn(23) ${data.statusText}`); } catch (error) { if (import_axios.default.isAxiosError(error)) { this.log.warn(`Warn(21) axios error for ${this.getService()} url: ${this.url}`); } else { this.log.error( `Error(22) no data for ${this.getService()} from ${this.url} with Error ${error}` ); } } await this.setConnected(false); return null; } /** * after updating data, this function sorts the messages, deletes the messages that send a remove with a follow up and * writes the formated keys to the states. */ async finishUpdateData() { let index = -1; this.messages.sort((a, b) => { return a.starttime - b.starttime; }); this.messages = this.messages.filter((item) => { if (item.notDeleted || item.newMessage) { return true; } if (item.endtime > Date.now()) { return true; } for (const innerItem of this.messages) { if (innerItem == item || innerItem.provider !== item.provider || innerItem.type !== item.type) { continue; } const diff = Math.abs(innerItem.starttime - item.endtime); if (diff <= this.providerController.refreshTime || innerItem.starttime <= item.endtime) { return false; } } return true; }); for (let m = 0; m < this.messages.length && m < this.adapter.config.numOfRawWarnings; m++) { index = m; await this.messages[m].writeFormatedKeys(m); } for (index++; index < this.adapter.config.numOfRawWarnings; index++) { await this.noMessage.writeFormatedKeys(index); } await this.library.garbageColleting( `${this.name}.formatedKeys`, (this.providerController.refreshTime || 6e5) / 2 ); await this.library.garbageColleting( `${this.name}.warning`, (this.providerController.refreshTime || 6e5) / 2 ); } /** * Writes the raw warning data to the states. * * @param data - raw warning data * @param counter - counter of the raw warning */ async updateData(data, counter) { if (!data) { return; } await this.library.writedp(`${this.name}.warning`, void 0, definitionen.genericStateObjects.warningDevice); await this.library.writeFromJson( `${this.name}.warning.${`00${counter.toString()}`.slice(-2)}`, `${this.service}.raw`, definitionen.statesObjectsWarnings, data ); } /** Remove marked Messages. */ clearMessages() { for (let m = 0; m < this.messages.length; m++) { this.messages[m].newMessage = false; if (this.messages[m].notDeleted == false) { this.log.debug("Remove a warning from db."); this.messages.splice(Number(m--), 1); } } } /** * Called at the end of a turn. * * Writes the summary warning data to the states. * * @returns A Promise that resolves when the data has been written. */ async finishTurn() { await this.adapter.library.writedp( `${this.name}.summary`, void 0, definitionen.genericStateObjects.summary._channel ); await this.adapter.library.writedp( `${this.name}.summary.warntypes`, this.messages.map((a) => a.formatedData ? a.formatedData.warntypegenericname : "").join(", "), definitionen.genericStateObjects.summary.warntypes ); await this.library.garbageColleting(`${this.name}.activeWarnings_json`, 15e3); } } class DWDProvider extends BaseProvider { /** * Initializes the DWD provider. * * @param adapter The adapter instance. * @param options The options for the provider. */ constructor(adapter, options) { super(adapter, { ...options, service: "dwdService" }, `dwd`); this.warncellId = options.warncellId; const url = definitionen.PROVIDER_OPTIONS.dwdService.url_base + (this.warncellId.startsWith("9") || this.warncellId.startsWith("10") ? definitionen.PROVIDER_OPTIONS.dwdService.url_appendix_land : definitionen.PROVIDER_OPTIONS.dwdService.url_appendix_town) + definitionen.PROVIDER_OPTIONS.dwdService.url_language; this.url = DWDProvider.getUrl(url, [this.warncellId, options.language], this.service); } /** * Retrieves the data from the DWD provider and updates the messages array. * * @returns A Promise that resolves when the data has been written. */ async updateData() { const result = await this.getDataFromProvider(); if (!result) { return; } this.log.debug(`Got ${result.features.length} warnings from server`); result.features.sort((a, b) => { return new Date(a.properties.ONSET).getTime() - new Date(b.properties.ONSET).getTime(); }); this.messages.forEach((a) => a.notDeleted = false); for (let a = 0; a < this.adapter.config.numOfRawWarnings && a < result.features.length; a++) { const w = result.features[a]; if (w.properties.STATUS == "Test") { continue; } if (this.filter.hours > 0 && new Date(w.properties.ONSET).getTime() > Date.now() + this.filter.hours * 36e5) { continue; } await super.updateData(w.properties, a); const index = this.messages.findIndex((m) => m.rawWarning.IDENTIFIER == w.properties.IDENTIFIER); if (index == -1) { const nmessage = new import_messages.MessagesClass( this.adapter, "dwd-msg", this, w.properties, this.providerController ); await nmessage.updateFormated(); if (nmessage && nmessage.filter(this.filter)) { this.messages.push(nmessage); } } else { await this.messages[index].updateData(w.properties); } } for (let n = 0; n < this.messages.length; n++) { const newmsg = this.messages[n]; if (!newmsg.newMessage) { continue; } for (let o = 0; o < this.messages.length; o++) { const oldmsg = this.messages[o]; if (oldmsg.newMessage) { continue; } if (oldmsg.formatedData === void 0 || newmsg.formatedData === void 0) { continue; } if (oldmsg.rawWarning.EC_II != newmsg.rawWarning.EC_II) { continue; } if (oldmsg.starttime > newmsg.endtime || newmsg.starttime > oldmsg.endtime) { continue; } newmsg.silentUpdate(); this.log.debug("Remove a old warning.(Silent Update)"); if (o <= n) { n--; } this.messages.splice(o--, 1); break; } } await this.finishUpdateData(); } } class ZAMGProvider extends BaseProvider { /** * Initializes a new instance of the ZAMGProvider class. * * @param adapter The WeatherWarnings adapter instance. * @param options The options for configuring the ZAMG provider, including warncellId and language. */ constructor(adapter, options) { super(adapter, { ...options, service: "zamgService" }, `zamg`); this.warncellId = options.warncellId; this.url = ZAMGProvider.getUrl("", [this.warncellId[0], this.warncellId[1], options.language], this.service); } /** * Updates the data for the ZAMG provider. * * Retrieves the data from the ZAMG server and updates the messages. * If the filter is set to only show warnings in the next X hours, ignores the warnings that are outside of this time frame. * Also ignores warnings that are already expired. * * @returns A promise that resolves when the update is complete. */ async updateData() { const result = await this.getDataFromProvider(); if (!result) { return; } if (!result.properties || !result.properties.warnings) { this.log.debug(`Got 0 warnings from server`); return; } this.log.debug(`Got ${result.properties.warnings.length} warnings from server`); result.properties.warnings.sort((a, b2) => { return Number(a.properties.rawinfo.start) - Number(b2.properties.rawinfo.start); }); this.messages.forEach((a) => a.notDeleted = false); let b = 0; for (let a = 0; b < this.adapter.config.numOfRawWarnings && a < result.properties.warnings.length; a++) { if (this.filter.hours > 0 && Number(result.properties.warnings[a].properties.rawinfo.start) > Date.now() / 1e3 + this.filter.hours * 3600) { continue; } if (Number(result.properties.warnings[a].properties.rawinfo.end) < Date.now() / 1e3) { continue; } b++; result.properties.warnings[a].properties.location = result.properties.location.properties.name; result.properties.warnings[a].properties.nachrichtentyp = result.properties.warnings[a].type; await super.updateData(result.properties.warnings[a].properties, a); const index = this.messages.findIndex((m) => { return m.rawWarning.warnid == result.properties.warnings[a].properties.warnid && result.properties.warnings[a].properties.rawinfo.wlevel == m.rawWarning.rawinfo.wlevel && result.properties.warnings[a].properties.rawinfo.wtype == m.rawWarning.rawinfo.wtype && result.properties.warnings[a].properties.rawinfo.start == m.rawWarning.rawinfo.start; }); if (index == -1) { const nmessage = new import_messages.MessagesClass( this.adapter, "zamg-msg", this, result.properties.warnings[a].properties, this.providerController ); await nmessage.updateFormated(); if (nmessage && nmessage.filter(this.filter)) { this.messages.push(nmessage); } } else { await this.messages[index].updateData(result.properties.warnings[a].properties); } } await this.finishUpdateData(); } } class UWZProvider extends BaseProvider { /** * Initializes the UWZ provider. * * @param adapter The adapter instance. * @param options The options for the provider. */ constructor(adapter, options) { super(adapter, { ...options, service: "uwzService" }, `uwz`); this.warncellId = options.warncellId.toUpperCase(); this.url = BaseProvider.getUrl("", [this.warncellId, options.language], this.service); } /** * Gets the warncell for the given warncellId. * * The warncell is retrieved by querying the UWZ provider's API. * * @param warncellId The warncellId to retrieve the warncell for. * @param service The service to use for retrieving the warncell. * @param that The WeatherWarnings instance to use for logging errors. * @returns The warncell for the given warncellId, or an empty string if no valid warncell could be found. */ static async getWarncell(warncellId, service, that) { try { const result = await import_axios.default.get( UWZProvider.getUrl( definitionen.PROVIDER_OPTIONS.uwzService.warncellUrl, [warncellId[0], warncellId[1]], service ) ); if (result) { if (result.data && result.data[0]) { return result.data[0].AREA_ID; } } that.log.error(`No valid warncell found for ${JSON.stringify(warncellId)}`); } catch (error) { that.log.warn(`Dont get warncell. ${JSON.stringify(error.toJSON)}`); } return ""; } /** * Updates the data for the UWZ provider. * * Retrieves the data from the UWZ server and updates the messages. * If the filter is set to only show warnings in the next X hours, ignores the warnings that are outside of this time frame. * Also ignores warnings that are already expired. * * @returns A promise that resolves when the update is complete. */ async updateData() { const result = await this.getDataFromProvider(); if (!result || !result.results || result.results == null) { this.log.warn("Invalid result from uwz server!"); return; } result.results.sort((a, b) => { if (a && b && a.dtgStart && b.dtgStart) { return a.dtgStart - b.dtgStart; } return 0; }); this.messages.forEach((a) => a.notDeleted = false); for (let a = 0; a < this.adapter.config.numOfRawWarnings && a < result.results.length; a++) { if (result.results[a] == null) { continue; } if (this.filter.hours > 0 && result.results[a].dtgStart > Date.now() / 1e3 + this.filter.hours * 3600) { continue; } await super.updateData(result.results[a], a); const index = this.messages.findIndex((m) => m.rawWarning.payload.id == result.results[a].payload.id); if (index == -1) { const nmessage = new import_messages.MessagesClass( this.adapter, "uwz-msg", this, result.results[a], this.providerController ); await nmessage.updateFormated(); if (nmessage && nmessage.filter(this.filter)) { this.messages.push(nmessage); } } else { await this.messages[index].updateData(result.results[a]); } } this.log.debug(`Got ${result.results.length} warnings from server`); await this.finishUpdateData(); } } class NINAProvider extends BaseProvider { /** * Initializes a new instance of the NINAProvider class. * * @param adapter The WeatherWarnings adapter instance. * @param options The options for the provider. */ constructor(adapter, options) { super(adapter, { ...options, service: "ninaService" }, `nina`); } } class METROProvider extends BaseProvider { /** * Initializes a new instance of the METROProvider class. * * @param adapter The WeatherWarnings adapter instance. * @param options The options for the provider. */ constructor(adapter, options) { super(adapter, { ...options, service: "metroService" }, `nina`); } } class ProviderController extends import_library.BaseClass { providers = []; notificationServices = []; noWarning; refreshTimeRef = void 0; alertTimeoutRef = void 0; connection = true; name = "provider"; refreshTime = 3e5; library; pushOn = false; //globalSpeakSilentTime: ({ profil: string; day: number[]; start: number; end: number } | null)[] = []; testStatus = 0; activeMessages = 0; silentTime = { forceOff: false, profil: [] }; /** * Initializes a new instance of the ProviderController class. * * @param adapter The WeatherWarnings adapter instance. */ constructor(adapter) { super(adapter, "provider"); this.library = this.adapter.library; this.noWarning = new import_messages.MessagesClass(this.adapter, "noMessage", null, {}, this); this.noWarning.newMessage = false; this.noWarning.notDeleted = false; this.doEndOfUpdater.bind(this); } /** * Initializes the provider controller. * * Reads the config, sets some states and updates the formated data objects. * Also sets the silent time based on the config. * * @returns A promise that resolves when initialization is complete. */ async init() { this.pushOn = !this.adapter.config.notPushAtStart; this.refreshTime = this.adapter.config.refreshTime * 6e4; const typeStates = []; for (const a in messagesDef.genericWarntyp) { typeStates[parseInt(a)] = this.library.getTranslation( messagesDef.genericWarntyp[a].name ); } definitionen.statesObjectsWarnings.allService.formatedkeys.warntypegeneric.common.states = typeStates; const states = await this.adapter.getStatesAsync("provider.*"); for (const state in states) { if (state.includes(".formatedKeys.")) { const key = state.split(".").pop(); if (definitionen.statesObjectsWarnings.allService.formatedkeys[key] != void 0) { const def = definitionen.statesObjectsWarnings.allService.formatedkeys[key]; def.common.name = typeof def.common.name == "string" ? await this.library.getTranslationObj(def.common.name) : def.common.name; await this.adapter.extendObjectAsync( state.replace(`${this.adapter.name}.${this.adapter.instance}.`, ""), definitionen.statesObjectsWarnings.allService.formatedkeys[key] ); } } } const profileNames = []; if (this.adapter.config.silentTime !== void 0) { for (let p = 0; p < this.adapter.config.silentTime.length; p++) { if (!this.adapter.config.silentTime[p].speakProfile) { continue; } if (this.adapter.config.silentTime[p].silentTime.length == 0) { continue; } profileNames.push(this.adapter.config.silentTime[p].speakProfile); this.silentTime.profil.push( (this.adapter.config.silentTime[p].silentTime || []).map((item) => { const result = { day: [], start: 0, end: 0 }; if (typeof item !== "object" || item === null || typeof item.start !== "string" || typeof item.end !== "string" || item.day === null || !Array.isArray(item.day) || item.day.length == 0) { return null; } for (const a in item) { const b = a; if (b != "day") { const t = item[b].split(":"); if (Number.isNaN(t[0])) { return null; } if (!Number.isNaN(t[1]) && parseInt(t[1]) > 0) { t[1] = String(parseInt(t[1]) / 60); item[b] = String(parseFloat(t[0]) + parseFloat(t[1])); } else { item[b] = t[0]; } } if (b == "day") { result.day = item.day; } else if (b == "end") { result.end = parseFloat(item.end); } else { result.start = parseFloat(item.start); } } this.log.info( `Silent time added: Profil: ${this.adapter.config.silentTime[p].speakProfile} start: ${result.start} end: ${result.end} days: ${JSON.stringify(result.day)}` ); return result.day.length != 0 && result.start != result.end ? result : null; }).filter((f) => f != null) ); } definitionen.statesObjectsWarnings.allService.commands.silentTime.profil.common.states = profileNames; await this.library.writedp( `commands.silentTime`, void 0, definitionen.statesObjectsWarnings.allService.commands.silentTime._channel ); for (const a in definitionen.actionStates) { const dp = a; const data = definitionen.actionStates[dp]; if (!this.library.readdp(String(dp))) { await this.library.writedp(String(dp), data.default, data.def); } else { const def = definitionen.actionStates[dp].def; const obj = await this.adapter.getObjectAsync(String(dp)); if (obj) { obj.common = def.common; await this.adapter.setObjectAsync(String(dp), obj); } } } } } /** * Create notification services from the given configuration. * * @param optionList A dictionary of notification service configuration options * @returns A promise that resolves when the notification services have been created. */ async createNotificationService(optionList) { for (const a in optionList) { const options = optionList[a]; if (options === void 0) { return; } let tempAdapters = [options.adapter]; if (options.useadapterarray && options.adapters) { tempAdapters = options.adapters; } for (const a2 of tempAdapters) { options.adapter = a2; const objs = options.adapter != "" ? await this.adapter.getObjectViewAsync("system", "instance", { startkey: `system.adapter.${options.adapter}`, endkey: `system.adapter.${options.adapter}` }) : null; if (!options.useadapter || objs && objs.rows && objs.rows.length > 0) { const noti = new NotificationClass.NotificationClass( this.adapter, JSON.parse(JSON.stringify(options)) ); this.notificationServices.push(noti); await noti.init(); } else { this.log.error( `Configuration: ${options.name} is active, ${options.adapter === "none" ? `but no adapter selected!` : `but dont find ${options.adapter} adapter!`}` ); throw new Error( `Configuration: ${options.name} is active, ${options.adapter === "none" ? `but no adapter selected` : `but dont find ${options.adapter} adapter!`}` ); } } } } /** * Creates a new provider instance if it does not already exist. * * Checks the list of existing providers to see if one already exists with the specified * `warncellId` and `service`. If no such provider exists, it creates a new provider * instance based on the specified service type ('dwdService', 'uwzService', 'zamgService', or 'ninaService'). * * Throws an error if the `warncellId` type does not match the expected type for the service. * * @param options - The options used to configure the provider, including `warncellId` and `service`. * @returns The newly created provider instance or the existing provider if it already exists. * @throws An error if the service type is not defined or if the `warncellId` type is incorrect. */ createProviderIfNotExist(options) { const index = this.providers.findIndex( (p) => p && (typeof p.warncellId == "string" && p.warncellIdString == options.warncellId || typeof options.warncellId == "object" && options.warncellId.join(DIV) == p.warncellIdString) && p.getService() == options.service ); if (index == -1) { let p; switch (options.service) { case "dwdService": if (Array.isArray(options.warncellId)) { throw new Error("Error 122 warncellId is a Array"); } p = new DWDProvider(this.adapter, { ...options, warncellId: options.warncellId, providerController: this }); break; case "uwzService": if (Array.isArray(options.warncellId)) { throw new Error("Error 123 warncellId is a Array"); } p = new UWZProvider(this.adapter, { ...options, warncellId: options.warncellId, providerController: this }); break; case "zamgService": if (!Array.isArray(options.warncellId)) { throw new Error("Error 124 warncellId is not an Array"); } p = new ZAMGProvider(this.adapter, { ...options, warncellId: options.warncellId, providerController: this }); break; case "ninaService": if (!Array.isArray(options.warncellId)) { throw new Error("Error 125 warncellId is not an Array"); } p = new NINAProvider(this.adapter, { ...options, warncellId: options.warncellId, providerController: this, language: this.adapter.config.dwdLanguage }); break; default: throw new Error("Error 126 service is not defined"); } if (p) { this.providers.push(p); } return p; } this.log.error(`Attempt to create an existing provider. ${options.service}`); return this.providers[index]; } /** * Deletes the provider controller and all of its providers and notification services. * * @returns A promise that resolves when the deletion is complete. */ async delete() { await super.delete(); await this.library.memberDeleteAsync(this.providers); await this.library.memberDeleteAsync(this.notificationServices); this.providers = []; this.notificationServices = []; await this.setConnected(false); if (this.refreshTimeRef) { this.adapter.clearTimeout(this.refreshTimeRef); } if (this.alertTimeoutRef) { this.adapter.clearTimeout(this.alertTimeoutRef); } } /** * Updates all providers in endless loop. */ updateEndless = async () => { if (this.adapter.config.useTestCase) { if (++this.testStatus > 3) { this.testStatus = 1; } this.adapter.config.useTestWarnings = true; this.refreshTime = 6e4; } this.connection = false; if (this.refreshTimeRef) { this.adapter.clearTimeout(this.refreshTimeRef); } if (this.providers.length == 0) { await this.setConnected(false); return; } const updater = async (index = 0) => { if (this.unload) { return; } if (index < this.providers.length) { if (this.providers[index]) { await this.providers[index].updateData(); } index++; this.refreshTimeRef = this.adapter.setTimeout(updater, 250, index); } else { await this.doEndOfUpdater(); await this.updateAlertEndless(false); this.refreshTimeRef = this.adapter.setTimeout(this.updateEndless, this.refreshTime || 6e5); } }; updater().catch(() => { }); }; /** * Periodically updates alerts in an endless loop. * * This function sets the `speakAllowed` status and checks for alerts. * It avoids updating alerts on exact minute, second, and half-second intervals * by scheduling the next update 1.333 seconds after the last full minute. * If `endless` is true, it continues scheduling updates indefinitely. * * @param endless - A boolean indicating whether to continue updating alerts endlessly. * Defaults to true. * @returns A promise that resolves when the alert update is complete. */ updateAlertEndless = async (endless = true) => { if (this.unload) { return; } await this.setSpeakAllowed(); await this.checkAlerts(); const timeout = 61333 - Date.now() % 6e4; if (endless) { this.alertTimeoutRef = this.adapter.setTimeout(this.updateAlertEndless, timeout); } }; /** * Updates the alerts for all providers. * * Iterates over all providers, calling their `getAlertsAndWrite` method and passing the result to the next provider. * After all providers have been called, it writes the resulting alerts to the `allService.alerts` state. * * @returns A promise that resolves when the alerts have been written. */ async checkAlerts() { let reply = void 0; for (const p of this.providers) { reply = await p.getAlertsAndWrite(reply); } if (reply !== void 0) { await this.library.writeFromJson( "alerts", "allService.alerts", definitionen.statesObjectsWarnings, reply, false ); } } /** * Completes the update process for all providers. * * Sets the connection status and counts active messages across all providers. * Writes the number of active warnings to the data points for each provider and the controller. * If push notifications are enabled, sends notifications for new, removed, or all messages. * Clears messages and finishes the turn for each provider. * * @returns A promise that resolves when the update process is complete. */ async doEndOfUpdater() { await this.setConnected(); this.activeMessages = 0; for (const a of this.providers) { let am = 0; for (const b of a.messages) { if (b.notDeleted) { am++; } } await this.adapter.library.writedp( `${a.name}.activeWarnings`, am, definitionen.genericStateObjects.activeWarnings ); this.activeMessages += am; } if (this.pushOn) { for (const push of this.notificationServices) { await push.sendMessage(this.providers, ["new", "remove", "all", "removeAll"]); } } this.pushOn = true; await this.adapter.library.writedp( `${this.name}.activeWarnings`, this.activeMessages, definitionen.genericStateObjects.activeWarnings ); this.providers.forEach((a) => a.clearMessages()); this.providers.forEach((a) => a.finishTurn()); await this.library.garbageColleting(`${this.name}.activeWarnings_json`, 15e3); this.log.debug(`We have ${this.activeMessages} active messages.`); } /** * Returns true if there are providers. * * @returns If there are providers. */ providersExist() { return this.providers.length > 0; } /** * Sets the connection status. * * @param status - the status to set, defaults to the current status * @returns a promise that resolves when the status is set */ async setConnected(status = this.connection) { await this.adapter.library.writedp( `info.connection`, !!status, definitionen.genericStateObjects.info.connection ); } /** * Handles the state push command for sending messages. * * This function listens for changes in the state that include the command * 'commands.send_message.'. If such a command is detected, it attempts to * send a message through the appropriate notification service. * * @param id - The identifier of the state change event. * @returns A promise that resolves to a boolean indicating whether the * message was successfully sent through a notification service. */ async onStatePush(id) { if (!id.includes("commands.send_message.")) { return false; } id = id.replace(`${this.adapter.namespace}.`, ""); const cmd = id.split(".").pop(); const service = id.split(".").slice(0, -3).join("."); let index = -1; let providers = []; if ((index = this.providers.findIndex((a) => a.name == service)) > -1) { providers.push(this.providers[index]); } else { providers = this.providers; } let result = false; for (const push of this.notificationServices) { if (cmd == push.name && push.canManual()) { await push.sendMessage(providers, [...NotificationType.manual, "removeAll"], true); await this.library.writedp(id, false); result = true; } } return result; } /** * Completes the initialization process by writing the state objects for commands. * * This function writes the commands channel and device objects, and sets up the * send_message state objects for each notification service. Then, it subscribes to * the commands state objects and cleans up any state objects that should not exist. * * @returns A promise that resolves when initialization is complete. */ async finishInit() { for (const p of [...this.providers, this]) { const channel = `${p instanceof BaseProvider ? `${this.adapter.namespace}.${p.name}` : `${this.adapter.namespace}`}.commands`; const commandChannel = `${channel}.send_message`; await this.library.writedp( channel, void 0, definitionen.statesObjectsWarnings.allService.commands._channel ); await this.library.writedp( commandChannel, void 0, definitionen.statesObjectsWarnings.allService.commands.send_message._channel ); await this.library.writedp( `${channel}.clearHistory`, false, definitionen.statesObjectsWarnings.allService.commands.clearHistory ); const states = this.library.getStates(`${commandChannel}.*`.replace(".", "\\.")); states[`${commandChannel}`] = void 0; for (const n of this.notificationServices) { if (n.options.notifications.findIndex((a) => NotificationType.manual.indexOf(a) != -1) == -1) { continue; } if (!(p instanceof BaseProvider) || n.options.service.indexOf(p.service) != -1) { const dp = `${commandChannel}.${n.name}`; states[dp] = void 0; await this.library.writedp( dp, false, definitionen.statesObjectsWarnings.allService.commands.send_message[n.name] ); } } for (const dp in states) { if (states[dp] !== void 0) { await this.adapter.delObjectAsync(dp); this.log.debug(`Remove state ${dp}`); } } await this.adapter.subscribeStatesAsync(`${channel}.*`); } } /** * Handles the clearHistory command for sending messages. * * This function listens for changes in the state that include the command * 'commands.send_message.'. If such a command is detected, it attempts to * send a message through the appropriate notification service. * * @param id - The identifier of the state change event. * @returns A promise that resolves to a boolean indicating whether the * message was successfully sent through a notification service. */ async clearHistory(id) { if (!id.endsWith(".clearHistory")) { return false; } let targets = []; this.providers.forEach((a) => { if (id.includes(a.name)) { targets.push(a); } }); if (targets.length == 0) { targets = [...this.providers, this]; } let result = false; for (const a of targets) { try { const dp = `${a.name}.history`; await this.adapter.library.writedp(dp, "[]", definitionen.genericStateObjects.history); result = true; } catch (error) { this.log.error(error); } } if (result) { await this.library.writedp(id.replace(`${this.adapter.namespace}.`, ""), false); } return result; } /** * Set the forbidden directories for the library. This method is used to prevent certain states from being written to the database. * * @param allowedDirs - An object with the provider name as key and a boolean value for each datapoint category (warning, alerts, messages, formatedKeys). * If the value is false, the corresponding datapoints will be forbidden. */ setAllowedDirs(allowedDirs) { const dirs = []; for (const a in allowedDirs) { if (allowedDirs[a] === void 0) { continue; } if (!allowedDirs[a].dpWarning) { dirs.push(`^provider\\.${a.replace(`Service`, ``)}\\.[a-zA-Z0-9-_]+\\.warning`); } if (!allowedDirs[a].dpAlerts) { dirs.push(`^provider\\.${a.replace(`Service`, ``)}\\.[a-zA-Z0-9-_]+\\.alerts`); } if (!allowedDirs[a].dpFormated) { dirs.push(`^provider\\.${a.replace(`Service`, ``)}\\.[a-zA-Z0-9-_]+\\.formatedKeys`); } this.library.setForbiddenDirs(dirs); } } /** * Updates formatted data and writes formatted keys for messages across all providers. * * Iterates over each provider's messages and updates their formatted data. * Also writes the formatted keys for each message up to the configured * maximum number of raw warnings. * * @returns A promise that resolves when the messages have been updated. */ async updateMesssages() { for (const a of this.providers) { for (let b = 0; b < a.messages.length && b < this.adapter.config.numOfRawWarnings; b++) { await a.messages[b].updateFormatedData(); await a.messages[b].writeFormatedKeys(Number(b)); } } } /** * Sets the speak allowed state based on the provided identifier or silent time profile. * * If an ID is provided, it updates the speak state for that ID if it exists in actionStates. * If the ID's onlyAck property is true, the function returns immediately after writing the state. * * If no ID is provided or the silent time is in automatic mode, it checks the current time and * day against the configured silent time profiles to determine if speaking is allowed. * Updates the state if the allowed status has changed. * * @param id - (Optional) The identifier for the speak state to be updated. * @returns A promise that resolves to true if the operation is successful. */ async setSpeakAllowed(id = "") { if (id !== "") { id = id.replace(`${this.adapter.namespace}.`, ""); if (definitionen.actionStates[id] === void 0) { return false; } this.log.debug(`Set speak ${id.split(".").slice(-1).join(".")} to ${this.library.readdp(id).val}`); await this.library.writedp(id, this.library.readdp(id).val); if (definitionen.actionStates[id].onlyAck) { return true; } } if (this.isSilentAuto()) { const profil = this.getSpeakProfil(); let isSpeakAllowed = true; if (this.silentTime !== void 0 && this.silentTime.profil[profil] && Array.isArray(this.silentTime.profil[profil])) { const now = (/* @__PURE__ */ new Date()).getHours() + (/* @__PURE__ */ new Date()).getMinutes() / 60; const day = String((/* @__PURE__ */ new Date()).getDay()); for (const t of this.silentTime.profil[profil]) { if (t === null) { continue; } if (t.day.indexOf(day) == -1) { continue; } if (t.start < t.end) { if (t.start <= now && t.end > now) { isSpeakAllowed = false; break; } } else { if (t.start <= now || t.end > now) { isSpeakAllowed = false; break; } } isSpeakAllowed = true; } } if (isSpeakAllowed !== this.silentTime.shouldSpeakAllowed) { await this.library.writedp( `commands.silentTime.isSpeakAllowed`, isSpeakAllowed, definitionen.statesObjectsWarnings.allService.commands.silentTime.isSpeakAllowed ); this.silentTime.shouldSpeakAllowed = isSpeakAllowed; } } else { this.silentTime.shouldSpeakAllowed = void 0; } return true; } /** * Returns true if the silent time is in automatic mode, false otherwise. * * In au