iobroker.valuetrackerovertime
Version:
Tracks number-changes over timepsans like hours/days/years ...
1,279 lines (1,199 loc) • 61.7 kB
JavaScript
'use strict';
/*
* Created with @iobroker/create-adapter v1.27.0
*/
// The adapter-core module gives you access to the core ioBroker functions
// you need to create an adapter
const utils = require('@iobroker/adapter-core');
const cron = require('node-cron'); // Cron Schedulervar
const ObjectSettings = require('./ObjectSettings.js');
const historyData = require('./historyData.js');
const KWInfo = require('./KWInfo.js');
const DateHelper = require('./dateHelper.js');
const TimeFrames = {
Minute: 'Minute',
Hour: 'Hour',
Day: 'Day',
Week: 'Week',
Month: 'Month',
Quarter: 'Quarter',
Year: 'Year',
Infinite: 'Infinite',
};
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const TimeFramesNumber = {
Minute: '001',
Hour: '002',
Day: '01',
Week: '02',
Month: '03',
Quarter: '04',
Year: '05',
Infinite: '06',
};
// Load your modules here, e.g.:
// const fs = require("fs");
class valuetrackerovertime extends utils.Adapter {
/**
* @param [options]
*/
constructor(options) {
super({
...options,
name: 'valuetrackerovertime',
});
this.dicDatas = {};
this.myObjects = {};
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
this.on('objectChange', this.onObjectChange.bind(this));
this.on('unload', this.onUnload.bind(this));
this.writeTrimeFrameInfo = true;
this.historyWrite = 0;
this.crons = [];
}
/**
* Is called when databases are connected and adapter received configuration.
*/
async onReady() {
await this.subscribeForeignObjectsAsync('*');
await this.initialObjects();
this.crons.push(
cron.schedule('* * * * *', async () => {
await this._timeFrameFinished(TimeFrames.Minute);
}),
);
this.crons.push(
cron.schedule('0 * * * *', async () => {
await this._timeFrameFinished(TimeFrames.Hour);
}),
);
this.crons.push(
cron.schedule('0 0 * * *', async () => {
await this._timeFrameFinished(TimeFrames.Day);
}),
);
this.crons.push(
cron.schedule('0 0 * * 1', async () => {
await this._timeFrameFinished(TimeFrames.Week);
}),
);
this.crons.push(
cron.schedule('0 0 1 * *', async () => {
await this._timeFrameFinished(TimeFrames.Month);
}),
);
this.crons.push(
cron.schedule('0 0 1 1,4,7,10 *', async () => {
await this._timeFrameFinished(TimeFrames.Quarter);
}),
);
this.crons.push(
cron.schedule('0 0 1 1 *', async () => {
await this._timeFrameFinished(TimeFrames.Year);
}),
);
this.crons.push(
cron.schedule('0 0 1 1 *', async () => {
//await this._timeFrameFinished(TimeFrames.Infinite);
}),
);
}
/**
* Is called if a subscribed object changes
*
* @param id
* @param obj
*/
async onObjectChange(id, obj) {
await this._initialObject(obj);
}
/**
* Is called if a subscribed state changes
*
* @param id
* @param state
*/
async onStateChange(id, state) {
if (state) {
if (id in this.dicDatas) {
await this._publishCurrentValue(
this.dicDatas[id],
new Date(state.ts),
await this._getNumberfromState(state),
);
} else if (id.startsWith(this.namespace) && id.includes('_startValues.start_')) {
const TimeFrame = id.substring(id.lastIndexOf('_') + 1);
const idsplit = id.split('.');
for (const oneoSID in this.dicDatas) {
/**@type {ObjectSettings} */
const oS = this.dicDatas[oneoSID];
if (oS.alias == idsplit[2]) {
if (state.ack) {
oS.startValues[TimeFrame] = await this._getNumberfromState(state);
} else {
this.log.warn(
`${id} changed, recalc Timeframe, old-Value: ${await this._getStartValue(
oS,
TimeFrame,
)} new-value: ${await this._getNumberfromState(state)}`,
);
await this._setStartValue(oS, TimeFrame, await this._getNumberfromState(state));
}
}
}
}
}
}
/**
* Is called when adapter shuts down - callback has to be called under any circumstances!
*
* @param callback
*/
async onUnload(callback) {
try {
await this.unsubscribeForeignStatesAsync('*');
await this.unsubscribeForeignObjectsAsync('*');
for (const cron_i in this.crons) {
const onecron = this.crons[cron_i];
onecron.destroy();
}
callback();
} catch {
callback();
}
}
/**
* Is called if a subscribed object changes
*
* @param TimeFrame
*/
async _timeFrameFinished(TimeFrame) {
const date = new Date();
for (const oneOD in this.dicDatas) {
const oS = this.dicDatas[oneOD];
this.log.debug(`${oS.alias} TimeFrame ${TimeFrame} end, insert now previous values `);
await this._pushNewPreviousSates(
oS,
TimeFrame,
oS.lastGoodValue - (await this._getStartValue(oS, TimeFrame)),
await this._getDateTimeInfoForPrevious(TimeFrame, date, 1),
);
this.log.debug(`${oS.alias} set startValue from TimeFrame ${TimeFrame} to ${oS.lastGoodValue}`);
await this._setStartValue(oS, TimeFrame, oS.lastGoodValue);
}
}
/**
* Get the Number from Iobroker State
*
* @param State
* @returns
*/
async _getNumberfromState(State) {
if (State && State.val && !isNaN(Number(State.val))) {
return Number(State.val);
}
return 0;
}
/**
* Read all Objects in iobroker and try to initial every Datapoint (only wenn custom set)
*/
async initialObjects() {
this.log.info('inital all Objects');
const objectschannels = await this.getForeignObjectsAsync(`${this.namespace}*`, 'channel');
for (const id in objectschannels) {
await this._setMyObject(id, objectschannels[id]);
}
const objectView = await this.getObjectViewAsync('system', 'custom', null);
if (objectView && objectView.rows) {
for (const counterObjectView in objectView.rows) {
const oneObjectview = objectView.rows[counterObjectView];
if (oneObjectview && oneObjectview.value && this.namespace in oneObjectview.value) {
await this._initialObject(await this.getForeignObjectAsync(oneObjectview.id));
}
}
}
this.log.info('initial completed');
}
async _getObjectAsync(ObjectID) {
if (!(ObjectID in this.myObjects)) {
this.myObjects[ObjectID] = await this.getObjectAsync(ObjectID);
}
return this.myObjects[ObjectID];
}
_setMyObject(id, obj) {
if (id.startsWith(this.namespace)) {
this.myObjects[id.substring(this.namespace.length + 1)] = obj;
}
}
/**
* Try to Initial an Datapoint (if Custom Setting exists, otherwise it is uninitial)
*
* @param iobrokerObject
*/
async _initialObject(iobrokerObject) {
if (iobrokerObject && iobrokerObject != undefined) {
await this._setMyObject(iobrokerObject._id, iobrokerObject);
// uninitialize ID
if (iobrokerObject._id in this.dicDatas) {
this.log.info(`disable : ${iobrokerObject._id}`);
await this._setExtendChannel(this.dicDatas[iobrokerObject._id], '', 'disabled', true);
await this.unsubscribeForeignStatesAsync(iobrokerObject._id);
delete this.dicDatas[iobrokerObject._id];
}
// only do something when enabled
if (
iobrokerObject &&
iobrokerObject.common &&
iobrokerObject.common.custom &&
iobrokerObject.common.custom[this.namespace] &&
iobrokerObject.common.custom[this.namespace].enabled
) {
const oS = new ObjectSettings(iobrokerObject, this.namespace);
//HistoryLoad
if (oS.history_work) {
iobrokerObject.common.custom[this.namespace].history_work = false;
await this.setForeignObjectAsync(oS.id, iobrokerObject);
await this._history_readDataFromHistory(oS);
return;
}
if (
iobrokerObject.common.custom[this.namespace].work !== undefined &&
!iobrokerObject.common.custom[this.namespace].work
) {
this.log.info(`not initial (work disabled)): ${iobrokerObject._id}`);
} else {
this.log.info(`initial (enabled): ${iobrokerObject._id}`);
//Check for duplicate Alias
for (const oneoSIdtoCheck in this.dicDatas) {
/**@type {ObjectSettings} */
const oStoCheck = this.dicDatas[oneoSIdtoCheck];
if (oStoCheck.alias.toLowerCase() == oS.alias.toLowerCase()) {
this.log.error(
`The Datapoint ${oS.id} have the same Alias (${oS.alias}) as the Datapoint ${
oStoCheck.id
}, ${oS.id} is now disabled`,
);
return;
}
}
//Do Subcribe and Create Objects
await this._generateTreeStructure(oS);
this.log.debug(`subscribeForeignStates ${oS.id}`);
await this.subscribeStatesAsync(`${oS.alias}._startValues.*`);
await this.subscribeForeignStatesAsync(oS.id);
//Read out last good value
const currentval = await this._getNumberfromState(await this.getForeignStateAsync(oS.id));
const startDay = await this._getStartValue(oS, TimeFrames.Day);
if (currentval < startDay) {
oS.lastGoodValue = startDay;
} else {
oS.lastGoodValue = currentval;
}
await this._publishCurrentValue(oS, new Date(), currentval);
this.dicDatas[oS.id] = oS;
}
this.log.debug(`initial done ${iobrokerObject._id} -> ${this.namespace}.${oS.alias}`);
}
}
}
/**
* Pull the before Values one level back and write set the latest with current TimeFrameValue
*
* @param oS
* @param TimeFrame
* @param TimeFrameValue
* @param DateTimeInfo
*/
async _pushNewPreviousSates(oS, TimeFrame, TimeFrameValue, DateTimeInfo) {
//Days before befüllen
let iBeforeCount;
//Die PreviousValues jeweils ein nach hinten schieben (von hinten anfangen um keine Daten zu verlieren)
for (iBeforeCount = oS.beforeCount(TimeFrame); iBeforeCount > 1; iBeforeCount--) {
const theValBefore = await this.getStateAsync(
oS.alias + (await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount - 1)),
);
const theObjectBefore = await this._getObjectAsync(
oS.alias + (await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount - 1)),
);
if (theValBefore && theObjectBefore && typeof theValBefore.val === 'number') {
await this._setStateRoundedAsync(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount),
theValBefore.val,
false,
);
await this._setExtendObject(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount),
theObjectBefore.common.name.toString(),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
}
//den jetzigen previous Wert speichern
if (oS.beforeCount(TimeFrame) >= 1) {
await this._setStateRoundedAsync(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount),
TimeFrameValue,
true,
);
await this._setExtendObject(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBeforeCount),
DateTimeInfo,
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
}
/**
* Analyse for CounterReset and recalc current TimeFrames
*
* @param oS
* @param date
* @param current_value
*/
async _publishCurrentValue(oS, date, current_value) {
if (oS.counterResetDetection && current_value < oS.lastGoodValue) {
//Verringerung erkannt -> neuanpassung der startWerte
if (Number.isNaN(oS.FirstWrongValue)) {
oS.FirstWrongValue = current_value;
oS.counterResetDetetion_CurrentCountAfterReset = 0;
oS.lastWrongValue = NaN;
}
//nur veränderte werte werden gezählt
if (oS.lastWrongValue != current_value) {
oS.counterResetDetetion_CurrentCountAfterReset += 1;
oS.lastWrongValue = current_value;
}
// Wenn der counterResetDetetion_CountAfterReset noch nicht erreicht ist, diesen Wert einfach ignorieren
if (oS.counterResetDetetion_CurrentCountAfterReset <= oS.counterResetDetetion_CountAfterReset) {
return;
}
//Ein Counter-Reset wurde erkannt, passe Startwerte an
this.log.warn(
`${oS.id} wurde scheinbar resetet! Reset von ${oS.lastGoodValue} nach ${
current_value
} passe alle Startwerte an`,
);
const theAnpassung = oS.lastGoodValue - oS.FirstWrongValue;
oS.lastGoodValue = current_value;
for (const TimeFrame in TimeFrames) {
await this._setStartValue(oS, TimeFrame, (await this._getStartValue(oS, TimeFrame)) - theAnpassung);
}
}
oS.lastGoodValue = current_value;
oS.FirstWrongValue = NaN;
for (const TimeFrame in TimeFrames) {
await this._calcCurrentTimeFrameValue(oS, date, TimeFrame);
}
}
/**
* Recalculate the Current and Detailed Values
*
* @param oS
* @param date
* @param TimeFrame
*/
async _calcCurrentTimeFrameValue(oS, date, TimeFrame) {
const TimeFrame_value = oS.lastGoodValue - (await this._getStartValue(oS, TimeFrame));
//Set Current_TimeframeValue
if (oS.beforeCount(TimeFrame) >= 0) {
await this._setStateRoundedAsync(oS, await this._getObjectIDCurrent(TimeFrame), TimeFrame_value, true);
}
//Set Detailed
await this._CreateAndSetObjectIdDetailed(oS, TimeFrame, date, TimeFrame_value);
}
/**
* round and set the State in iobroker
*
* @param oS
* @param id
* @param value
* @param outputMultiplie
*/
async _setStateRoundedAsync(oS, id, value, outputMultiplie) {
if (value) {
if (outputMultiplie) {
value *= oS.output_multiplier;
}
value = Number(value.toFixed(10));
}
await this.setStateAsync(oS.alias + id, value, true);
}
/**
* returns the quarter of the date
*
* @param date
*/
_getQuarter(date) {
return Math.ceil((date.getMonth() + 1) / 3);
}
/**
* create for every enabled object the needed current, history and before Datapoints
*
* @param oS
*/
async _generateTreeStructure(oS) {
await this._setExtendChannel(oS, '', `CounterData for ${oS.id}`, true);
await this._setExtendObject(oS, '._counterID', 'ObjectID', '', true, false, null, 'string');
await this.setStateAsync(`${oS.alias}._counterID`, oS.id, true);
await this._setExtendChannel(oS, '._startValues', 'startValues for TimeFrames', true);
for (const TimeFrame in TimeFrames) {
//Current_DP erzeugen/anpassen
if (oS.beforeCount(TimeFrame) >= 0) {
await this._setExtendObject(
oS,
await this._getObjectIDCurrent(TimeFrame),
`Current ${TimeFrame}`,
`value.Current.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
} else {
await this._setExtendObject(
oS,
await this._getObjectIDCurrent(TimeFrame),
'Disabled',
'value.Current.disabled',
false,
false,
oS.output_unit,
'number',
);
}
//Before erzeugen bzw leeren
let iBefore = 1;
for (iBefore = 1; iBefore <= oS.beforeCount(TimeFrame); iBefore++) {
const thePreviousID = await this._GetObjectIdPrevious(oS, TimeFrame, iBefore);
const oldObject = await this._getObjectAsync(oS.alias + thePreviousID);
let touseName = 'no data yet';
if (oldObject) {
touseName = oldObject.common.name.toString();
}
await this._setExtendObject(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBefore),
touseName,
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
//Before Objecte die existieren aber nicht mehr aktiv sind auf diabled stellen
let theObject = await this._getObjectAsync(
oS.alias + (await this._GetObjectIdPrevious(oS, TimeFrame, iBefore)),
);
while (theObject != null) {
await this._setExtendObject(
oS,
await this._GetObjectIdPrevious(oS, TimeFrame, iBefore),
theObject.common.name.toString(),
'value.history.disabled',
false,
false,
oS.output_unit,
'number',
);
iBefore++;
theObject = await this._getObjectAsync(
oS.alias + (await this._GetObjectIdPrevious(oS, TimeFrame, iBefore)),
);
}
//CurrentYear Detailed erzeugen
const oneDateThisYear = new Date();
if (oS.detailed_current(TimeFrame)) {
for (let i = 0; i < 366; i++) {
oneDateThisYear.setDate(oneDateThisYear.getDate() - 1);
await this._CreateAndGetObjectIdDetailedCurrent(oS, TimeFrame, oneDateThisYear);
}
}
}
}
/**
* Change or Create a iobroker Object if it is necessary
*
* @param oS
* @param id
* @param name
* @param role
* @param createIfnotExists
* @param writeable
* @param writeable
* @param unit
* @param type
*/
async _setExtendObject(oS, id, name, role, createIfnotExists, writeable, unit, type) {
name = await this._replaceTimeFrameInfo(name, id);
if (!name.includes(` (${oS.alias})`)) {
name += ` (${oS.alias})`;
}
let theObject = await this._getObjectAsync(oS.alias + id);
if (
theObject == null ||
theObject.common.name != name ||
theObject.common.role != role ||
theObject.common.unit != unit ||
theObject.common.write != writeable ||
theObject.common.type != type
) {
if (createIfnotExists || theObject != null) {
if (theObject == null || theObject == undefined) {
theObject = {
type: 'state',
common: {
desc: `Created by ${this.namespace}`,
},
native: {},
};
}
theObject.type = 'state';
theObject.common.name = name;
theObject.common.role = role;
theObject.common.type = type;
theObject.common.unit = unit;
theObject.common.write = writeable;
theObject.common.read = true;
await this.setObjectAsync(oS.alias + id, theObject);
this.myObjects[oS.alias + id] = await this._getObjectAsync(oS.alias + id);
}
}
}
/**
* Replace the Timeframe info on Datapoints with variable data (previous and Current History) with static Text (reduce ObjectChanges)
*
* @param name
* @param id
*/
async _replaceTimeFrameInfo(name, id) {
if (this.writeTrimeFrameInfo == false) {
if (name.startsWith('Timeframe ') && !id.startsWith('20')) {
name = id.split('.').join(' ');
}
}
return name;
}
/**
* Change or Create a iobroker Channel if necessary
*
* @param oS
* @param id
* @param name
* @param createIfnotExists
*/
async _setExtendChannel(oS, id, name, createIfnotExists) {
name = await this._replaceTimeFrameInfo(name, id);
if (!name.includes(` (${oS.alias})`)) {
name += ` (${oS.alias})`;
}
let theObject = await this._getObjectAsync(oS.alias + id);
if (
theObject == null ||
theObject == undefined ||
theObject.common.name != name ||
theObject.type != 'channel'
) {
if (createIfnotExists || theObject != null) {
if (theObject == null || theObject == undefined) {
theObject = {
common: {
desc: `Created by ${this.namespace}`,
},
native: {},
};
}
theObject.common.name = name;
theObject.type = 'channel';
await this.setObjectAsync(oS.alias + id, theObject);
this.myObjects[oS.alias + id] = await this._getObjectAsync(oS.alias + id);
}
}
}
/**
* Build the ObjectID String for Previous Datapoints
*
* @param oS
* @param TimeFrame
* @param beforeCounter
*/
async _GetObjectIdPrevious(oS, TimeFrame, beforeCounter) {
if (oS.beforeCount(TimeFrame) > 0) {
await this._setExtendChannel(
oS,
`.${TimeFramesNumber[TimeFrame]}_previous${TimeFrame}s`,
`${TimeFrame}s Before`,
true,
);
} else {
//Channel Auf disabled setzen wenn es bereits gibt
await this._setExtendChannel(
oS,
`.${TimeFramesNumber[TimeFrame]}_previous${TimeFrame}s`,
'disabled',
false,
);
}
const theID = `.${TimeFramesNumber[TimeFrame]}_previous${TimeFrame}s.Before_${beforeCounter
.toString()
.padStart(2, '0')}_${TimeFrame}`;
return theID;
}
/**
* Build a string for DatapointName for a Previous DP
*
* @param TimeFrame
* @param theDate
* @param beforeZähler
*/
async _getDateTimeInfoForPrevious(TimeFrame, theDate, beforeZähler) {
const newdate = await this._dateChangeTimeFrame(TimeFrame, theDate, beforeZähler * -1);
return await this._getTimeFrameInfo(TimeFrame, newdate);
}
/**
* Build a string for DatapointName
*
* @param TimeFrame
* @param theDate
*/
async _getTimeFrameInfo(TimeFrame, theDate) {
let ret = '';
if (TimeFrame == TimeFrames.Minute) {
ret = `minute ${DateHelper.GetTime(theDate)}`;
} else if (TimeFrame == TimeFrames.Hour) {
ret = `hour ${DateHelper.GetTime(theDate)}`;
} else if (TimeFrame == TimeFrames.Day) {
ret = `${DateHelper.GetDateNumber(theDate)} ${DateHelper.GetMonthName(theDate)} ${theDate.getFullYear()}`;
} else if (TimeFrame == TimeFrames.Week) {
const theKW = new KWInfo(theDate);
ret = theKW.InfoString;
} else if (TimeFrame == TimeFrames.Month) {
ret = `${DateHelper.GetMonthName(theDate)} ${theDate.getFullYear()}`;
} else if (TimeFrame == TimeFrames.Quarter) {
const myquarter = await this._getQuarter(theDate);
ret = `quarter ${myquarter} `;
ret += `${DateHelper.GetMonthNamefromNumber((myquarter - 1) * 3 + 0)},`;
ret += `${DateHelper.GetMonthNamefromNumber((myquarter - 1) * 3 + 1)},`;
ret += `${DateHelper.GetMonthNamefromNumber((myquarter - 1) * 3 + 2)} `;
ret += ` ${theDate.getFullYear()}`;
} else if (TimeFrame == TimeFrames.Year) {
ret = `year ${theDate.getFullYear().toString()}`;
} else if (TimeFrame == TimeFrames.Infinite) {
ret = 'Infinite';
}
return `Timeframe ${ret}`;
}
/**
* Build a substring for ObjectID
*
* @param TimeFrame
* @param theDate
*/
async _getTimeFrameObjectID(TimeFrame, theDate) {
if (TimeFrame == TimeFrames.Minute) {
return 'not valid';
} else if (TimeFrame == TimeFrames.Hour) {
return 'not valid';
} else if (TimeFrame == TimeFrames.Day) {
return DateHelper.GetDateNumber(theDate);
} else if (TimeFrame == TimeFrames.Week) {
const theKW = new KWInfo(theDate);
return `KW${theKW.weekNumberString}`;
} else if (TimeFrame == TimeFrames.Month) {
return `${DateHelper.GetMonthNumber(theDate)}_${DateHelper.GetMonthName(theDate)}`;
} else if (TimeFrame == TimeFrames.Quarter) {
return `quater_${await this._getQuarter(theDate)}`;
} else if (TimeFrame == TimeFrames.Year) {
return `${TimeFramesNumber.Year}_Year_${theDate.getFullYear()}`;
} else if (TimeFrame == TimeFrames.Infinite) {
return `${TimeFramesNumber.Year}_Infinite`;
}
return '';
}
/**
* Create and Fill Detailed Datapoints (Current year and Detailed)
*
* @param oS
* @param TimeFrame
* @param date
* @param TimeFrame_value
*/
async _CreateAndSetObjectIdDetailed(oS, TimeFrame, date, TimeFrame_value) {
const a = await this._CreateAndGetObjectIdDetailed(oS, TimeFrame, date);
if (a) {
await this._setStateRoundedAsync(oS, a, TimeFrame_value, true);
}
const b = await this._CreateAndGetObjectIdDetailedCurrent(oS, TimeFrame, date);
if (b) {
await this._setStateRoundedAsync(oS, b, TimeFrame_value, true);
}
}
/**
* Creates the needed TreeStructure and returns the Id after alias-name
*
* @param oS
* @param TimeFrame
* @param date
*/
async _CreateAndGetObjectIdDetailed(oS, TimeFrame, date) {
if (oS.detailed(TimeFrame)) {
let mydetailedObjectId = '';
mydetailedObjectId = `.${date.getFullYear()}`;
await this._setExtendChannel(oS, mydetailedObjectId, String(date.getFullYear()), true);
if (TimeFrame == TimeFrames.Infinite) {
//nothing to do
} else if (TimeFrame == TimeFrames.Year) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Year, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Year, date),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
} else {
mydetailedObjectId = `${mydetailedObjectId}.${TimeFramesNumber[TimeFrame]}_${TimeFrame}s`;
await this._setExtendChannel(oS, mydetailedObjectId, `${TimeFrame}s`, true);
if (TimeFrame == TimeFrames.Day) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Month, date)}`;
await this._setExtendChannel(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Month, date),
true,
);
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Day, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Day, date),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
if (TimeFrame == TimeFrames.Week) {
const theKWInfo = new KWInfo(date);
if (date.getFullYear() != theKWInfo.yearOfThursday) {
mydetailedObjectId.replace(date.getFullYear().toString(), theKWInfo.yearOfThursday.toString());
}
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Week, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Week, date),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
if (TimeFrame == TimeFrames.Month) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Month, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Month, date),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
if (TimeFrame == TimeFrames.Quarter) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Quarter, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Quarter, date),
`value.history.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
}
}
return mydetailedObjectId;
}
return null;
}
/**
* Creates the needed TreeStructure and returns the Id after alias-name
*
* @param TimeFrame
* @param date
*/
async _getTimeFrameBeginn(TimeFrame, date) {
const Checkdate = new Date(date);
Checkdate.setMilliseconds(0);
Checkdate.setSeconds(0);
if (TimeFrame == TimeFrames.Minute) {
return Checkdate;
}
Checkdate.setMinutes(0);
if (TimeFrame == TimeFrames.Hour) {
return Checkdate;
}
Checkdate.setHours(0);
if (TimeFrame == TimeFrames.Day) {
return Checkdate;
}
if (TimeFrame == TimeFrames.Week) {
Checkdate.setDate(Checkdate.getDate() - (Checkdate.getDay() == 0 ? 6 : Checkdate.getDay() - 1));
return Checkdate;
}
Checkdate.setDate(1);
if (TimeFrame == TimeFrames.Month) {
return Checkdate;
}
if (TimeFrame == TimeFrames.Quarter) {
Checkdate.setMonth(Checkdate.getMonth() - (Checkdate.getMonth() % 3));
return Checkdate;
}
Checkdate.setMonth(0);
if (TimeFrame == TimeFrames.Year) {
return Checkdate;
}
Checkdate.setFullYear(1990);
if (TimeFrame == TimeFrames.Infinite) {
return Checkdate;
}
return Checkdate;
}
/**
* Creates the needed TreeStructure and returns the Id after alias-name, returns null if date not should write in current
*
* @param oS
* @param TimeFrame
* @param date
*/
async _CreateAndGetObjectIdDetailedCurrent(oS, TimeFrame, date) {
if (oS.detailed_current(TimeFrame)) {
let mydetailedObjectId = '';
let endOfTimeFrame = await this._getTimeFrameBeginn(TimeFrame, new Date());
endOfTimeFrame = await this._dateChangeTimeFrame(TimeFrame, endOfTimeFrame, 1);
endOfTimeFrame.setMilliseconds(-1);
const currentDataRelevantBegin = new Date(endOfTimeFrame);
if (TimeFrame != TimeFrames.Year) {
mydetailedObjectId = `${mydetailedObjectId}.${TimeFramesNumber[TimeFrame]}_${TimeFrame}s`;
await this._setExtendChannel(oS, mydetailedObjectId, `${TimeFrame}s`, true);
if (TimeFrame == TimeFrames.Day) {
if (date >= (await this._dateChangeTimeFrame(TimeFrames.Day, endOfTimeFrame, -7))) {
let myDayNumber = date.getDay();
if (myDayNumber == 0) {
myDayNumber = 7;
}
mydetailedObjectId = `${mydetailedObjectId}.${myDayNumber}_${days[date.getDay()]}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Day, date),
`value.currenthistory.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
return mydetailedObjectId;
}
}
if (TimeFrame == TimeFrames.Week) {
currentDataRelevantBegin.setFullYear(currentDataRelevantBegin.getFullYear() - 1);
if (date >= currentDataRelevantBegin) {
{
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Week, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Week, date),
`value.currenthistory.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
return mydetailedObjectId;
}
}
}
if (TimeFrame == TimeFrames.Month) {
currentDataRelevantBegin.setFullYear(currentDataRelevantBegin.getFullYear() - 1);
if (date >= currentDataRelevantBegin) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Month, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Month, date),
`value.currenthistory.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
return mydetailedObjectId;
}
}
if (TimeFrame == TimeFrames.Quarter) {
currentDataRelevantBegin.setFullYear(currentDataRelevantBegin.getFullYear() - 1);
if (date >= currentDataRelevantBegin) {
mydetailedObjectId = `${mydetailedObjectId}.${await this._getTimeFrameObjectID(TimeFrames.Quarter, date)}`;
await this._setExtendObject(
oS,
mydetailedObjectId,
await this._getTimeFrameInfo(TimeFrames.Quarter, date),
`value.currenthistory.${TimeFrame}`,
true,
false,
oS.output_unit,
'number',
);
return mydetailedObjectId;
}
}
}
}
return null;
}
/**
* returns the ObjectID for Current Timeframe
*
* @param TimeFrame
*/
async _getObjectIDCurrent(TimeFrame) {
return `.${TimeFramesNumber[TimeFrame]}_current${TimeFrame}`;
}
/**
* save the start-value
*
* @param oS
* @param TimeFrame
* @param value
*/
async _setStartValue(oS, TimeFrame, value) {
oS.startValues[TimeFrame] = value;
await this._setStateRoundedAsync(oS, await this._getStartID(TimeFrame), value, false);
await this._calcCurrentTimeFrameValue(oS, new Date(), TimeFrame);
}
/**
* Returns the startid
*
* @param TimeFrame
*/
async _getStartID(TimeFrame) {
return `._startValues.start_${TimeFramesNumber[TimeFrame]}_${TimeFrame}`;
}
/**
* Returns the StartVaalue of the current Timeframe
*
* @param oS
* @param TimeFrame
*/
async _getStartValue(oS, TimeFrame) {
//Create the DP if not exists
if (TimeFrame in oS.startValues) {
return oS.startValues[TimeFrame];
}
const startID = await this._getStartID(TimeFrame);
await this._setExtendObject(
oS,
startID,
`start_${TimeFrame}`,
'',
true,
true,
oS.iobrokerObject.common.unit,
'number',
);
//set startData if not set
const state = await this.getStateAsync(oS.alias + startID);
if (state && state.val != null && !isNaN(Number(state.val))) {
oS.startValues[TimeFrame] = Number(state.val);
} else {
const currentValue = await this._getNumberfromState(await this.getForeignStateAsync(oS.id));
await this._setStateRoundedAsync(oS, startID, currentValue, false);
oS.startValues[TimeFrame] = currentValue;
}
return oS.startValues[TimeFrame];
}
/**
* Check and store Data to the history-Instance of the Current_DP
*
* @param oS
* @param ts
* @param TimeFrame
* @param isTimeFrameChange
*/
async _history_fill_history(oS, ts, TimeFrame, isTimeFrameChange) {
if (oS.history_fill_history_enabled[TimeFrame] && oS.historyReadoutData.lastGoodhis) {
const val =
(oS.historyReadoutData.lastGoodhis.val - oS.historyReadoutData.hisstartvalues[TimeFrame]) *
oS.output_multiplier;
if (
isTimeFrameChange ||
Math.abs(val - oS.historyReadoutData.lastwriteValues[TimeFrame]) >=
oS.history_fill_history_MinChange[TimeFrame]
) {
if (this.historyWrite % oS.history_fill_history_parallelstore == 0) {
await this.sendToAsync(oS.history_fill_history_Instanz, 'query', '');
}
const state = [{ val: val, ts: ts }];
if (isTimeFrameChange) {
state.push({ val: 0, ts: ts + 1 });
}
await this.sendToAsync(oS.history_fill_history_Instanz, 'storeState', {
id: oS.history_fill_history_DPs[TimeFrame],
state: state,
});
this.historyWrite += 1;
oS.historyReadoutData.lastwriteValues[TimeFrame] = val;
oS.historyReadoutData.workcounts.historyWrite += state.length;
}
}
}
/**
* read DetailedData from History Instanz and analyse it
*
* @param oS
*/
async _history_readDataFromHistory(oS) {
//Read the history-Instances if empty
if (!oS.history_Instanz || oS.history_Instanz == '') {
const system_config = await this.getForeignObjectAsync('system.config');
if (system_config) {
oS.history_Instanz = system_config.common.defaultHistory;
}
}
if (!oS.history_fill_history_Instanz || oS.history_fill_history_Instanz == '') {
oS.history_fill_history_Instanz = oS.history_Instanz;
}
this.log.info(
`HistoryAnalyse ${oS.id}: Query from Storage Instance '${
oS.history_Instanz
}' all Data (if not working after this please check the instance is active) `,
);
const end = Date.now();
const start = end - 100 * 365 * 24 * 3600000;
const gethistory = await this.sendToAsync(oS.history_Instanz, 'getHistory', {
id: oS.id,
options: {
end: end,
start: start,
count: 1000000000,
aggregate: 'none',
},
});
if (gethistory) {
if (gethistory['error']) {
this.log.error(`HistoryAnalyse ${oS.id}: Fehler bei Datenlesen: ${gethistory['error']}`);
} else if (gethistory['result']) {
this.log.info(`HistoryAnalyse ${oS.id}: wandle SQL Datensätze um: ${gethistory['result'].length}`);
const allData = [];
for (const one in gethistory['result']) {
allData.push(new historyData(gethistory['result'][one].ts, gethistory['result'][one].val));
}
allData.push(new historyData(new Date(), oS.lastGoodValue));
await this._history_DoAnalyseHistoryData(oS, allData);
}
} else {
this.log.warn('no Response from Instance (active and exists?)');
}
}
/**
* Checks if a TimeFrame-Jump happend with the MyHisDate, if happend it write the detailed, before and history
*
* @param oS
* @param TimeFrame
* @param MyHisDate
*/
async _history_TimeFrameDetection(oS, TimeFrame, MyHisDate) {
if (oS.historyReadoutData.lastGoodhis && oS.historyReadoutData.lastHis) {
if (
MyHisDate.getDate() == oS.historyReadoutData.lastHis.date.getDate() &&
MyHisDate.getFullYear() == oS.historyReadoutData.lastHis.date.getFullYear() &&
MyHisDate.getMonth() == oS.historyReadoutData.lastHis.date.getMonth()
) {
// wenn gleicher Tag wie vorheriges datum, dann kann kein TimeFrameWechsel sein
return;
}
const startForMyHisDate = await this._getTimeFrameBeginn(TimeFrame, MyHisDate);
let startForOldTimeframe = await this._getTimeFrameBeginn(TimeFrame, oS.historyReadoutData.lastHis.date);
while (startForOldTimeframe < startForMyHisDate) {
const TimeFrameValue =
oS.historyReadoutData.lastGoodhis.val - oS.historyReadoutData.hisstartvalues[TimeFrame];
if (oS.history_fill_detailed) {
//Detailed setzen
await this._CreateAndSetObjectIdDetailed(oS, TimeFrame, startForOldTimeframe, TimeFrameValue);
oS.historyReadoutData.workcounts.detailed += 1;
}
if (oS.history_fill_before) {
//liste füllen (für before-Data)
oS.historyReadoutData.timeFrameValueData[TimeFrame].push({
date: new Date(startForOldTimeframe),
value: TimeFrameValue,
});
}
startForOldTimeframe = await this._dateChangeTimeFrame(TimeFrame, startForOldTimeframe, 1);
await this._history_fill_history(oS, startForOldTimeframe.getTime() - 1, TimeFrame, true);
oS.historyReadoutData.hisstartvalues[TimeFrame] = oS.historyReadoutData.lastGoodhis.val;
}
}
}
/**
* Adds or Remove Timeframes from Date
*
* @param TimeFrame
* @param theDate
* @param numberofChange
*/
async _dateChangeTimeFrame(TimeFrame, theDate, numberofChange) {
const newdate = new Date(theDate);
if (TimeFrame == TimeFrames.Minute) {
newdate.setMinutes(newdate.getMinutes() + numberofChange);
} else if (TimeFrame == TimeFrames.Hour) {
newdate.setHours(newdate.getHours() + numberofChange);
} else if (TimeFrame == TimeFrames.Day) {
newdate.setDate(newdate.getDate() + numberofChange);
} else if (TimeFrame == TimeFrames.Week) {
newdate.setDate(newdate.getDate() + 7 * numberofChange);
} else if (TimeFrame == TimeFrames.Month) {
newdate.setMonth(newdate.getMonth() + numberofChange);
} else if (TimeFrame == TimeFrames.Quarter) {
newdate.setMonth(newdate.getMonth() + numberofChange * 3);
} else if (TimeFrame == TimeFrames.Year) {
newdate.setFullYear(newdate.getFullYear() + numberofChange);
} else if (TimeFrame == TimeFrames.Infinite) {
newdate.setFullYear(newdate.getFullYear() + numberofChange * 1000);
}
return newdate;
}
/**
* Use the Data to Fill the Datastructure Detailed
*
* @param oS
* @param historyDataList
*/
async _history_DoAnalyseHistoryData(oS, historyDataList) {
this.log.info(`HistoryAnalyse ${oS.id}: Verarbeite ${historyDataList.length} History Datensätze `);
// Needed for Logging
const logEveryMS = 10000;
let last