UNPKG

iobroker.growatt

Version:

ioBroker Growatt Adapter to communiacte with ShineAPI

710 lines (678 loc) 28.7 kB
<html> <head> <!-- Load ioBroker scripts and styles--> <link rel="stylesheet" type="text/css" href="../../css/adapter.css" /> <link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css" /> <script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="../../socket.io/socket.io.js"></script> <script type="text/javascript" src="../../js/translate.js"></script> <script type="text/javascript" src="../../lib/js/materialize.js"></script> <script type="text/javascript" src="../../js/adapter-settings.js"></script> <!-- Load our own files --> <link rel="stylesheet" type="text/css" href="style.css" /> <script type="text/javascript" src="words.js"></script> <script type="text/javascript"> let secret = 'Zgfr56gFe87jJOM'; const NUMBER = 'number'; if (!encrypt || typeof encrypt !== 'function') { function encrypt(key, value) { if (typeof value === 'undefined' || value === undefined) { value = key; key = secret; } var result = ''; for (var i = 0; i < value.length; ++i) { result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i)); } return result; } } if (!decrypt || typeof decrypt !== 'function') { function decrypt(key, value) { if (typeof value === 'undefined' || value === undefined) { value = key; key = secret; } var result = ''; for (var i = 0; i < value.length; ++i) { result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i)); } return result; } } function initLoggerText() { $('#datalogger').html( '<h6>' + translateWord( 'The instance must be running and logged in to the server. Then the settings of the data logger can be called up via the refresh button in this tab.' ) + '</h6>' ); } function warnsettings(val) { if (val) { $('#warnsettings').html( '<h6>' + translateWord( 'Reading and writing the inverter settings is at your own risk. It can damage the inverter. The functions have been tested to the best of our knowledge.' ) + '</h6>' ); } else { $('#warnsettings').html(''); } } function storeObject(id, obj, objList) { let ebene = id.split('.'); ebene.shift(); ebene.shift(); if (ebene[0] == 'info' || ebene[3] == 'settings' || ebene.length < 2) { return; } let newId = ebene.join('.'); //let n = ebene[ebene.length-1] //let parent = ebene.slice(0,ebene.length-1).join('.'); let row = { id: newId, name: obj.common.name, type: obj.common.type, created: new Date(obj.ts).toLocaleString(), action: 'normal' }; if (row.type.toLowerCase() == NUMBER) { row.offset = 0; } objList.push(row); } function showObjList(onChange, settings, objList) { let update = Object.assign({}, settings.objUpdate); let offset = Object.assign({}, settings.objOffset); objList.forEach(function (o) { if (update[o.id.toLowerCase()]) { o.action = update[o.id.toLowerCase()].action; } delete update[o.id.toLowerCase()]; }); Object.keys(update).forEach(id => { if (update[id].action == 'delete') { objList.push({ id: update[id].id, name: '', type: '', created: '', action: update[id].action }); } }); objList.forEach(function (o) { if (offset[o.id.toLowerCase()]) { o.offset = offset[o.id.toLowerCase()].offset; } }); objList.sort(function (dev1, dev2) { let x = dev1.id.split('.'), y = dev2.id.split('.'); while ((x.length > 0 && y.length > 0 && x.length == y.length) || (x.length > 1 && y.length > 1 && x.length != y.length)) { let a = x.shift().toUpperCase(); b = y.shift().toUpperCase(); if (a != b) { return a > b ? 1 : -1; } } return y.length - x.length; }); initLoggerText(); values2table('objects', objList, onChange, function () { $('[data-name=id]').attr('disabled', 'disabled'); $('[data-name=name]').attr('readonly', 'readonly'); $('[data-name=type]').attr('readonly', 'readonly'); $('[data-name=created]').attr('readonly', 'readonly'); var _objects = table2values('objects'); for (var i = 0; i < _objects.length; i++) { if (_objects[i].type.toLowerCase() != NUMBER) $('#objects .values-input[data-name="offset"][data-index="' + i + '"]').attr('readonly', 'readonly'); } return; }); } function emitObjects(onChange, settings) { socket.emit( 'getObjectView', 'system', 'state', { startkey: 'growatt.' + instance + '.', endkey: 'growatt.' + instance + '.\u9999', include_docs: true }, function (err, _objects) { let objList = []; if (_objects && _objects.rows && _objects.rows.length) { for (let j = 0; j < _objects.rows.length; j++) { storeObject(_objects.rows[j].id, _objects.rows[j].value, objList); } } showObjList(onChange, settings, objList); } ); } function loadHelper(settings, onChange) { if (!settings) return; improveValues(settings); $('.value').each(function () { var $key = $(this); var id = $key.attr('id'); if ( (id === 'password' || id === 'shareKey') && (typeof supportsFeature !== 'function' || !supportsFeature('ADAPTER_AUTO_DECRYPT_NATIVE')) ) { settings[id] = decrypt(secret, settings[id]); } if ($key.attr('type') === 'checkbox') { // do not call onChange direct, because onChange could expect some arguments $key.prop('checked', settings[id]).on('change', () => onChange()); } else { // do not call onChange direct, because onChange could expect some arguments $key .val(settings[id]) .on('change', () => onChange()) .on('keyup', () => onChange()); } }); $('#keyLogin') .change(function () { if ($(this).prop('checked')) { $('.user').hide(); $('.password').hide(); $('.shareKey').show(); } else { $('.user').show(); $('.password').show(); $('.shareKey').hide(); } }) .trigger('change'); $('#settings') .change(function () { warnsettings($(this).prop('checked')); }) .trigger('change'); $('#sessionHold') .change(function () { if ($(this).prop('checked')) { $('.sessionTime').show(); } else { $('.sessionTime').hide(); } }) .trigger('change'); onChange(false); settings.objUpdate = settings.objUpdate || {}; emitObjects(onChange, settings); $('#tab-manage-objects') .find('.btn-objects-reload') .on('click', function () { var obj = {}; obj.objUpdate = getObjUpdate(); emitObjects(onChange, obj); }); $('#tab-manage-datalogger') .find('.btn-datalogger-reload') .on('click', function () { getDatalogger(); }); // reinitialize all the Materialize labels on the page if you are dynamically adding inputs: if (M) M.updateTextFields(); } // This will be called by the admin adapter when the settings page loads function load(settings, onChange) { socket.emit('getObject', 'system.config', function (err, obj) { secret = (obj.native ? obj.native.secret : secret) || secret; loadHelper(settings, onChange); }); onChange(false); } function getObjUpdate() { let objectListAll = table2values('objects'); let objUpdate = {}; objectListAll.forEach(function (o) { if (o.action != 'normal') { objUpdate[o.id.toLowerCase()] = { id: o.id, action: o.action }; } }); return objUpdate; } function getObjOffset() { let objectListAll = table2values('objects'); let objOffset = {}; objectListAll.forEach(function (o) { if (o.offset && !!parseFloat(o.offset) && o.offset != 0) { objOffset[o.id.toLowerCase()] = { id: o.id, offset: parseFloat(o.offset) }; } }); return objOffset; } function improveValues(obj) { if (typeof obj.webTimeout === 'undefined' || obj.webTimeout == '') obj.webTimeout = '60'; if (typeof obj.processTimeout === 'undefined' || obj.processTimeout == '') obj.processTimeout = '600'; if (typeof obj.sessionHold === 'undefined' || obj.sessionHold == '') obj.sessionHold = true; if (typeof obj.sessionTime === 'undefined' || obj.sessionTime == '') obj.sessionTime = '0'; if (typeof obj.cycleTime === 'undefined' || obj.cycleTime == '') obj.cycleTime = '30'; if (typeof obj.errorCycleTime === 'undefined' || obj.errorCycleTime == '') obj.errorCycleTime = '120'; } function getDatalogger() { $('#datalogger').empty(); let callTimeout = setTimeout(() => { initLoggerText(); }, 5000); sendTo('growatt.' + instance, 'getDatalogger', '{}', function (result) { clearTimeout(callTimeout); let res = JSON.parse(result); let r = ''; for (let row = 0; row < res.length; row += 1) { function aRow(html) { return '<div class="row">' + html + '</div>'; } function aText(width, id, label, val) { return ( '<div class="col s' + width + ' input-field ' + id + (row + 1) + '">' + ' <input type="text" class="value" id="' + id + (row + 1) + '" disabled>' + val + '</>' + ' <label for="' + id + (row + 1) + '">' + translateWord(label) + '</label>' + '</div>' ); } function aCheck(val) { let result = ''; if (val.toString().toLowerCase === 'true') { result = 'checked'; } return result; } function aNCheck(val) { let result = ''; if (val.toString().toLowerCase === 'false') { result = 'checked'; } return result; } function aBox(width, id, label, val) { return ( '<div class="col s' + width + ' input-field ' + id + (row + 1) + '">' + ' <input type="checkbox" class="value" id="' + id + (row + 1) + '" disabled ' + val + ' />' + ' <span for="' + id + (row + 1) + '" >' + translateWord(label) + '</span>' + '</div>' ); } function aButton(width, typ, id, label, func, data) { return ( '<div class="col s' + width + ' input-field ' + id + (row + 1) + '">' + ' <a class="btn btn-small" id="' + id + (row + 1) + '" href="javascript:' + func + '(' + JSON.stringify(data).replaceAll('"', '&quot;') + ');">' + ' <i class="material-icons left">' + typ + '</i>' + ' ' + translateWord(label) + ' </a>' + '</div>' ); } r += aRow('<h6 class="sub-title col12">' + translateWord('Datalogger') + ' ' + (row + 1) + '</h6>') + aRow( aText(4, 'plantId', 'PV plant id', res[row].plantId) + aText(4, 'plantName', 'PV plant name', res[row].plantName) + aText(4, 'accountName', 'Account name', res[row].accountName) + aText(4, 'sn', 'Serial number', res[row].sn) + aText(4, 'alias', 'Alias', res[row].alias) + aText(4, 'firmwareVersion', 'Firmware version', res[row].firmwareVersion) + aText(4, 'deviceTypeIndicate', 'Device type index', res[row].deviceTypeIndicate) + aText(4, 'deviceType', 'Device type', res[row].deviceType) + aText(4, 'stateGridModel', 'Wifi model', res[row].stateGridModel) + aText(4, 'lost', 'Comunication lost', res[row].lost) + aText(4, 'simSignal', 'Wifi signal level', res[row].simSignal) + aText(4, 'ipAndPort', 'IP and port', res[row].ipAndPort) + aText(4, 'interval', 'Interval', res[row].interval) + aText(4, 'lastUpdateTime', 'Last update timepoint', res[row].lastUpdateTime) ) + aRow( aButton(4, 'av_timer', 'btninterval', 'Interval', 'setLoggerInterval', { sn: res[row].sn }) + aButton(4, 'cloud', 'btnserverip', 'Server ip', 'setLoggerIp', { sn: res[row].sn }) + aButton(4, 'cloud_queue', 'btnserverport', 'Server port', 'setLoggerPort', { sn: res[row].sn }) + aButton(4, 'play_for_work', 'btnfirmware', 'Check firmware', 'checkLoggerFirmware', { type: res[row].deviceTypeIndicate, version: res[row].firmwareVersion, }) + aButton(4, 'replay', 'btnrestart', 'Restart datalogger', 'restartDatalogger', { sn: res[row].sn }) ); } $('#datalogger').html(r); }); } function alertResponse(result) { let s = ''; let res = JSON.parse(result); if (res.success && res.success.toString().toLowerCase() === 'true') { s = translateWord('success'); } else { s = translateWord('no success'); } alert(translateWord("The server's response") + ': ' + s + ': ' + res.msg); } function promptResult(result, ask) { let res = JSON.parse(result); if (res.success && res.success.toString().toLowerCase() === 'true') { return prompt(translateWord(ask), res.msg); } else { alert(translateWord("The server's response") + ': ' + translateWord('no success') + ': ' + res.msg); } } function setLoggerInterval(opt) { let callTimeout = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'getDataLoggerIntervalRegister', JSON.stringify({ sn: opt.sn }), function (result) { clearTimeout(callTimeout); let interval = promptResult(result, 'Please enter new interval in minutes'); if (interval !== null) { let callTimeoutInner = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'setDataLoggerIntervalRegister', JSON.stringify({ sn: opt.sn, value: interval }), function (result) { clearTimeout(callTimeoutInner); alertResponse(result); }); } }); } function setLoggerIp(opt) { let callTimeout = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'getDataLoggerIpRegister', JSON.stringify({ sn: opt.sn }), function (result) { clearTimeout(callTimeout); let ip = promptResult(result, 'Please enter new IP for server'); if (ip !== null) { let callTimeoutInner = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'setDataLoggerIntervalRegister', JSON.stringify({ sn: opt.sn, value: ip }), function (result) { clearTimeout(callTimeoutInner); alertResponse(result); }); } }); } function setLoggerPort(opt) { let callTimeout = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'getDataLoggerPortRegister', JSON.stringify({ sn: opt.sn }), function (result) { clearTimeout(callTimeout); let port = promptResult(result, 'Please enter new port for server'); if (port !== null) { let callTimeoutInner = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'setDataLoggerIntervalRegister', JSON.stringify({ sn: opt.sn, value: ip }), function (result) { clearTimeout(callTimeoutInner); alertResponse(result); }); } }); } function checkLoggerFirmware(opt) { let callTimeout = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'checkLoggerFirmware', JSON.stringify({ type: opt.type, version: opt.version }), function (result) { clearTimeout(callTimeout); alertResponse(result); }); } function restartDatalogger(opt) { let callTimeout = setTimeout(() => { alert('The server did not respond to the request!'); }, 20000); sendTo('growatt.' + instance, 'restartDatalogger', JSON.stringify({ sn: opt.sn }), function (result) { clearTimeout(callTimeout); alertResponse(result); }); } // This will be called by the admin adapter when the user presses the save button function save(callback) { var obj = {}; obj.objUpdate = getObjUpdate(); obj.objOffset = getObjOffset(); $('.value').each(function () { var $this = $(this); if ($this.attr('type') === 'checkbox') { obj[$this.attr('id')] = $this.prop('checked'); } else { var value = $this.val(); if ($this.attr('id') === 'shareKey' && value.includes('/')) { value = value.substring(value.lastIndexOf('/') + 1); $this.val(value); } if ( ($this.attr('id') === 'password' || $this.attr('id') === 'shareKey') && (typeof supportsFeature !== 'function' || !supportsFeature('ADAPTER_AUTO_DECRYPT_NATIVE')) ) { value = encrypt(secret, value); } obj[$this.attr('id')] = value; } }); improveValues(obj); callback(obj); } </script> </head> <body> <div class="m adapter-container"> <div class="row"> <div class="col s12 m4 l2"> <img src="growatt.png" class="logo" /> </div> </div> <div class="row"> <div class="col s12"> <ul class="tabs"> <li class="tab col s2"> <a href="#tab-main" class="translate active">Main Settings</a> </li> <li class="tab col s2"> <a href="#tab-manage-objects" class="translate">Manage Objects</a> </li> <li class="tab col s2"> <a href="#tab-manage-datalogger" class="translate">Manage Dataloggers</a> </li> </ul> </div> <div id="tab-main" class="col s12 page"> <div class="row"> <div class="col s12 input-field"> <input type="checkbox" class="value" id="keyLogin" /> <label for="keyLogin" class="translate">Login with shared key</label> </div> </div> <div class="row"> <div class="col s6 input-field user"> <input type="text" class="value" id="user" /> <label for="user" class="translate">User</label> </div> <div class="col s6 input-field password"> <input type="password" class="value" id="password" /> <label for="password" class="translate">Password</label> </div> <div class="col s12 input-field shareKey"> <input type="text" class="value" id="shareKey" /> <label for="shareKey" class="translate">The key from shared URL</label> </div> </div> <div class="row"> <div class="col s6 input-field"> <input type="checkbox" class="value" id="plantData" /> <label for="plantData" class="translate">Read plant data</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="historyLast" /> <label for="historyLast" class="translate">Read last history data</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="statusData" /> <label for="statusData" class="translate">Read status data (not INV/MAX/TLX)</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="totalData" /> <label for="totalData" class="translate">Read total data</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="deviceData" /> <label for="deviceData" class="translate">Read device data</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="weather" /> <label for="weather" class="translate">Read weather</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="faultlog" /> <label for="faultlog" class="translate">Read fault log entries</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="settings" /> <label for="settings" class="translate">Write inverter settings (only mix/tlx/tlxh)</label> </div> <div class="col s6 input-field"> <input type="checkbox" class="value" id="indexCandI" /> <label for="indexCandI" class="translate">Select it if your Growatt page is a black C and I page</label> </div> </div> <div class="row"> <div class="col s12"> <a class="col s12" id="warnsettings">&nbsp;</a> </div> </div> <div class="row"> <div class="col s6 input-field"> <input class="value number" type="number" id="webTimeout" size="3" maxlength="3" /> <label class="translate" for="webTimeout">Timeout in seconds</label> </div> <div class="col s6 input-field"> <input class="value number" type="number" id="processTimeout" size="3" maxlength="3" /> <label class="translate" for="processTimeout">Process timeout in seconds</label> </div> </div> <div class="row"> <div class="col s6 input-field"> <input type="checkbox" class="value" id="sessionHold" /> <label for="sessionHold" class="translate">Keep web session</label> </div> <div class="col s6 input-field sessionTime"> <input class="value number" type="number" id="sessionTime" size="3" maxlength="3" /> <label class="translate" for="sessionTime">Session time in minutes</label> </div> </div> <div class="row"> <div class="col s6 input-field"> <input class="value number" type="number" id="cycleTime" size="3" maxlength="3" /> <label class="translate" for="cycleTime">Cycle time in seconds</label> </div> <div class="col s6 input-field"> <input class="value number" type="number" id="errorCycleTime" size="3" maxlength="3" /> <label class="translate" for="errorCycleTime">Error cycle time in seconds</label> </div> </div> <div class="row"> <div class="col s6 input-field"> <input type="text" class="value" id="growattServer" /> <label for="growattServer" class="translate">Growatt server default https://server.growatt.com</label> </div> </div> </div> <div id="tab-manage-objects" class="col s12 page" style="height: auto; width: auto"> <div class="row"> <div class="col s12"> <a class="btn-floating btn-small btn-objects-reload" title="reload" data-lang-title="reload"> <i class="material-icons">refresh</i> </a> </div> </div> <div class="row"> <div class="col s12" id="objects"> <div class="table-values-div"> <table class="table-values"> <thead> <tr> <th data-name="_index" style="width: 2em"></th> <th data-name="action" style="width: 4em; white-space: nowrap" data-options="normal;delete;noupdate" data-type="select"> <span class="translate">Action</span> </th> <th data-name="offset" data-type="number" style="width: 8em; white-space: nowrap"> <span class="translate">Offset</span> </th> <th data-name="id" style="width: 35em; white-space: nowrap"> <span class="translate">ID</span> </th> <th data-name="name" style="width: 10em; white-space: nowrap"> <span class="translate">Name</span> </th> <th data-name="type" style="width: 6em; white-space: nowrap"> <span class="translate">Type</span> </th> <th data-name="created" style="width: 8em; white-space: nowrap"> <span class="translate">Created</span> </th> </tr> </thead> </table> </div> </div> </div> </div> <div id="tab-manage-datalogger" class="col s12 page" style="height: auto; width: auto"> <div class="row"> <div class="col s12"> <a class="btn-floating btn-small btn-datalogger-reload" title="reload" data-lang-title="reload"> <i class="material-icons">refresh</i> </a> </div> </div> <div class="row"> <div class="col s12" id="datalogger"> <a class="col s12" >The instance must be running and logged in to the server. Then the settings of the data logger can be called up via the refresh button in this tab.</a > </div> </div> </div> </div> </div> </body> </html>