iobroker.lovelace
Version:
With this adapter you can build visualization for ioBroker with Home Assistant Lovelace UI
536 lines (503 loc) • 21.7 kB
JavaScript
function formatValue(value, decimals, _format) {
if (typeof decimals !== 'number') {
decimals = 2;
_format = decimals;
}
//format = (_format === undefined) ? (that.isFloatComma) ? ".," : ",." : _format;
// does not work...
// using default german...
const format = _format === undefined || _format === null ? '.,' : _format;
if (typeof value !== 'number') {
value = parseFloat(value);
}
return isNaN(value)
? ''
: value
.toFixed(decimals || 0)
.replace(format[0], format[1])
.replace(/\B(?=(\d{3})+(?!\d))/g, format[0]);
}
function formatDate(dateObj, isDuration, _format) {
// copied from js-controller/lib/adapter.js
if ((typeof isDuration === 'string' && isDuration.toLowerCase() === 'duration') || isDuration === true) {
isDuration = true;
}
if (typeof isDuration !== 'boolean') {
_format = isDuration;
isDuration = false;
}
if (!dateObj) {
return '';
}
const type = typeof dateObj;
if (type === 'string') {
dateObj = new Date(dateObj);
}
if (type !== 'object') {
const j = parseInt(dateObj, 10);
if (j == dateObj) {
// may this is interval
if (j < 946681200) {
isDuration = true;
dateObj = new Date(dateObj);
} else {
// if less 2000.01.01 00:00:00
dateObj = j < 946681200000 ? new Date(j * 1000) : new Date(j);
}
} else {
dateObj = new Date(dateObj);
}
}
const format = _format || /*this.dateFormat || */ 'DD.MM.YYYY';
if (isDuration) {
dateObj.setMilliseconds(dateObj.getMilliseconds() + dateObj.getTimezoneOffset() * 60 * 1000);
}
const validFormatChars = 'YJГMМDTДhSчmмsс';
let s = '';
let result = '';
function put(s) {
let v = '';
switch (s) {
case 'YYYY':
case 'JJJJ':
case 'ГГГГ':
case 'YY':
case 'JJ':
case 'ГГ':
v = dateObj.getFullYear();
if (s.length === 2) {
v %= 100;
}
break;
case 'MM':
case 'M':
case 'ММ':
case 'М':
v = dateObj.getMonth() + 1;
if (v < 10 && s.length === 2) {
v = `0${v}`;
}
break;
case 'DD':
case 'TT':
case 'D':
case 'T':
case 'ДД':
case 'Д':
v = dateObj.getDate();
if (v < 10 && s.length === 2) {
v = `0${v}`;
}
break;
case 'hh':
case 'SS':
case 'h':
case 'S':
case 'чч':
case 'ч':
v = dateObj.getHours();
if (v < 10 && s.length === 2) {
v = `0${v}`;
}
break;
case 'mm':
case 'm':
case 'мм':
case 'м':
v = dateObj.getMinutes();
if (v < 10 && s.length === 2) {
v = `0${v}`;
}
break;
case 'ss':
case 's':
case 'cc':
case 'c':
v = dateObj.getSeconds();
if (v < 10 && s.length === 2) {
v = `0${v}`;
}
v = v.toString();
break;
case 'sss':
case 'ссс':
v = dateObj.getMilliseconds();
if (v < 10) {
v = `00${v}`;
} else if (v < 100) {
v = `0${v}`;
}
v = v.toString();
}
return (result += v);
}
for (let i = 0; i < format.length; i++) {
if (validFormatChars.indexOf(format[i]) >= 0) {
s += format[i];
} else {
put(s);
s = '';
result += format[i];
}
}
put(s);
return result;
}
function getSpecialValues(visOid, systemOid, state) {
if (!state) {
return '';
}
let member = 'val';
if (visOid && systemOid !== visOid) {
member = visOid.replace(`${systemOid}.`, '') || 'val';
}
return state[member];
}
/**
* Formats the string with bindings, i.e., inserts data.
*
* @param format {string} - format string
* @param states {object} - states object
* @param dateFormat {string} - date format
* @returns {string} string with inserted binding data
*/
function formatBinding(format, states, dateFormat) {
const oids = extractBinding(format);
if (!oids) {
return format;
}
for (let t = 0; t < oids.length; t++) {
let value;
if (oids[t].systemOid) {
value = getSpecialValues(oids[t].visOid, oids[t].systemOid, states[oids[t].systemOid]);
if (value === undefined || value === null) {
value = states[oids[t].systemOid] ? states[oids[t].systemOid].val : '';
}
}
if (oids[t].operations) {
for (let k = 0; k < oids[t].operations.length; k++) {
let string = ''; // '(function() {';
switch (oids[t].operations[k].op) {
case 'eval':
for (let a = 0; a < oids[t].operations[k].arg.length; a++) {
if (!oids[t].operations[k].arg[a].name) {
continue;
}
value = getSpecialValues(
oids[t].operations[k].arg[a].visOid,
oids[t].operations[k].arg[a].systemOid,
states[oids[t].operations[k].arg[a].systemOid],
);
if (value === undefined || value === null) {
value = states[oids[t].operations[k].arg[a].systemOid]
? states[oids[t].operations[k].arg[a].systemOid].val
: '';
}
string += `const ${oids[t].operations[k].arg[a].name} = "${value}";`;
}
/*const formula = oids[t].operations[k].formula;
if (formula && formula.indexOf('widget.') !== -1) {
string += 'const widget = ' + JSON.stringify(widget) + ';';
}*/
string += `return ${oids[t].operations[k].formula};`;
//string += '}())';
try {
value = new Function(string)();
} catch (e) {
console.error(`Error in eval[value] : ${format}`);
console.error(`Error in eval[script]: ${string}`);
console.error(`Error in eval[error] : ${e}`);
value = 0;
}
break;
case '*':
if (oids[t].operations[k].arg !== undefined && oids[t].operations[k].arg !== null) {
value = parseFloat(value) * oids[t].operations[k].arg;
}
break;
case '/':
if (oids[t].operations[k].arg !== undefined && oids[t].operations[k].arg !== null) {
value = parseFloat(value) / oids[t].operations[k].arg;
}
break;
case '+':
if (oids[t].operations[k].arg !== undefined && oids[t].operations[k].arg !== null) {
value = parseFloat(value) + oids[t].operations[k].arg;
}
break;
case '-':
if (oids[t].operations[k].arg !== undefined && oids[t].operations[k].arg !== null) {
value = parseFloat(value) - oids[t].operations[k].arg;
}
break;
case '%':
if (oids[t].operations[k].arg !== undefined && oids[t].operations[k].arg !== null) {
value = parseFloat(value) % oids[t].operations[k].arg;
}
break;
case 'round':
if (oids[t].operations[k].arg === undefined && oids[t].operations[k].arg !== null) {
value = Math.round(parseFloat(value));
} else {
value = parseFloat(value).toFixed(oids[t].operations[k].arg);
}
break;
case 'pow':
if (oids[t].operations[k].arg === undefined && oids[t].operations[k].arg !== null) {
value = Math.pow(parseFloat(value), 2);
} else {
value = Math.pow(parseFloat(value), oids[t].operations[k].arg);
}
break;
case 'sqrt':
value = Math.sqrt(parseFloat(value));
break;
case 'hex':
value = Math.round(parseFloat(value)).toString(16);
break;
case 'hex2':
value = Math.round(parseFloat(value)).toString(16);
if (value.length < 2) {
value = `0${value}`;
}
break;
case 'HEX':
value = Math.round(parseFloat(value)).toString(16).toUpperCase();
break;
case 'HEX2':
value = Math.round(parseFloat(value)).toString(16).toUpperCase();
if (value.length < 2) {
value = `0${value}`;
}
break;
case 'value':
value = formatValue(value, parseInt(oids[t].operations[k].arg, 10));
break;
case 'array':
value = oids[t].operations[k].arg[~~value];
break;
case 'date':
value = formatDate(value, oids[t].operations[k].arg, dateFormat);
break;
case 'min':
value = parseFloat(value);
value = value < oids[t].operations[k].arg ? oids[t].operations[k].arg : value;
break;
case 'max':
value = parseFloat(value);
value = value > oids[t].operations[k].arg ? oids[t].operations[k].arg : value;
break;
case 'random':
if (oids[t].operations[k].arg === undefined && oids[t].operations[k].arg !== null) {
value = Math.random();
} else {
value = Math.random() * oids[t].operations[k].arg;
}
break;
case 'floor':
value = Math.floor(parseFloat(value));
break;
case 'ceil':
value = Math.ceil(parseFloat(value));
break;
} //switch
}
} //if for
format = format.replace(oids[t].token, value);
} //for
format = format.replace(/{{/g, '{').replace(/}}/g, '}');
return format;
}
/**
* Extracts all bindings from format string
*
* @param format {string} - format string
* @returns {Array} - array of bindings
*/
function extractBinding(format) {
if (typeof format !== 'string') {
format = JSON.stringify(format);
}
const oid = format.match(/{(.+?)}/g);
let result = null;
if (oid) {
if (oid.length > 50) {
console.warn(`Too many bindings in one widget: ${oid.length}[max = 50]`);
}
for (let p = 0; p < oid.length && p < 50; p++) {
const _oid = oid[p].substring(1, oid[p].length - 1);
if (_oid[0] === '{') {
continue;
}
// If first symbol '"' => it is JSON
if (_oid && _oid[0] === '"') {
continue;
}
const parts = _oid.split(';');
result = result || [];
let systemOid = parts[0].trim();
let visOid = systemOid;
let test1 = visOid.substring(visOid.length - 4);
let test2 = visOid.substring(visOid.length - 3);
if (visOid && test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
visOid = `${visOid}.val`;
}
const isSeconds = test2 === '.ts' || test2 === '.lc';
test1 = systemOid.substring(systemOid.length - 4);
test2 = systemOid.substring(systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
systemOid = systemOid.substring(0, systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
systemOid = systemOid.substring(0, systemOid.length - 3);
}
let operations = null;
const isEval = visOid.match(/[\d\w_.]+:\s?[-\d\w_.]+/) || (!visOid.length && parts.length > 0); //(visOid.indexOf(':') !== -1) && (visOid.indexOf('::') === -1);
if (isEval) {
const xx = visOid.split(':', 2);
const yy = systemOid.split(':', 2);
visOid = xx[1];
systemOid = yy[1];
operations = [];
operations.push({
op: 'eval',
arg: [
{
name: xx[0],
visOid: visOid,
systemOid: systemOid,
},
],
});
}
for (let u = 1; u < parts.length; u++) {
// eval construction
if (isEval) {
if (parts[u].trim().match(/^[\d\w_.]+:\s?[-.\d\w_]+$/)) {
//parts[u].indexOf(':') !== -1 && parts[u].indexOf('::') === -1) {
let _systemOid = parts[u].trim();
let _visOid = _systemOid;
test1 = _visOid.substring(_visOid.length - 4);
test2 = _visOid.substring(_visOid.length - 3);
if (test1 !== '.val' && test2 !== '.ts' && test2 !== '.lc' && test1 !== '.ack') {
_visOid = `${_visOid}.val`;
}
test1 = systemOid.substring(_systemOid.length - 4);
test2 = systemOid.substring(_systemOid.length - 3);
if (test1 === '.val' || test1 === '.ack') {
_systemOid = _systemOid.substring(0, _systemOid.length - 4);
} else if (test2 === '.lc' || test2 === '.ts') {
_systemOid = _systemOid.substring(0, _systemOid.length - 3);
}
const x1 = _visOid.split(':', 2);
const y1 = _systemOid.split(':', 2);
operations[0].arg.push({
name: x1[0],
visOid: x1[1],
systemOid: y1[1],
});
} else {
operations = operations || [];
parts[u] = parts[u].replace(/::/g, ':');
if (operations[0].formula) {
const n = JSON.parse(JSON.stringify(operations[0]));
n.formula = parts[u];
operations.push(n);
} else {
operations[0].formula = parts[u];
}
}
} else {
const parse = parts[u].match(/([\w\s/+*%-]+)(\(.+\))?/);
if (parse && parse[1]) {
parse[1] = parse[1].trim();
// operators requires parameter
if (
parse[1] === '*' ||
parse[1] === '+' ||
parse[1] === '-' ||
parse[1] === '/' ||
parse[1] === '%' ||
parse[1] === 'min' ||
parse[1] === 'max'
) {
if (parse[2] === undefined) {
console.log(`Invalid format of format string: ${format}`);
parse[2] = null;
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log(`Invalid format of format string: ${format}`);
parse[2] = null;
} else {
operations = operations || [];
operations.push({ op: parse[1], arg: parse[2] });
}
}
} else if (parse[1] === 'date') {
// date formatting
operations = operations || [];
parse[2] = (parse[2] || '').trim();
parse[2] = parse[2].substring(1, parse[2].length - 1);
operations.push({ op: parse[1], arg: parse[2] });
} else if (parse[1] === 'array') {
// returns array[value]. e.g.: {id.ack;array(ack is false,ack is true)}
operations = operations || [];
let param = (parse[2] || '').trim();
param = param.substring(1, param.length - 1);
param = param.split(',');
if (Array.isArray(param)) {
operations.push({ op: parse[1], arg: param }); //xxx
}
} else if (parse[1] === 'value') {
// value formatting
operations = operations || [];
let param = parse[2] === undefined ? '(2)' : parse[2] || '';
param = param.trim();
param = param.substring(1, param.length - 1);
operations.push({ op: parse[1], arg: param });
} else if (parse[1] === 'pow' || parse[1] === 'round' || parse[1] === 'random') {
// operators have optional parameter
if (parse[2] === undefined) {
operations = operations || [];
operations.push({ op: parse[1] });
} else {
parse[2] = (parse[2] || '').trim().replace(',', '.');
parse[2] = parse[2].substring(1, parse[2].length - 1);
parse[2] = parseFloat(parse[2].trim());
if (parse[2].toString() === 'NaN') {
console.log(`Invalid format of format string: ${format}`);
parse[2] = null;
} else {
operations = operations || [];
operations.push({ op: parse[1], arg: parse[2] });
}
}
} else {
// operators without parameter
operations = operations || [];
operations.push({ op: parse[1] });
}
} else {
console.log(`Invalid format ${format}`);
}
}
}
result.push({
visOid,
systemOid,
token: oid[p],
operations: operations ? operations : undefined,
format,
isSeconds,
});
}
}
return result;
}
module.exports = {
extractBinding,
formatBinding,
};
;