UNPKG

iobroker.solarviewdatareader

Version:
843 lines (842 loc) 24.3 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 __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 utils = __toESM(require("@iobroker/adapter-core")); var net = __toESM(require("node:net")); class Solarviewdatareader extends utils.Adapter { tout; jobSchedule; chkCnt; conn; lastCommand; commandQueue; isProcessingQueue; isProcessingCmd; isError; constructor(options = {}) { super({ ...options, name: "solarviewdatareader" }); this.on("ready", this.onReady.bind(this)); this.on("unload", this.onUnload.bind(this)); this.lastCommand = ""; this.commandQueue = []; this.isProcessingQueue = false; this.isProcessingCmd = false; this.chkCnt = 0; this.isError = false; } async createObject(id, obj) { await this.setObjectNotExistsAsync(id, obj); await this.extendObject(id, obj); const currentState = await this.getStateAsync(id); const stCommon = obj.common; if (currentState === null && stCommon.def !== void 0) { await this.setState(id, stCommon.def, true); } } async createGlobalObjects() { const options = [ [ "info.connection", { type: "state", common: { name: "connection", type: "boolean", role: "indicator.connected", def: false, read: true, write: false, desc: "Solarview connection state", unit: "" }, native: {} } ], [ "info.lastUpdate", { type: "state", common: { name: "lastUpdate", type: "string", role: "date", def: (/* @__PURE__ */ new Date("1900-01-01T00:00:00")).toString(), read: true, write: false, desc: "Last connection date/time", unit: "" }, native: {} } ] ]; for (const option of options) { await this.createObject(...option); } } async createSolarviewObjects(device, additional = false) { let options = [ [device, { type: "channel", common: { name: device }, native: {} }], [ `${device}.current`, { type: "state", common: { name: "current", type: "number", role: "value.power", def: 0, read: true, write: false, desc: "Current PAC", unit: "W" }, native: {} } ], [ `${device}.daily`, { type: "state", common: { name: "daily", type: "number", role: "value.energy", def: 0, read: true, write: false, desc: "Daily yield", unit: "kWh" }, native: {} } ], [ `${device}.monthly`, { type: "state", common: { name: "monthly", type: "number", role: "value.energy", def: 0, read: true, write: false, desc: "Monthly yield", unit: "kWh" }, native: {} } ], [ `${device}.yearly`, { type: "state", common: { name: "yearly", type: "number", role: "value.energy", def: 0, read: true, write: false, desc: "Yearly yield", unit: "kWh" }, native: {} } ], [ `${device}.total`, { type: "state", common: { name: "total", type: "number", role: "value.energy", def: 0, read: true, write: false, desc: "Total yield", unit: "kWh" }, native: {} } ] ]; if (additional) { const additionalOptions = [ [ `${device}.udc`, { type: "state", common: { name: "udc", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Generator voltage", unit: "V" }, native: {} } ], [ `${device}.idc`, { type: "state", common: { name: "idc", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Generator current", unit: "A" }, native: {} } ], [ `${device}.udcb`, { type: "state", common: { name: "udcb", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Generator voltage", unit: "V" }, native: {} } ], [ `${device}.idcb`, { type: "state", common: { name: "idcb", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Generator current", unit: "A" }, native: {} } ], [ `${device}.udcc`, { type: "state", common: { name: "udcc", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Generator voltage", unit: "V" }, native: {} } ], [ `${device}.idcc`, { type: "state", common: { name: "idcc", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Generator current", unit: "A" }, native: {} } ], [ `${device}.udcd`, { type: "state", common: { name: "udcd", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Generator voltage", unit: "V" }, native: {} } ], [ `${device}.idcd`, { type: "state", common: { name: "idcd", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Generator current", unit: "A" }, native: {} } ], [ `${device}.ul1`, { type: "state", common: { name: "ul1", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Mains voltage", unit: "V" }, native: {} } ], [ `${device}.il1`, { type: "state", common: { name: "il1", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Mains current", unit: "A" }, native: {} } ], [ `${device}.ul2`, { type: "state", common: { name: "ul2", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Mains voltage", unit: "V" }, native: {} } ], [ `${device}.il2`, { type: "state", common: { name: "il2", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Mains current", unit: "A" }, native: {} } ], [ `${device}.ul3`, { type: "state", common: { name: "ul3", type: "number", role: "value.voltage", def: 0, read: true, write: false, desc: "Mains voltage", unit: "V" }, native: {} } ], [ `${device}.il3`, { type: "state", common: { name: "il3", type: "number", role: "value.current", def: 0, read: true, write: false, desc: "Mains current", unit: "A" }, native: {} } ], [ `${device}.tkk`, { type: "state", common: { name: "tkk", type: "number", role: "value.temperature", def: 0, read: true, write: false, desc: "Temperature", unit: "\xB0C" }, native: {} } ] ]; options = options.concat(additionalOptions); } for (const option of options) { await this.createObject(...option); } } //for compatibility reasons. Minute interval changed to seconds. async adjustIntervalToSeconds() { const adapterObj = await this.getForeignObjectAsync( `system.adapter.${this.namespace}` ); if (!this.config.interval_seconds) { if (adapterObj) { adapterObj.native.interval_seconds = true; adapterObj.native.intervalVal *= 60; this.log.warn("Interval changed to seconds!"); this.log.info("Interval attribute changed! Please check the configuration"); this.log.info("Adapter restarts"); } } } // Nullen voranstellen - add Leading Zero aLZ(n) { return n <= 9 ? `0${n}` : n.toString(); } calcChecksum(inputString) { const buffer = Buffer.from(inputString); let sum = 0; let index = 0; for (let i = 0; i < buffer.length; i++) { sum = (sum + buffer[i]) % 128; if (buffer[i] === 125) { index = i + 2; break; } } const result = { result: buffer[index] === sum, chksum: sum, ind: index, data: buffer }; return result; } preprocessSolarviewData(response) { let sv_data = response.toString("ascii"); sv_data = sv_data.replace(/[{}]+/g, ""); return sv_data.split(","); } getSolarviewPrefix(dataCode) { const prefixMap = { "00": "pvig.", "01": "pvi1.", "02": "pvi2.", "03": "pvi3.", "04": "pvi4.", 10: "scm0.", 11: "scm1.", 12: "scm2.", 13: "scm3.", 14: "scm4.", 21: "d0supply.", 22: "d0consumption." }; return prefixMap[dataCode] || ""; } async updateSolarviewStates(sv_data, sv_prefix) { try { this.updateState(`${sv_prefix}current`, sv_data, 10); if (sv_prefix === "pvig.") { await this.handleCCUUpdate(sv_data); } this.updateState(`${sv_prefix}daily`, sv_data, 6); this.updateState(`${sv_prefix}monthly`, sv_data, 7); this.updateState(`${sv_prefix}yearly`, sv_data, 8); this.updateState(`${sv_prefix}total`, sv_data, 9); if (sv_data.length >= 23) { this.updateExtendedStates(sv_data, sv_prefix); } } catch (error) { this.errorHandler(`updateSolarviewStates`, error); throw error; } } async handleCCUUpdate(sv_data) { try { if (this.config.setCCU) { const obj = await this.findForeignObjectAsync(this.config.CCUSystemV, null); if (obj && obj.name) { this.log.debug(`set CCU system variable: ${this.config.CCUSystemV}`); this.setForeignState(this.config.CCUSystemV, { val: Number(sv_data[10]), ack: false }); } else { this.log.error(`CCU system variable ${this.config.CCUSystemV} does not exist!`); } } } catch (error) { this.errorHandler(`handleCCUUpdate`, error); throw error; } } updateExtendedStates(sv_data, sv_prefix) { this.updateState(`${sv_prefix}udc`, sv_data, 11); this.updateState(`${sv_prefix}idc`, sv_data, 12); this.updateState(`${sv_prefix}udcb`, sv_data, 13); this.updateState(`${sv_prefix}idcb`, sv_data, 14); this.updateState(`${sv_prefix}udcc`, sv_data, 15); this.updateState(`${sv_prefix}idcc`, sv_data, 16); this.updateState(`${sv_prefix}udcd`, sv_data, 17); this.updateState(`${sv_prefix}idcd`, sv_data, 18); if (sv_data.length === 27) { this.updateState(`${sv_prefix}ul1`, sv_data, 19); this.updateState(`${sv_prefix}il1`, sv_data, 20); this.updateState(`${sv_prefix}ul2`, sv_data, 21); this.updateState(`${sv_prefix}il2`, sv_data, 22); this.updateState(`${sv_prefix}ul3`, sv_data, 23); this.updateState(`${sv_prefix}il3`, sv_data, 24); this.updateState(`${sv_prefix}tkk`, sv_data, 25); } else if (sv_data.length === 23) { this.updateState(`${sv_prefix}ul1`, sv_data, 19); this.updateState(`${sv_prefix}il1`, sv_data, 20); this.updateState(`${sv_prefix}tkk`, sv_data, 21); } } updateState(stateName, sv_data, index) { const value = Number(sv_data[index]); this.setStateChanged(stateName, { val: value, ack: true }); } async onReady() { try { const starttime = this.config.intervalstart.split(":").slice(0, 2).join(":"); const endtime = this.config.intervalend.split(":").slice(0, 2).join(":"); this.log.info( `start solarview ${this.config.ipaddress}:${this.config.port} - polling interval: ${this.config.intervalVal}s (${starttime} to ${endtime})` ); this.log.info(`d0 converter: ${this.config.d0converter.toString()}`); await this.createGlobalObjects(); await this.createSolarviewObjects("pvig", false); if (this.config.d0converter) { await this.createSolarviewObjects("d0supply", false); } if (this.config.d0converter) { await this.createSolarviewObjects("d0consumption", false); } if (this.config.scm0) { await this.createSolarviewObjects("scm0", false); } if (this.config.scm1) { await this.createSolarviewObjects("scm1", false); } if (this.config.scm2) { await this.createSolarviewObjects("scm2", false); } if (this.config.scm3) { await this.createSolarviewObjects("scm3", false); } if (this.config.scm4) { await this.createSolarviewObjects("scm4", false); } if (this.config.pvi1) { await this.createSolarviewObjects("pvi1", true); } if (this.config.pvi2) { await this.createSolarviewObjects("pvi2", true); } if (this.config.pvi3) { await this.createSolarviewObjects("pvi3", true); } if (this.config.pvi4) { await this.createSolarviewObjects("pvi4", true); } if (!this.config.interval_seconds) { await this.adjustIntervalToSeconds.call(this); } this.conn = new net.Socket(); this.conn.setTimeout(2e3); this.conn.on("data", this.onDataHandler.bind(this)); this.conn.on("close", this.onCloseHandler.bind(this)); this.conn.on("error", this.onErrorHandler.bind(this)); this.conn.on("timeout", this.onTimeoutHandler.bind(this)); await this.setCmdQueue(); this.jobSchedule = setInterval(async () => { try { await this.setCmdQueue(); } catch (error) { this.log.error(`onReady.schedule: ${error.message}`); } }, this.config.intervalVal * 1e3); } catch (error) { this.errorHandler(`onReady`, error); } } onTimeoutHandler() { this.log.error("onTimeoutHandler: socket timeout"); this.conn.end(); } async onDataHandler(data) { try { await this.processData(data); this.conn.end(); } catch (error) { this.errorHandler(`conn.on data`, error); } } processData = async (data) => { try { const strdata = this.preprocessSolarviewData(data); const id = this.getSolarviewPrefix(strdata[0]); const cs = this.calcChecksum(data.toString("ascii")); if (cs.result) { await this.handleChecksumSuccess(strdata, id, data); } else { this.handleChecksumFailure(strdata, cs); } } catch (error) { this.errorHandler(`processData`, error); throw error; } }; async handleChecksumSuccess(sv_data, sv_prefix, response) { try { this.chkCnt = 0; this.log.debug(`${response.toString("ascii")}`); await this.updateSolarviewStates(sv_data, sv_prefix); const sDate = `${sv_data[3]}-${this.aLZ(parseInt(sv_data[2]))}-${this.aLZ(parseInt(sv_data[1]))} ${this.aLZ(parseInt(sv_data[4]))}:${this.aLZ(parseInt(sv_data[5]))}`; this.setStateChanged("info.lastUpdate", { val: sDate, ack: true }); this.setStateChanged("info.connection", { val: true, ack: true }); } catch (error) { this.errorHandler(`handleChecksumSuccess`, error); throw error; } } handleChecksumFailure(sv_data, csum) { if (csum.chksum !== 0) { if (csum.data.toString("ascii") != ";-)\n") { this.chkCnt += 1; this.log.warn(`checksum not correct! ${sv_data[0]}: ${csum.data.toString("ascii")}`); } else { this.log.warn(`command ${this.lastCommand} not supported!`); } } } async onCloseHandler() { try { if (this.lastCommand) { this.log.debug(`Connection closed after executing command: ${this.lastCommand}`); } else { this.log.debug("Connection closed"); } this.isProcessingCmd = false; if (this.chkCnt > 3) { await this.setState("info.connection", false, true); this.log.warn("Solarview Server is not reachable"); this.chkCnt = 0; } } catch (error) { this.errorHandler(`conn.on close`, error); } } onErrorHandler(error) { this.errorHandler(`conn.on error - cmd: ${this.lastCommand}`, error); } _sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } connectAsync = (port, ip_address) => { return new Promise((resolve, reject) => { this.conn.connect(port, ip_address, () => { resolve(); }); this.conn.on("error", (err) => { reject(err); }); }); }; async setCmdQueue() { try { const commands = []; const { d0converter, scm0, scm1, scm2, scm3, scm4, pvi1, pvi2, pvi3, pvi4, intervalstart, intervalend } = this.config; const starttime = intervalstart.split(":").slice(0, 2).join(":"); let endtime = intervalend.split(":").slice(0, 2).join(":"); endtime = endtime === "00:00" ? "23:59" : endtime; const dnow = /* @__PURE__ */ new Date(); const dstart = /* @__PURE__ */ new Date(`${dnow.getFullYear()}-${dnow.getMonth() + 1}-${dnow.getDate()} ${starttime}`); const dend = /* @__PURE__ */ new Date(`${dnow.getFullYear()}-${dnow.getMonth() + 1}-${dnow.getDate()} ${endtime}`); if (dnow >= dstart && dnow <= dend) { commands.push("00*"); if (d0converter) { commands.push("21*"); } if (pvi1) { commands.push("01*"); } if (pvi2) { commands.push("02*"); } if (pvi3) { commands.push("03*"); } if (pvi4) { commands.push("04*"); } } if (d0converter) { commands.push("22*"); } if (scm0) { commands.push("10*"); } if (scm1) { commands.push("11*"); } if (scm2) { commands.push("12*"); } if (scm3) { commands.push("13*"); } if (scm4) { commands.push("14*"); } this.commandQueue.push(...commands); await this.processQueue(); } catch (error) { this.errorHandler(`setCmdQueue`, error); } } async processQueue() { if (this.isProcessingQueue) { this.log.warn(`processQueue: command queue not empty! Waiting for solarview server ...`); return; } this.isProcessingQueue = true; while (this.commandQueue.length > 0) { if (!this.isProcessingCmd) { const cmd = this.commandQueue.shift(); if (cmd) { try { this.isProcessingCmd = true; await this.executeCommand(cmd); if (this.isError === true) { this.log.info("Server connection is now working again!"); this.isError = false; } } catch { this.isError = true; this.commandQueue.length = 0; this.isProcessingQueue = false; this.isProcessingCmd = false; } } } await this._sleep(500); } this.isProcessingQueue = false; } async executeCommand(cmd) { this.log.debug(`Attempting to execute command: ${cmd}`); this.lastCommand = cmd; await this.connectAsync(this.config.port, this.config.ipaddress); this.conn.write(cmd); this.log.debug(`Command successfully sent: ${cmd}`); } onUnload(callback) { try { clearInterval(this.jobSchedule); clearTimeout(this.tout); this.conn.destroy(); this.setStateChanged("info.connection", { val: false, ack: true }); this.log.info("cleaned everything up..."); callback(); } catch (error) { this.errorHandler("onUnload", error); callback(); } } errorHandler(errTitle, error) { if (error instanceof Error) { if (error.message.includes("EHOSTUNREACH") || error.message.includes("ECONNREFUSED")) { this.log.warn(`${errTitle}: Solarview server is not reachable! Please check the server.`); } else { this.log.error(`${errTitle}: ${error.message} Stacktrace: ${error.stack}`); } } else if (typeof error === "string") { this.log.error(`${errTitle}: ${error}`); } else { this.log.error(`${errTitle}: Unknown error occurred: {error}`); } } } if (require.main !== module) { module.exports = (options) => new Solarviewdatareader(options); } else { (() => new Solarviewdatareader())(); } //# sourceMappingURL=main.js.map