UNPKG

iobroker.lovelace

Version:

With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI

217 lines (216 loc) 8.19 kB
"use strict"; const WS_OPEN = 1; const entityDataSingleton = require("../../../lib/dataSingleton"); const { iobState2EntityState } = require("../converters/genericConverter"); const { getHistoryGated, boundHistoryCount } = require("../historyGate"); class LogbookModule { adapter; getUsedEntityIDs; instances; /** * Create a new logbook module. * * @param options - options object with adapter and getUsedEntityIDs function * @param options.adapter - ioBroker adapter instance * @param options.getUsedEntityIDs - function returning all currently used HA entity ids */ constructor(options) { this.adapter = options.adapter; this.getUsedEntityIDs = options.getUsedEntityIDs; this.instances = []; let objectType = "instance"; const params = { startkey: "system.adapter.", endkey: "system.adapter.\u9999" }; if (this.adapter.config.logbookSource === "user") { objectType = "user"; params.startkey = "system.user."; params.endkey = "system.user.\u9999"; } if (this.adapter.config.logbookSource !== "none") { this.adapter.getObjectView("system", objectType, params, (err, instances) => { if (err) { this.adapter.log.warn(`Could not get instances: ${err}`); } else if (instances) { for (const row of instances.rows) { if (row.value && row.value._id) { const id = row.value._id; let name = id.split(".").splice(2).join("."); if (this.adapter.config.logbookSource === "user") { name = row.value.common.name; } this.instances.push({ name, id }); } } } }); } } /** * Send a logbook response to the frontend. * * @param ws - websocket client to send to * @param id - subscription message id * @param startTime - start timestamp in milliseconds * @param endTime - end timestamp in milliseconds * @param results - array of entity+state pairs to include in the response * @param partial - whether this is a partial response (more to follow) */ sendLogbookResponse(ws, id, startTime, endTime, results = [], partial = false) { const message = { id: Number(id), type: "event", event: { events: [] } }; const event = message.event; if (startTime) { event.start_time = startTime / 1e3; } if (endTime) { event.end_time = endTime / 1e3; } if (partial) { event.partial = true; } const events = event.events; for (const result of results) { let from = void 0; if (this.adapter.config.logbookSource === "user") { from = result.state.user; } if (this.adapter.config.logbookSource === "adapter") { from = result.state.from; } events.push({ when: result.state.ts / 1e3, state: typeof result.entity.context.STATE.historyParser === "function" ? String(result.entity.context.STATE.historyParser(String(id), result.state.val)) : String(iobState2EntityState(result.entity, result.state.val)), entity_id: result.entity.entity_id, context_user_id: from }); } events.sort((a, b) => a.when - b.when); ws.send(JSON.stringify(message)); } /** * Process a message from the frontend. * * @param ws - websocket connection to the client * @param message - the message from the frontend */ async processMessage(ws, message) { if (message.type === "logbook/event_stream") { try { ws.send(JSON.stringify({ id: Number(message.id), type: "result", success: true, result: null })); ws._subscribes.logbook = ws._subscribes.logbook || []; const startTime = new Date(message.start_time).getTime(); const endTime = new Date(message.end_time).getTime(); const entityIds = message.entity_ids || this.getUsedEntityIDs(); this.adapter.log.debug(`Logbook subscription ${String(message.id)} for ${JSON.stringify(entityIds)}`); const queryEnd = Math.min(Date.now(), endTime); if (Number.isNaN(startTime) || Number.isNaN(queryEnd) || startTime >= queryEnd) { this.sendLogbookResponse(ws, message.id, startTime, endTime, [], true); setTimeout(() => this.sendLogbookResponse(ws, message.id, startTime, endTime, []), 300); return true; } if (!this.adapter.config.history) { this.adapter.log.warn(`History instance is not selected in the settings -> logbook won't work`); this.sendLogbookResponse(ws, message.id, startTime, endTime, []); return true; } const idsToWatch = []; const promises = []; const results = []; const options = { start: startTime, end: queryEnd, count: boundHistoryCount(this.adapter.config.historyMaxCount), aggregate: "onchange", from: true, returnNewestEntries: true, ack: true, user: true }; for (const entityId of entityIds) { const entity = entityDataSingleton.entityId2Entity[entityId]; if (entity) { if (entity.context && entity.context.STATE) { const id = entity.context.STATE.getId || entity.context.STATE.setId; if (id) { idsToWatch.push({ iobStateId: id, entity }); promises.push( getHistoryGated(this.adapter, this.adapter.config.history, { id, options }).then((stateResult) => { for (const state of stateResult.result) { if (state !== null) { results.push({ entity, state }); } } }) ); } } } else { this.adapter.log.warn(`Unknown entity id ${entityId}`); } } await Promise.all(promises); this.sendLogbookResponse(ws, message.id, startTime, endTime, results, true); setTimeout(() => this.sendLogbookResponse(ws, message.id, startTime, endTime, []), 300); const logbookSubs = ws._subscribes.logbook; const subscription = logbookSubs.find((entry) => entry.id === Number(message.id)); if (subscription) { subscription.idsToWatch = subscription.idsToWatch.concat(idsToWatch); } else { logbookSubs.push({ id: Number(message.id), idsToWatch }); } } catch (e) { this.adapter.log.error(`Could not create logbook answer: ${e.stack}`); this.sendLogbookResponse(ws, message.id, void 0, void 0, []); } return true; } else if (message.type === "config/auth/list") { const result = this.instances; ws.send(JSON.stringify({ id: message.id, type: "result", success: true, result })); return true; } else if (message.type === "trace/contexts") { ws.send(JSON.stringify({ id: message.id, type: "result", success: true, result: {} })); return true; } return false; } /** * Handle state changes. Check if they need to be added to the logbook. * * @param id - ioBroker state id that changed * @param state - the new state value * @param websocketServer - websocket server to iterate connected clients */ onStateChange(id, state, websocketServer) { if (state) { if (websocketServer) { for (const client of websocketServer.clients) { if (client._subscribes.logbook && client.readyState === WS_OPEN) { for (const subscription of client._subscribes.logbook) { const idAndEntity = subscription.idsToWatch.find((entry) => id === entry.iobStateId); if (idAndEntity) { this.sendLogbookResponse(client, subscription.id, void 0, void 0, [ { state, entity: idAndEntity.entity } ]); } } } } } } } } module.exports = LogbookModule; //# sourceMappingURL=logbook.js.map