iobroker.growatt
Version:
ioBroker Growatt Adapter to communiacte with ShineAPI
710 lines (678 loc) • 28.7 kB
HTML
<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('"', '"') +
');">' +
' <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"> </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>