iobroker.janitza-gridvis
Version:
Read out data from Energymanagementsystem Janitza® GridVis®
472 lines (436 loc) • 17.4 kB
JavaScript
;
const lodash = require('lodash');
const { DeviceManagement } = require('@iobroker/dm-utils');
/**
* DeviceManager Class
*/
class GridVisDeviceManagement extends DeviceManagement {
/**
* Initialize Class with Adapter
*
* @param adapter Adapter Reference
*/
constructor(adapter) {
super(adapter);
this.adapter = adapter;
}
/**
* get Instance Info for Device Manager
*/
async getInstanceInfo() {
const info = await super.getInstanceInfo();
if (!info.actions) {
info.actions = [];
}
info.actions.push({
id: 'GridVisVersion',
title: `GridVis Server Version: ${(await this.adapter.getStateAsync(`${this.adapter.namespace}.info.gridVisVersion`)).val}`,
icon: 'lines',
description: 'GridVis-Version',
//url: `http://${this.adapter.config.address}:${this.adapter.config.port}`,
});
return info;
}
/**
* List all devices
*
* @param context Context of loadDevices
*/
async loadDevices(context) {
context.setTotalDevices(Object.keys(this.adapter.objectStore.devices).length);
let primary = true;
for (const [deviceId, deviceValue] of Object.entries(this.adapter.objectStore.devices)) {
const res = {
id: deviceId,
identifier: deviceId,
name: deviceValue.object.common.name,
icon: await this.getIcon(deviceValue),
manufacturer: 'Janitza',
model: deviceValue.info?.type
? deviceValue.info.type.state.val.replace('MeasurementGroup', '\nMeasurementGroup')
: undefined,
status: await this.getStatus(deviceValue),
hasDetails: true,
backgroundColor: primary ? 'primary' : 'secondary',
};
res.actions = [
{
id: 'doc',
icon: 'lines',
description: this.adapter.i18nTranslation['DeviceDocumentation'],
url: this.getDocUrl(res.model),
},
];
res.customInfo = {
id: deviceId,
schema: {
type: 'panel',
items: {},
},
};
for (const [key, value] of Object.entries(deviceValue.onlineValues || {})) {
if (key === 'object') {
continue;
}
for (const [stateKey, stateValue] of Object.entries(value)) {
if (stateKey === 'object') {
continue;
}
let card = {
preLabel: `${this.replaceNameing(value.object.common.name)} `,
name: ` ${stateValue.object.common.name}`,
};
card = lodash.merge(card, stateValue.object.native?.card);
const preLabel = card.preLabel ?? '';
let label = '';
if (card.name) {
label = card.name;
} else if (card.label) {
label = card.label;
} else {
label = stateValue.object._id.substring(stateValue.object._id.lastIndexOf('.') + 1);
}
res.customInfo.schema.items[`_${stateValue.object._id}`] = {
type: 'state',
oid: stateValue.object._id,
foreign: true,
control: 'text',
label: preLabel + label,
digits: card.digits ?? 0,
};
}
}
// Sort
const sortedItems = Object.fromEntries(
Object.entries(res.customInfo.schema.items).sort(([, a], [, b]) => {
const labelA = a.label?.toLowerCase() || '';
const labelB = b.label?.toLowerCase() || '';
return labelA.localeCompare(labelB, 'de');
}),
);
res.customInfo.schema.items = sortedItems;
context.addDevice(res);
primary = !primary;
}
}
/**
*
* @param name Name to replace
*/
replaceNameing(name) {
switch (name) {
case 'Bezogene Wirkarbeit':
return 'Bezogen';
case 'Gelieferte Wirkarbeit':
return 'Geliefert';
case 'Wirkleistung':
return 'Leistung';
case 'UserDefined':
return '';
default:
return name;
}
}
/**
* @param model Model to search for doc url
*/
getDocUrl(model) {
const docs = {
PRODATA: 'https://www.janitza.com/de-de/produkte/pro-data-2/downloads',
'20CM': 'https://www.janitza.com/de-de/produkte/umg-20-cm/downloads',
PA_MID: 'https://www.janitza.com/de-de/produkte/umg-96-pa-mid/downloads',
PA: 'https://www.janitza.com/de-de/produkte/umg-96-pa/downloads',
PQ_L_LP: 'https://www.janitza.com/de-de/produkte/umg-96-pq-l-lp/downloads',
PQ_L: 'https://www.janitza.com/de-de/produkte/umg-96-pq-l/downloads',
RME: 'https://www.janitza.com/de-de/produkte/umg-96-rm-e/downloads',
RMEL: 'https://www.janitza.com/de-de/produkte/umg-96-el/downloads',
RMCBM: 'https://www.janitza.com/de-de/produkte/umg-96-rm-cbm/downloads',
'96EL': 'https://www.janitza.com/de-de/produkte/umg-96-el/downloads',
201: 'https://www.janitza.com/de-de/produkte/rcm-201-rogo/downloads',
202: 'https://www.janitza.com/de-de/produkte/rcm-202-ab/downloads',
604: 'https://www.janitza.com/de-de/produkte/umg-604-pro/downloads',
605: 'https://www.janitza.com/de-de/produkte/umg-605-pro/downloads',
800: 'https://www.janitza.com/de-de/produkte/umg-800/downloads',
801: 'https://www.janitza.com/de-de/produkte/umg-801/downloads',
};
const generalDownloads = 'https://www.janitza.com/de-de/downloads';
if (model) {
const key = Object.keys(docs).find(key => model.includes(key));
if (key) {
return docs[key];
}
}
return generalDownloads;
}
/**
* @param deviceValue Value from devicestructure
*/
async getStatus(deviceValue) {
const status = {};
if (!deviceValue.info?.type?.state.val.startsWith('XXX')) {
status.connection = deviceValue?.info?.status?.state.val === 'OK' ? 'connected' : 'disconnected';
}
return status;
}
/**
*
* @param deviceValue values of device
*/
async getIcon(deviceValue) {
if (deviceValue.object?.common?.icon) {
return `/adapter/${this.adapter.name}/${deviceValue.object.common.icon}`;
}
return `/adapter/${this.adapter.name}/icons/default.png`;
}
/**
* @param {string} id ID from device
* @returns {Promise<import('@iobroker/dm-utils').DeviceDetails>} return the right value
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
async getDeviceDetails(id) {
// eslint-disable-next-line jsdoc/check-tag-names
/** @type {Record<string, import('@iobroker/dm-utils').ConfigItemAny>} */
const onlineValuesItems = {};
// eslint-disable-next-line jsdoc/check-tag-names
/** @type {Record<string, import('@iobroker/dm-utils').ConfigItemAny>} */
const historicValuesItems = {};
// eslint-disable-next-line jsdoc/check-tag-names
/** @type {Record<string, import('@iobroker/dm-utils').ConfigItemAny>} */
const deviceInfoItems = {};
// eslint-disable-next-line jsdoc/check-tag-names
/** @type {Record<string, import('@iobroker/dm-utils').ConfigItemAny>} */
const deviceObjectItems = {};
const data = {};
// Online Values
if (this.adapter.objectStore.devices[id].onlineValues) {
const onlineValues = this.adapter.objectStore.devices[id].onlineValues;
const sortedEntries = Object.entries(onlineValues)
.filter(([k]) => k !== 'object')
.sort(([, a], [, b]) =>
a.object.common.name.localeCompare(b.object.common.name, { sensitivity: 'base' }),
);
for (const [k, v] of sortedEntries) {
if (k === 'object') {
continue;
}
const prefix = onlineValues[k].object.common.name;
onlineValuesItems[`${prefix}_Header`] = {
newLine: true,
type: 'header',
text: prefix,
size: 3,
};
const innerSortedEntries = Object.entries(v)
.filter(([k]) => k !== 'object')
.sort(([, a], [, b]) =>
a.object.common.name.localeCompare(b.object.common.name, { sensitivity: 'base' }),
);
for (const [key, value] of innerSortedEntries) {
if (key === 'object') {
continue;
}
const postfix = value.object.common.name;
onlineValuesItems[`Value_${prefix}_${postfix}`] = {
type: 'state',
label: `${postfix}`,
oid: value.object._id,
foreign: true,
};
}
}
}
// historic Values
if (this.adapter.objectStore.devices[id].historicValues) {
const historicValues = this.adapter.objectStore.devices[id].historicValues;
const sortedEntries = Object.entries(historicValues)
.filter(([k]) => k !== 'object')
.sort(([, a], [, b]) =>
a.object.common.name.localeCompare(b.object.common.name, { sensitivity: 'base' }),
);
for (const [, v] of sortedEntries) {
const prefix = v.object.common.name;
// ===== group the states into L1, L2 ... =====
const groups = {};
for (const [key, value] of Object.entries(v)) {
if (key === 'object') {
continue;
}
const postfix = value.object.common.name;
const timeKey = key.substring(key.indexOf('_') + 1);
groups[postfix] ??= [];
groups[postfix].push({
timeKey,
oid: value.object._id,
});
}
// ===== output the groups separate =====
for (const [postfix, states] of Object.entries(groups)) {
// Header (one time))
historicValuesItems[`Header_${prefix}_${postfix}`] = {
newLine: true,
type: 'header',
text: `${prefix} ${postfix}`,
size: 3,
};
// ===== sort in the groupt chronological =====
states.sort((a, b) => this.getChronoOffset(a.timeKey) - this.getChronoOffset(b.timeKey));
// Output the states
for (const state of states) {
historicValuesItems[`Value_${prefix}_${postfix}_${state.timeKey}`] = {
type: 'state',
label: state.timeKey,
oid: state.oid,
foreign: true,
};
}
}
}
}
if (this.adapter.objectStore.devices[id].info) {
deviceInfoItems[`Header`] = {
newLine: true,
type: 'header',
text: `deviceInfo`,
size: 3,
};
const deviceInfo = this.adapter.objectStore.devices[id].info;
const sortedEntries = Object.entries(deviceInfo)
.filter(([k]) => k !== 'object')
.sort(([, a], [, b]) =>
a.object.common.name.localeCompare(b.object.common.name, { sensitivity: 'base' }),
);
for (const [k, v] of sortedEntries) {
if (k === 'object') {
continue;
}
const label = deviceInfo[k].object.common.name;
deviceInfoItems[`${label}`] = {
newLine: true,
type: 'state',
label: label,
oid: v.object._id,
foreign: true,
};
}
}
// Devices Object
deviceObjectItems['DeviceObjectHeader'] = {
newLine: true,
type: 'header',
text: 'DeviceObject',
size: 3,
};
deviceObjectItems['DeviceObject'] = {
type: 'text',
readOnly: true,
minRows: 30,
maxRows: 30,
};
data.DeviceObject = JSON.stringify(this.adapter.objectStore.devices[id], null, 2);
// eslint-disable-next-line jsdoc/check-tag-names
/** @type {import('@iobroker/dm-utils').JsonFormSchema} */
const schema = {
type: 'tabs',
tabsStyle: {
minWidth: 850,
},
items: {},
};
if (this.adapter.objectStore.devices[id].onlineValues) {
schema.items.onlineValues = {
type: 'panel',
label: 'onlineValues',
items: onlineValuesItems,
};
}
if (this.adapter.objectStore.devices[id].historicValues) {
schema.items.historicValues = {
type: 'panel',
label: 'historicValues',
items: historicValuesItems,
};
}
schema.items.deviceInfoItems = {
type: 'panel',
label: 'deviceInfo',
items: deviceInfoItems,
};
schema.items.deviceObtectItems = {
type: 'panel',
label: 'deviceObject',
items: deviceObjectItems,
};
// return the schema
return { id, schema, data };
}
/**
* @param key Key tha is to define
*/
getChronoOffset(key) {
// Hours
if (key === 'Last_1_Hour') {
return 1;
}
if (key === 'Last_X_Hours') {
return 10;
}
if (key.startsWith('Last_') && key.endsWith('_Hours')) {
const hours = parseInt(key.match(/\d+/)?.[0], 10);
return hours ?? 9999;
}
// Days
if (key.startsWith('Today')) {
const timevalue = 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue + 1 : timevalue;
}
if (key.startsWith('Yesterday')) {
const timevalue = 48;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue + 1 : timevalue;
}
// Weeks
if (key.startsWith('ThisWeek')) {
const timevalue = 7 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
if (key.startsWith('LastWeek')) {
const timevalue = 14 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
// Month
if (key.startsWith('ThisMonth')) {
const timevalue = 31 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
if (key.startsWith('LastMonth')) {
const timevalue = 2 * 31 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
// Quarter
if (key.startsWith('ThisQuarter')) {
const timevalue = 3 * 31 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
if (key.startsWith('LastQuarter')) {
const timevalue = 2 * 3 * 31 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
// Year
if (key.startsWith('ThisYear')) {
const timevalue = 365 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
if (key.startsWith('LastYear')) {
const timevalue = 2 * 365 * 24;
return key.endsWith('-1Year') ? 3 * 365 * 24 + timevalue : timevalue;
}
// Flexible Time
if (key.startsWith('FlexibleTime')) {
const hours = parseInt(key.match(/\d+/)?.[0], 10) + 50000;
return hours ?? 9999;
}
// Unbekannt → nach unten
return 100000;
}
}
module.exports = GridVisDeviceManagement;