UNPKG

iobroker.growatt

Version:

ioBroker Growatt Adapter to communiacte with ShineAPI

914 lines (884 loc) 33.4 kB
/* * Created with @iobroker/create-adapter v1.26.0 */ // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter const API = require('growatt'); const utils = require('@iobroker/adapter-core'); const growartyp = { INUM_0_100: { type: 'number', role: 'value', min: 0, max: 100, step: 1, read: true, write: true }, INUM_0_24: { type: 'number', role: 'value', min: 0, max: 24, step: 1, read: true, write: true }, INUM_0_60: { type: 'number', role: 'value', min: 0, max: 60, step: 1, read: true, write: true }, BOOL: { type: 'boolean', role: 'value', read: true, write: true }, STIME_H_MIN: { type: 'string', role: 'value', read: true, write: true }, DATETIME: { type: 'number', role: 'value.time', read: true, write: true }, INUM_0_1: { type: 'number', role: 'value', min: 0, max: 1, step: 1, read: true, write: true }, INUM_0_2: { type: 'number', role: 'value', min: 0, max: 2, step: 1, read: true, write: true }, }; const SETTINGS = 'settings'; // Load your modules here, e.g.: // const fs = require("fs"); function getTime() { return new Date().getTime(); } function getTimeDiff(start) { return getTime() - start; } const getJSONCircularReplacer = () => { const seen = new WeakMap(); return (key, val) => { const value = val; if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return `loop on ${seen.get(value)}`; } seen.set(value, key); } return value; }; }; /** * Is called to decrypt the Password * @param {key} the secret * @param {value} the encrypted password * */ function decrypt(key, value) { let result = ''; for (let i = 0; i < value.length; i += 1) { result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i)); } return result; } class Growatt extends utils.Adapter { /** * @param {Partial<utils.AdapterOptions>} [options={}] */ constructor(options) { super({ ...options, name: 'growatt', }); this.callTimeout = null; this.processTimeout = null; this.objNames = {}; this.on('ready', this.onReady.bind(this)); this.on('stateChange', this.onStateChange.bind(this)); this.on('unload', this.onUnload.bind(this)); this.on('message', this.onMessage.bind(this)); } /** * Is called when databases are connected and adapter received configuration. */ async onReady() { this.getForeignObject('system.config', (errFO, obj) => { this.config.objUpdate = this.config.objUpdate || {}; this.config.objOffset = this.config.objOffset || {}; // ! for stateChange this.subscribeStates('*.read'); this.subscribeStates('*.write'); if (!this.supportsFeature || !this.supportsFeature('ADAPTER_AUTO_DECRYPT_NATIVE')) { if (obj && obj.native && obj.native.secret) { this.config.password = decrypt(obj.native.secret, this.config.password); this.config.shareKey = decrypt(obj.native.secret, this.config.shareKey); } else { this.config.password = decrypt('Zgfr56gFe87jJOM', this.config.password); this.config.shareKey = decrypt('Zgfr56gFe87jJOM', this.config.shareKey); } } if (typeof this.config.webTimeout === 'undefined' || this.config.webTimeout === '') this.config.webTimeout = 60; if (typeof this.config.processTimeout === 'undefined' || this.config.processTimeout === '') this.config.processTimeout = 600; if (typeof this.config.sessionHold === 'undefined' || this.config.sessionHold === '') this.config.sessionHold = true; if (typeof this.config.sessionTime === 'undefined' || this.config.sessionTime === '') this.config.sessionTime = 0; if (typeof this.config.cycleTime === 'undefined' || this.config.cycleTime === '') this.config.cycleTime = 30; if (typeof this.config.errorCycleTime === 'undefined' || this.config.errorCycleTime === '') this.config.errorCycleTime = 120; if (typeof this.config.indexCandI === 'undefined' || this.config.indexCandI === '') this.config.indexCandI = false; this.getStates(`${this.name}.${this.instance}.*`, (errGS, states) => { Object.keys(states).forEach(id => { const ebene = id.toString().split('.'); ebene.shift(); ebene.shift(); if (ebene[0] !== 'info' && ebene[3] !== SETTINGS && ebene.length > 1) { const ownID = ebene.join('.'); const ownIDsearch = ownID.toLowerCase(); if (this.config.objUpdate[ownIDsearch] && this.config.objUpdate[ownIDsearch].action === 'delete') { this.delObject(ownID); this.log.info(`deleted: ${ownID}`); } else if ( (!this.config.weather && ebene.length > 1 && ebene[1].toLowerCase() === 'weather') || (!this.config.faultlog && ebene.length > 1 && ebene[1].toLowerCase() === 'faultlog') || (!this.config.totalData && ebene.length > 3 && ebene[3].toLowerCase() === 'totaldata') || (!this.config.statusData && ebene.length > 3 && ebene[3].toLowerCase() === 'statusdata') || (!this.config.plantData && ebene.length > 1 && ebene[1].toLowerCase() === 'plantdata') || (!this.config.deviceData && ebene.length > 3 && ebene[3].toLowerCase() === 'devicedata') || (!this.config.historyLast && ebene.length > 3 && ebene[3].toLowerCase() === 'historylast') || (!this.config.chartLast && ebene.length > 3 && ebene[3].toLowerCase() === 'chart') ) { this.delObject(ownID); this.log.info(`deleted: ${ownID}`); } else if (this.objNames[ownIDsearch]) { this.log.warn(`${this.objNames[ownIDsearch]} exists twice: ${ownID}`); } else if ( ebene.length > 5 && ebene[3].toLowerCase() === 'historylast' && (ebene[4] === 'calendar' || ebene[4] === 'time') && (ebene[5] === 'year' || ebene[5] === 'month' || ebene[5] === 'dayOfMonth' || ebene[5] === 'hourOfDay' || ebene[5] === 'minute' || ebene[5] === 'second') ) { this.delObject(ownID); this.log.info(`deleted: ${ownID}`); } else { this.objNames[ownIDsearch] = ownID; } } else if (!this.config.settings && ebene.length > 1 && ebene[3] === SETTINGS) { const ownID = ebene.join('.'); this.delObject(ownID); this.log.info(`deleted: ${ownID}`); } }); this.callRun = true; this.growattData(); }); }); } /** * Is called when adapter shuts down - callback has to be called under any circumstances! * @param {() => void} callback */ onUnload(callback) { try { this.callRun = false; clearTimeout(this.processTimeout); clearTimeout(this.callTimeout); this.growattLogout(); this.setState('info.connection', { val: false, ack: true }); callback(); // eslint-disable-next-line no-unused-vars } catch (e) { callback(); } } /** * Parses the data from the website into objects. Is called recrusively. * @param {object} plantData * @param {path} path to object * @param {key} the key in the object */ async storeData(plantData, path, key) { const ele = path + key; const eleSearch = ele.toLowerCase(); this.log.silly(`storeData for ${ele}`); let data = plantData[key]; if (typeof data === 'object') { this.parseData(data, `${ele}.`); } else { if (!(typeof this.config.objUpdate[eleSearch] === 'undefined') && this.config.objUpdate[eleSearch].action !== 'normal') { return; } let objType = 'string'; let objRole = 'value'; if (key.toLowerCase().includes('name'.toLowerCase())) { data = data.toString(); } if (typeof data === 'number') { objType = 'number'; } else { data = data.toString(); // Date: yyyy-mm-dd hh:mi:ss if ( data.match('^\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d$') || data.match('^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\dZ$') ) { data = new Date(data).getTime(); objType = 'number'; objRole = 'value.time'; // Date: yyyy-mm-dd hh:mi } else if (data.match('^\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d$')) { data = new Date(`${data}:00`).getTime(); objType = 'number'; objRole = 'value.time'; // Date: yyyy-mm-dd } else if (data.match('^\\d\\d\\d\\d-\\d\\d-\\d\\d$')) { data = new Date(data).getTime(); objType = 'number'; objRole = 'date'; // number: -123 or +123.45 } else if (data.match('^(\\+|\\-)?\\d+(\\.\\d*)?$')) { data = parseFloat(data); objType = 'number'; // json: {...} or [...] } else if (data.match('^({.*}|\\[.*\\])$')) { objRole = 'json'; // boolean: true or false } else if (data.match('^(true)|(false)$')) { data = data === 'true'; objType = 'boolean'; } } if (objType === 'number' && !(typeof this.config.objOffset[eleSearch] === 'undefined') && this.config.objOffset[eleSearch].offset) { data += this.config.objOffset[eleSearch].offset; } if (typeof this.objNames[eleSearch] === 'undefined') { this.log.silly(`Create object not exists ${ele} type:${objType} role:${objRole}`); await this.setObjectNotExistsAsync(ele, { type: 'state', common: { name: key, type: objType, role: objRole, read: true, write: false, }, native: {}, }).catch(e => { this.log.error(`setObjectNotExists:${e}`); }); this.log.info(`added: ${ele}`); this.objNames[eleSearch] = ele; } this.log.silly(`Set value ${this.objNames[eleSearch]} type ${objType} : ${data}`); this.setState(this.objNames[eleSearch], { val: data, ack: true }); } } /** * loads the settings of the inverter and pastes the settings. * @param {string} path to id * @param {string} growattType * @param {string} setting * @param {string} sn */ async readSetting(path, growattType, setting, sn) { if (this.growatt) { this.growatt .getInverterSetting(growattType, setting, sn) .then(r => { this.log.debug(`Read inverter setting ${setting} : ${JSON.stringify(r, getJSONCircularReplacer())}`); if (r.success) { const params = Object.keys(r); params.forEach(p => { if (p.startsWith('param')) { this.setState(`${path}.values.${p}`, { val: r[p], ack: true }); } }); } this.setState(`${path}.read`, { val: r.success, ack: true }); }) .catch(e => { this.log.warn(`Read inverter settings ${setting}:${e}`); }); } } /** * writes the settings to the inverter. * @param {string} path to id * @param {string} growattType * @param {string} setting * @param {string} sn * @param {object param} set */ async writeSetting(path, growattType, setting, sn, set) { if (this.growatt && set && set.param) { const runState = []; const values = {}; const paramKeys = Object.keys(set.param); paramKeys.forEach(param => { runState.push( this.getStateAsync(`${path}.values.${param}`).then(s => { values[param] = s.val; }) ); }); await Promise.all(runState); this.growatt .setInverterSetting(growattType, setting, sn, values) .then(a => { this.setState(`${path}.write`, { val: a.success, ack: true }); this.setState(`${path}.msg`, { val: a.msg, ack: true }); this.log.debug(`${typeof a === 'object' ? JSON.stringify(a, getJSONCircularReplacer()) : a}`); if (a.success) { this.readSetting(path, growattType, setting, sn); } }) .catch(e => { this.setState(`${path}.write`, { val: false, ack: true }); this.setState(`${path}.msg`, { val: `${e}`, ack: true }); }); this.log.debug(`write inverter setting ${growattType}, ${setting}, ${sn}, ${JSON.stringify(values, getJSONCircularReplacer())}`); } } /** * Called when a subscribed status changes * @param {string} id * @param {ioBroker.State | null | undefined} state */ async onStateChange(id, state) { if (state) { if (!state.ack && state.val === true) { const obj = await this.getObjectAsync(id); const splitid = id.split('.'); splitid.pop(); const path = splitid.join('.'); if (obj.native && obj.native.action === 'read') { this.readSetting(path, obj.native.growattType, obj.native.setting, obj.native.sn); if (obj.native.set && obj.native.set.subRead) { obj.native.set.subRead.forEach(read => { this.readSetting(path, obj.native.growattType, read, obj.native.sn); }); } } else if (obj.native && obj.native.action === 'write') { this.writeSetting(path, obj.native.growattType, obj.native.setting, obj.native.sn, obj.native.set); } } } } /** * loads the settings of the inverter and pastes the settings. * @param {object} plantData */ async loadSettings(plantDatas) { /** Creates an iobroker state or update the properties * @param {this} t * @param {string} ele * @param {string} name * @param {object} common * @param {any} def * @param {object} native */ function createS(t, ele, name, common, def, native) { t.log.silly(`Create object not exists ${ele} type:${common} def:${def} native:${native}`); const o = { type: 'state', common: { name, }, native: {}, }; Object.assign(o.common, common); if (typeof def !== 'undefined' && def !== null) { o.common.def = def; } if (typeof native !== 'undefined' && native !== null) { Object.assign(o.native, native); } t.setObjectNotExists(ele, o); t.setObject(ele, o); } /** Creates an iobroker channel or update the properties * @param {this} t * @param {string} ele * @param {string} name */ function createC(t, ele, name) { t.log.silly(`Create object not exists ${ele} `); const o = { type: 'channel', common: { name, }, native: {}, }; t.setObjectNotExists(ele, o); t.setObject(ele, o); } if (plantDatas) { const plantDataKeys = Object.keys(plantDatas); plantDataKeys.forEach(plantDataKey => { const plantData = plantDatas[plantDataKey]; if (plantData.devices) { const snKeys = Object.keys(plantData.devices); if (snKeys) { snKeys.forEach(sn => { if (plantData.devices[sn].growattType) { const { growattType } = plantData.devices[sn]; const path = `${plantDataKey}.devices.${sn}.${SETTINGS}.`; if (this.growatt) { const com = this.growatt.getInverterCommunication(growattType); if (com) { const sets = Object.keys(com); sets.forEach(setting => { const set = com[setting]; this.log.silly(`getInverterCommunication ${path} answers ${setting} ${JSON.stringify(set, getJSONCircularReplacer())}`); if (!set.isSubread) { createC(this, path + setting, set.name); createS( this, `${path + setting}.write`, 'Write to the inverter', { type: 'boolean', role: 'value', read: true, write: true }, false, { set, sn, growattType, setting, action: 'write', } ); createS( this, `${path + setting}.read`, 'read from the inverter', { type: 'boolean', role: 'value', read: true, write: true }, false, { set, sn, growattType, setting, action: 'read', } ); createS(this, `${path + setting}.msg`, 'answer for write from the inverter', { type: 'string', role: 'value', read: true, write: false, }); const paramKeys = Object.keys(set.param); paramKeys.forEach(param => { const p = set.param[param]; const t = growartyp[p.type]; if (t) { if (p.values) { t.states = {}; Object.assign(t.states, p.values); } if (p.unit) { t.unit = p.unit; } createS(this, `${path + setting}.values.${param}`, p.name, t, p.def); } }); this.readSetting(path + setting, growattType, setting, sn); } else { this.readSetting(path + set.isSubread, growattType, setting, sn); } }); } } } }); } } }); } } /** * Parses the data from the website into objects. Is called recrusively. * @param {object} plantData * @param {path} path to object */ async parseData(plantData, path) { if (plantData) { const keys = Object.keys(plantData); // Duplicate keys are transmitted, we try to filter them here. const processed = {}; keys.forEach(key => { if (typeof processed[key.toLowerCase()] === 'undefined') { processed[key.toLowerCase()] = true; this.storeData(plantData, path, key); } }); } } /** * Is Called to get Data * @param {bool} ndel no delete */ async growattLogout(ndel) { if (this.log && this.log.debug) this.log.debug('Enter growattLogout'); const allTimeDiff = getTime(); delete this.connectTime; const { growatt } = this; if (!ndel) delete this.growatt; if (typeof growatt !== 'undefined') { if (growatt.isConnected()) { await growatt.logout().catch({}); } } if (this.log && this.log.debug) this.log.debug(`Leave growattLogout :${getTimeDiff(allTimeDiff)}ms`); } /** * Is Called to get a lifesign */ lifeSignCallback() { this.log.debug(`Enter lifeSignCallback ${this.config.processTimeout * 1000}ms`); clearTimeout(this.processTimeout); if (this.callRun && this.config.processTimeout && this.config.processTimeout > 0) { this.processTimeout = setTimeout(() => { this.growattLogout(true); this.log.warn('Process timeout reached'); if (this.callRun) { clearTimeout(this.callTimeout); this.callTimeout = setTimeout(() => { this.growattData(); }, this.config.errorCycleTime * 1000); } }, this.config.processTimeout * 1000); } } /** * Is Called to get Data */ async growattData() { this.log.debug(`Enter growattData, Param: sessionHold:${this.config.sessionHold}`); const allTimeDiff = getTime(); let debugTimeDiff = getTime(); let afterConnect = false; let timeout = this.config.errorCycleTime * 1000; this.lifeSignCallback(); try { if (typeof this.growatt === 'undefined') { this.log.debug('Growatt new API'); this.growatt = new API({ timeout: this.config.webTimeout * 1000, lifeSignCallback: this.lifeSignCallback.bind(this), server: this.config.growattServer || '', indexCandI: this.config.indexCandI, }); } this.log.debug(`Growatt isConnected() : ${this.growatt.isConnected()}`); if (!this.growatt.isConnected()) { afterConnect = true; if (this.config.keyLogin) { this.log.debug('Growatt share plant login'); await this.growatt.sharePlantLogin(this.config.shareKey).catch(e => { this.log.warn(`Login to share plant:${typeof e === 'object' ? JSON.stringify(e, getJSONCircularReplacer()) : e}`); }); } else { this.log.debug('Growatt login with user and password'); await this.growatt.login(this.config.user, this.config.password).catch(e => { this.log.warn(`Login:${typeof e === 'object' ? JSON.stringify(e, getJSONCircularReplacer()) : e}`); }); } this.log.debug(`Growatt isConnected() : ${this.growatt.isConnected()}`); if (this.growatt.isConnected() && this.config.sessionHold) { this.connectTime = getTime(); } this.log.debug(`Growatt time for login : ${getTimeDiff(debugTimeDiff)}ms`); debugTimeDiff = getTime(); } if (this.growatt.isConnected()) { const allPlantData = await this.growatt.getAllPlantData({ weather: this.config.weather, faultlog: this.config.faultlog, totalData: this.config.totalData, statusData: this.config.statusData, plantData: this.config.plantData, deviceData: this.config.deviceData, historyLast: this.config.historyLast, }); delete this.relogin; this.log.debug(`Growatt time for allPlantData : ${getTimeDiff(debugTimeDiff)}ms`); debugTimeDiff = getTime(); this.parseData(allPlantData, ''); this.log.debug(`Growatt time for parseData : ${getTimeDiff(debugTimeDiff)}ms`); if (afterConnect && this.config.settings) { this.log.debug(`Growatt time for settings : ${getTimeDiff(debugTimeDiff)}ms`); debugTimeDiff = getTime(); this.loadSettings(allPlantData); this.log.debug(`Growatt time for settings : ${getTimeDiff(debugTimeDiff)}ms`); } debugTimeDiff = getTime(); if (this.callRun) { this.setState('info.connection', { val: true, ack: true }); timeout = this.config.cycleTime * 1000 - getTimeDiff(allTimeDiff); if (timeout < 100) { timeout = 100; } } return; } this.log.info('not connected'); this.setState('info.connection', { val: false, ack: true }); } catch (e) { if (e.toString().toLowerCase().includes('errornologin')) { if (!this.config.sessionHold || this.relogin) { this.log.warn(`Growatt login: ${e}`); if (this.config.keyLogin) { this.log.info('If this message appears continuously, your key has expired. Please generate a new one.'); } } else { this.log.info(`Growatt relogin on session failed: ${e}`); this.relogin = true; timeout = 1; } } else { this.log.error(`Growatt exception: ${e}`); } this.setState('info.connection', { val: false, ack: true }); this.growattLogout(); if (this.supportsFeature && this.supportsFeature('PLUGINS')) { const sentryInstance = this.getPluginInstance('sentry'); if (sentryInstance && sentryInstance.getSentryObject() && !e.toString().toLowerCase().includes('errornologin')) { sentryInstance.getSentryObject().captureException(e); } } } finally { if (!this.config.sessionHold) { this.growattLogout(); } else if ( typeof this.connectTime !== 'undefined' && this.config.sessionTime > 0 && getTimeDiff(this.connectTime) > this.config.sessionTime * 60000 ) { this.log.debug('Connection time of the session reached'); this.growattLogout(); } clearTimeout(this.processTimeout); clearTimeout(this.callTimeout); if (this.callRun) { this.callTimeout = setTimeout(() => { this.growattData(); }, timeout); } this.log.debug(`Leave growattData :${getTimeDiff(allTimeDiff)}ms`); } } confCheckMsgObj(obj, vars) { let res = true; let data = obj.message; if (typeof data === 'string') { data = JSON.parse(data); } if (typeof data !== 'object') { this.log.error(`message arg ${typeof data} not an object or json string`); res = false; return [data, res]; } vars.forEach(v => { if (typeof data[v] === 'undefined') { this.log.error(`message <arg>.${v} is missing`); res = false; } }); return [data, res]; } /** * spinoff onMessage, reads a register * @param {string} sn serielnumber of datalogger * @param {integer} register to read * @param {object} obj the messageoject */ readLoggerRegister(register, obj) { if (this.growatt && this.growatt.isConnected()) { const [data, ok] = this.confCheckMsgObj(obj, ['sn']); if (!ok) { return; } this.growatt .getDataLoggerRegister(data.sn, register) .then(res => { this.log.debug(`readLoggerRegister: ${JSON.stringify(res, getJSONCircularReplacer())}`); if (obj.callback && typeof res.success !== 'undefined') { this.sendTo(obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback); } }) .catch(e => { this.log.error(e); }); } } /** * spinoff onMessage, writes a register * @param {string} sn serielnumber of datalogger * @param {integer} register to write * @param {string} value to write * @param {object} obj the messageoject */ writeLoggerRegister(register, obj) { if (this.growatt && this.growatt.isConnected()) { const [data, ok] = this.confCheckMsgObj(obj, ['sn', 'value']); if (!ok) { return; } this.growatt .setDataLoggerRegister(data.sn, register, data.value) .then(res => { this.log.debug(`writeLoggerRegister: ${JSON.stringify(res, getJSONCircularReplacer())}`); if (obj.callback && typeof res.success !== 'undefined') { this.sendTo(obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback); } }) .catch(e => { this.log.error(e); }); } } /** * spinoff onMessage, writes with function * @param {string} sn serielnumber of datalogger * @param {integer} function to use * @param {string} value to write * @param {object} obj the messageoject */ writeLoggerFunction(func, obj) { if (this.growatt && this.growatt.isConnected()) { const [data, ok] = this.confCheckMsgObj(obj, ['sn', 'value']); if (!ok) { return; } this.growatt .setDataLoggerParam(data.sn, func, data.value) .then(res => { this.log.debug(`writeLoggerFunction: ${JSON.stringify(res, getJSONCircularReplacer())}`); if (obj.callback && typeof res.success !== 'undefined') { this.sendTo(obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback); } }) .catch(e => { this.log.error(e); }); } } /** * onMessage, from Admin interface * @param {object} obj the messageoject */ onMessage(obj) { let wait = false; this.log.debug(JSON.stringify(obj, getJSONCircularReplacer())); if (obj) { switch (obj.command) { case 'getDatalogger': if (this.growatt && this.growatt.isConnected()) { wait = true; this.growatt .getDataLoggers() .then(res => { this.log.debug(`getDatalogger: ${JSON.stringify(res, getJSONCircularReplacer())}`); if (obj.callback) { this.sendTo( obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback ); } }) .catch(e => { this.log.error(e); }); } break; case 'getDataLoggerIntervalRegister': wait = true; this.readLoggerRegister(API.LOGGERREGISTER.INTERVAL, obj); break; case 'setDataLoggerIntervalRegister': wait = true; this.writeLoggerRegister(API.LOGGERREGISTER.INTERVAL, obj); break; case 'getDataLoggerIpRegister': wait = true; this.readLoggerRegister(API.LOGGERREGISTER.SERVERIP, obj); break; case 'setDataLoggerIp': wait = true; this.writeLoggerFunction(API.LOGGERFUNCTION.SERVERIP, obj); break; case 'getDataLoggerPortRegister': wait = true; this.readLoggerRegister(API.LOGGERREGISTER.SERVERPORT, obj); break; case 'setDataLoggerPort': wait = true; this.writeLoggerFunction(API.LOGGERFUNCTION.SERVERPORT, obj); break; case 'checkLoggerFirmware': if (this.growatt && this.growatt.isConnected()) { wait = true; const [data, ok] = this.confCheckMsgObj(obj, ['sn']); if (!ok) { return; } this.growatt .checkDataLoggerFirmware(data.type, data.version) .then(res => { this.log.debug(`checkDataLoggerFirmware: ${JSON.stringify(res, getJSONCircularReplacer())}`); if (obj.callback && typeof res.success !== 'undefined') { this.sendTo( obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback ); } }) .catch(e => { this.log.error(e); }); } break; case 'restartDatalogger': if (this.growatt && this.growatt.isConnected()) { wait = true; const [data, ok] = this.confCheckMsgObj(obj, ['sn']); if (!ok) { return; } this.growatt .setDataLoggerRestart(data.sn) .then(res => { if (obj.callback) { this.sendTo(obj.from, obj.command, res.msg, obj.callback); } }) .catch(e => { this.log.error(e); }); } break; case 'getHistory': if (this.growatt && this.growatt.isConnected()) { wait = true; const [data, ok] = this.confCheckMsgObj(obj, ['type', 'sn', 'startDate', 'endDate', 'start']); if (!ok) { return; } this.growatt .getHistory(data.type, data.sn, new Date(data.startDate), new Date(data.endDate), data.start, true) .then(res => { if (obj.callback) { this.sendTo( obj.from, obj.command, typeof obj.message === 'string' ? JSON.stringify(res, getJSONCircularReplacer()) : res, obj.callback ); } }) .catch(e => { this.log.error(e); }); } break; default: this.log.warn(`Unknown command: ${obj.command}`); return; } } if (!wait && obj.callback) { this.sendTo(obj.from, obj.command, obj.message, obj.callback); } } } // @ts-ignore parent is a valid property on module if (module.parent) { // Export the constructor in compact mode /** * @param {Partial<utils.AdapterOptions>} [options={}] */ module.exports = options => new Growatt(options); } else { // otherwise start the instance directly new Growatt(); }