iobroker.tibberlink
Version:
links tibber API data to be used in ioBroker
780 lines • 71.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TibberCalculator = void 0;
const date_fns_1 = require("date-fns");
const projectUtils_js_1 = require("./projectUtils.js");
class TibberCalculator extends projectUtils_js_1.ProjectUtils {
numBestCost;
numBestSingleHours;
numBestHoursBlock;
numBestCostLTF;
numBestSingleHoursLTF;
numBestHoursBlockLTF;
numSmartBatteryBuffer;
numBestPercentage;
numBestPercentageLTF;
numSmartBatteryBufferLTF;
constructor(adapter) {
super(adapter);
this.numBestCost = 0;
this.numBestSingleHours = 0;
this.numBestHoursBlock = 0;
this.numBestCostLTF = 0;
this.numBestSingleHoursLTF = 0;
this.numBestHoursBlockLTF = 0;
this.numSmartBatteryBuffer = 0;
this.numBestPercentage = 0;
this.numBestPercentageLTF = 0;
this.numSmartBatteryBufferLTF = 0;
}
initStats() {
this.numBestCost = 0;
this.numBestSingleHours = 0;
this.numBestHoursBlock = 0;
this.numBestCostLTF = 0;
this.numBestSingleHoursLTF = 0;
this.numBestHoursBlockLTF = 0;
this.numSmartBatteryBuffer = 0;
this.numBestPercentage = 0;
this.numBestPercentageLTF = 0;
this.numSmartBatteryBufferLTF = 0;
}
increaseStatsValueByOne(type) {
switch (type) {
case projectUtils_js_1.enCalcType.BestCost:
this.numBestCost++;
break;
case projectUtils_js_1.enCalcType.BestSingleHours:
this.numBestSingleHours++;
break;
case projectUtils_js_1.enCalcType.BestHoursBlock:
this.numBestHoursBlock++;
break;
case projectUtils_js_1.enCalcType.BestCostLTF:
this.numBestCostLTF++;
break;
case projectUtils_js_1.enCalcType.BestSingleHoursLTF:
this.numBestSingleHoursLTF++;
break;
case projectUtils_js_1.enCalcType.BestHoursBlockLTF:
this.numBestHoursBlockLTF++;
break;
case projectUtils_js_1.enCalcType.SmartBatteryBuffer:
this.numSmartBatteryBuffer++;
break;
case projectUtils_js_1.enCalcType.BestPercentage:
this.numBestPercentage++;
break;
case projectUtils_js_1.enCalcType.BestPercentageLTF:
this.numBestPercentageLTF++;
break;
case projectUtils_js_1.enCalcType.SmartBatteryBufferLTF:
this.numSmartBatteryBufferLTF++;
}
}
async setupCalculatorStates(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig.chName === undefined) {
channelConfig.chName = `Channel Name`;
}
const typeDesc = (0, projectUtils_js_1.getCalcTypeDescription)(channelConfig.chType);
await this.adapter.setObject(`Homes.${homeId}.Calculations.${channel}`, {
type: "channel",
common: {
name: channelConfig.chName,
desc: `type: ${typeDesc}`,
},
native: {},
});
if (channelConfig.chActive === undefined) {
channelConfig.chActive = false;
}
void this.checkAndSetValueBoolean(`Homes.${homeId}.Calculations.${channel}.Active`, channelConfig.chActive, `Whether the calculation channel is active`, `switch.enable`, true, true);
const valueActive = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.Active`);
if (typeof valueActive === "boolean") {
channelConfig.chActive = valueActive;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to Active: ${channelConfig.chActive}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chActive: ${valueActive}`);
}
switch (channelConfig.chType) {
case projectUtils_js_1.enCalcType.BestCost:
await this.deleteLTFInputs(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStart`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStop`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setup_chTriggerPrice(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestSingleHours:
await this.deleteLTFInputs(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStart`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStop`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setup_chAmountHours(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestHoursBlock:
await this.deleteLTFInputs(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setup_chAmountHours(homeId, channel);
this.setup_chAverageTotalCost(homeId, channel);
this.setup_chBlockStartFullHour(homeId, channel);
this.setup_chBlockEndFullHour(homeId, channel);
this.setup_chBlockStart(homeId, channel);
this.setup_chBlockEnd(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestCostLTF:
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setupLTFInputs(homeId, channel);
await this.setup_chTriggerPrice(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestSingleHoursLTF:
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setupLTFInputs(homeId, channel);
await this.setup_chAmountHours(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestHoursBlockLTF:
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setupLTFInputs(homeId, channel);
await this.setup_chAmountHours(homeId, channel);
this.setup_chAverageTotalCost(homeId, channel);
this.setup_chBlockStartFullHour(homeId, channel);
this.setup_chBlockEndFullHour(homeId, channel);
this.setup_chBlockStart(homeId, channel);
this.setup_chBlockEnd(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.SmartBatteryBuffer:
await this.deleteLTFInputs(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStart`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStop`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setup_chAmountHours(homeId, channel);
await this.setup_chEfficiencyLoss(homeId, channel);
await this.setup_chOutput(homeId, channel);
await this.setup_chOutput2(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
this.setup_chOutputJSON2(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestPercentage:
await this.deleteLTFInputs(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStart`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStop`);
await this.setup_chPercentage(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.BestPercentageLTF:
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.setupLTFInputs(homeId, channel);
await this.setup_chPercentage(homeId, channel);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`);
await this.setup_chOutput(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
break;
case projectUtils_js_1.enCalcType.SmartBatteryBufferLTF:
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStart`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.BlockStop`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Percentage`);
await this.setupLTFInputs(homeId, channel);
await this.setup_chAmountHours(homeId, channel);
await this.setup_chEfficiencyLoss(homeId, channel);
await this.setup_chOutput(homeId, channel);
await this.setup_chOutput2(homeId, channel);
this.setup_chOutputJSON(homeId, channel);
this.setup_chOutputJSON2(homeId, channel);
break;
default:
this.adapter.log.error(`Calculator Type for channel ${channel} not set, please do!`);
}
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.Active`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.StartTime`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.StopTime`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.RepeatDays`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
this.adapter.subscribeStates(`Homes.${homeId}.Calculations.${channel}.Percentage`);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of states for calculator`));
}
}
async setupLTFInputs(homeId, channel) {
const channelConfig = this.adapter.config.CalculatorList[channel];
try {
if (channelConfig.chStartTime === undefined) {
const today = new Date();
today.setHours(0, 0, 0, 0);
channelConfig.chStartTime = today;
}
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.StartTime`, channelConfig.chStartTime.toISOString(), `Start time for this channel`, `date`, true, true);
const valueStartTime = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.StartTime`);
if (typeof valueStartTime === "string") {
channelConfig.chStartTime.setTime(Date.parse(valueStartTime));
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to StartTime: ${channelConfig.chStartTime.toISOString()}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chStartTime: ${valueStartTime}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state StartTime for calculator`));
}
try {
if (channelConfig.chStopTime === undefined) {
const today = new Date();
today.setHours(23, 59, 0, 0);
channelConfig.chStopTime = today;
}
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.StopTime`, channelConfig.chStopTime.toISOString(), `Stop time for this channel`, `date`, true, true);
const valueStopTime = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.StopTime`);
if (typeof valueStopTime === "string") {
channelConfig.chStopTime.setTime(Date.parse(valueStopTime));
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to StopTime: ${channelConfig.chStopTime.toISOString()}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chStopTime: ${valueStopTime}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state StopTime for calculator`));
}
try {
if (channelConfig.chRepeatDays === undefined) {
channelConfig.chRepeatDays = 0;
}
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.RepeatDays`, channelConfig.chRepeatDays, `number of days to shift this LTF channel for repetition`, undefined, `level`, true, true);
const valueRepeatDays = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.RepeatDays`);
if (typeof valueRepeatDays === "number") {
channelConfig.chRepeatDays = valueRepeatDays;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to RepeatDays: ${channelConfig.chRepeatDays}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chTriggerPrice: ${valueRepeatDays}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state RepeatDays for calculator`));
}
}
async deleteLTFInputs(homeId, channel) {
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.StartTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.StopTime`);
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.RepeatDays`);
}
async setup_chOutput(homeId, channel) {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig?.chTargetState && channelConfig.chTargetState.length > 10 && !channelConfig.chTargetState.startsWith("choose your state to drive")) {
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output`);
}
else {
try {
void this.checkAndSetValueBoolean(`Homes.${homeId}.Calculations.${channel}.Output`, false, `standard output if no special one selected in config`, `switch.enable`, false, true);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state Output for calculator for Home ${homeId}, Channel ${channel}`));
}
}
}
async setup_chOutput2(homeId, channel) {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig?.chTargetState2 &&
channelConfig.chTargetState2.length > 10 &&
!channelConfig.chTargetState2.startsWith("choose your state to drive")) {
await this.adapter.delObjectAsync(`Homes.${homeId}.Calculations.${channel}.Output2`);
}
else {
try {
void this.checkAndSetValueBoolean(`Homes.${homeId}.Calculations.${channel}.Output2`, false, `standard output2 if no special one selected in config`, `switch.enable`, false, true);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state Output2 for calculator for Home ${homeId}, Channel ${channel}`));
}
}
}
setup_chOutputJSON(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.OutputJSON`, `[]`, `JSON output to see the schedule the channel will follow`, `json`, false, true);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state OutputJSON in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state OutputJSON for calculator in Home ${homeId}, Channel ${channel}`));
}
}
setup_chOutputJSON2(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.OutputJSON2`, `[]`, `JSON output 2 to see the schedule the channel will follow`, `json`, false, true);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state OutputJSON2 in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state OutputJSON2 for calculator in Home ${homeId}, Channel ${channel}`));
}
}
async setup_chTriggerPrice(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig.chTriggerPrice === undefined) {
channelConfig.chTriggerPrice = 0;
}
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`, channelConfig.chTriggerPrice, `pricelevel to trigger this channel at`, undefined, `level.max`, true, true);
const valueTriggerPrice = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.TriggerPrice`);
if (typeof valueTriggerPrice === "number") {
channelConfig.chTriggerPrice = valueTriggerPrice;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to TriggerPrice: ${channelConfig.chTriggerPrice}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chTriggerPrice: ${valueTriggerPrice}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state TriggerPrice for calculator`));
}
}
async setup_chAmountHours(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig.chAmountHours === undefined) {
channelConfig.chAmountHours = 0;
}
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.AmountHours`, channelConfig.chAmountHours, `amount of hours to trigger this channel`, undefined, `level`, true, true);
const valueAmountHours = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.AmountHours`);
if (typeof valueAmountHours === "number") {
channelConfig.chAmountHours = valueAmountHours;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to AmountHours: ${channelConfig.chAmountHours}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chAmountHours: ${valueAmountHours}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state AmountHours for calculator`));
}
}
async setup_chPercentage(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig.chPercentage === undefined) {
channelConfig.chPercentage = 0;
}
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.Percentage`, channelConfig.chPercentage, `amount of percentage to trigger this channel`, undefined, `level.max`, true, true);
const valuePercentage = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.Percentage`);
if (typeof valuePercentage === "number") {
channelConfig.chPercentage = valuePercentage;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to percentage: ${channelConfig.chPercentage}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chPercentage: ${valuePercentage}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state Percentage for calculator`));
}
}
async setup_chEfficiencyLoss(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
if (channelConfig.chEfficiencyLoss === undefined) {
channelConfig.chEfficiencyLoss = 0;
}
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`, channelConfig.chEfficiencyLoss, `efficiency loss between charge and discharge of battery system`, undefined, `level.max`, true, true);
const valueEfficiencyLoss = await this.getStateValue(`Homes.${homeId}.Calculations.${channel}.EfficiencyLoss`);
if (typeof valueEfficiencyLoss === "number") {
channelConfig.chAmountHours = valueEfficiencyLoss;
this.adapter.log.debug(`[tibberCalculator]: setup settings state in home: ${homeId} - channel: ${channel}-${channelConfig.chName} - set to EfficiencyLoss: ${channelConfig.chEfficiencyLoss}`);
}
else {
this.adapter.log.debug(`[tibberCalculator]: wrong type for chEfficiencyLoss: ${valueEfficiencyLoss}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state EfficiencyLoss for calculator`));
}
}
setup_chAverageTotalCost(homeId, channel) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValueNumber(`Homes.${homeId}.Calculations.${channel}.AverageTotalCost`, 0, `average total cost in determined block`, undefined, `value`, false, false);
this.adapter.log.debug(`[tibberCalculator]: setup output state AverageTotalCost in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `setup of state AverageTotalCost for calculator`));
}
}
setup_chBlockStartFullHour(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.BlockStartFullHour`, `-`, `first hour of determined block`, `value`, false, false);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state BlockStartFullHour in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state BlockStartFullHour for calculator in Home ${homeId}, Channel ${channel}`));
}
}
setup_chBlockEndFullHour(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.BlockEndFullHour`, `-`, `end hour of determined block`, `value`, false, false);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state BlockEndFullHour in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state BlockEndFullHour for calculator`));
}
}
setup_chBlockStart(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.BlockStart`, `-`, `start date string of determined block`, `date`, false, false);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state BlockStart in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state BlockStart for calculator`));
}
}
setup_chBlockEnd(homeId, channel, delMode = false) {
try {
const channelConfig = this.adapter.config.CalculatorList[channel];
void this.checkAndSetValue(`Homes.${homeId}.Calculations.${channel}.BlockEnd`, `-`, `stop date string of determined block`, `date`, false, false);
if (!delMode) {
this.adapter.log.debug(`[tibberCalculator]: setup output state BlockEnd in home: ${homeId} - channel: ${channel}-${channelConfig.chName}`);
}
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `write state BlockEnd for calculator`));
}
}
async startCalculatorTasks(onStateChange = false, firstRun = false) {
if (!this.adapter.config.UseCalculator) {
return;
}
const badComponents = ["tibberlink", "Homes", "Calculations"];
for (const channel in this.adapter.config.CalculatorList) {
if (firstRun) {
this.adapter.config.CalculatorList[channel].chChannelID = channel;
if (!this.adapter.config.CalculatorList[channel] ||
!this.adapter.config.CalculatorList[channel].chTargetState ||
!this.adapter.config.CalculatorList[channel].chTargetState.trim()) {
this.adapter.log.warn(`Empty destination state in calculator channel ${channel} defined - provide correct external state - channel will use internal state OUTPUT`);
}
if (this.adapter.config.CalculatorList[channel].chTargetState != null &&
typeof this.adapter.config.CalculatorList[channel].chTargetState === "string" &&
this.adapter.config.CalculatorList[channel].chTargetState !== "") {
const chTargetStateComponents = this.adapter.config.CalculatorList[channel].chTargetState.split(".");
let foundAllBadComponents = true;
badComponents.forEach(badComponent => {
if (!chTargetStateComponents.includes(badComponent)) {
foundAllBadComponents = false;
}
});
if (foundAllBadComponents) {
this.adapter.log.error(`Invalid destination state defined in calculator channel ${channel}. Please avoid specifying the activation state of this channel as the destination. Skipping channel execution.`);
continue;
}
}
else {
this.adapter.log.debug(`[tibberCalculator]: chTargetState is null or undefined in channel ${channel}. Skipping channel output verification.`);
}
if (this.adapter.config.CalculatorList[channel].chType === projectUtils_js_1.enCalcType.SmartBatteryBuffer ||
this.adapter.config.CalculatorList[channel].chType === projectUtils_js_1.enCalcType.SmartBatteryBufferLTF) {
if (!this.adapter.config.CalculatorList[channel] ||
!this.adapter.config.CalculatorList[channel].chTargetState2 ||
!this.adapter.config.CalculatorList[channel].chTargetState2.trim()) {
this.adapter.log.warn(`Empty second destination state in calculator channel ${channel} defined - provide correct external state 2 - upon this, channel will use internal state OUTPUT2`);
}
if (this.adapter.config.CalculatorList[channel].chTargetState2 != null &&
typeof this.adapter.config.CalculatorList[channel].chTargetState2 === "string" &&
this.adapter.config.CalculatorList[channel].chTargetState2 !== "") {
const chTargetState2Components = this.adapter.config.CalculatorList[channel].chTargetState2.split(".");
let foundAllBadComponents = true;
badComponents.forEach(badComponent => {
if (!chTargetState2Components.includes(badComponent)) {
foundAllBadComponents = false;
}
});
if (foundAllBadComponents) {
this.adapter.log.error(`Invalid second destination state defined in calculator channel ${channel}. Please avoid specifying the activation state of this channel as the destination. Skipping channel execution.`);
continue;
}
}
else {
this.adapter.log.debug(`[tibberCalculator]: chTargetState2 is null or undefined in channel ${channel}. Skipping channel output verification.`);
}
if (this.adapter.config.CalculatorList[channel].chValueOn2 == null ||
this.adapter.config.CalculatorList[channel].chValueOn2 === "" ||
this.adapter.config.CalculatorList[channel].chValueOff2 == null ||
this.adapter.config.CalculatorList[channel].chValueOff2 === "") {
this.adapter.log.error(`"Value YES 2" or "Value NO 2" is null or undefined in calculator channel ${channel}. Please provide usable values in config.`);
continue;
}
}
}
try {
if (this.adapter.config.CalculatorList[channel].chActive || onStateChange) {
switch (this.adapter.config.CalculatorList[channel].chType) {
case projectUtils_js_1.enCalcType.BestCost:
await this.executeCalculatorBestCost(parseInt(channel));
break;
case projectUtils_js_1.enCalcType.BestSingleHours:
await this.executeCalculatorBestSingleHours(parseInt(channel));
break;
case projectUtils_js_1.enCalcType.BestHoursBlock:
await this.executeCalculatorBestHoursBlock(parseInt(channel));
break;
case projectUtils_js_1.enCalcType.BestCostLTF:
await this.executeCalculatorBestCost(parseInt(channel), true);
break;
case projectUtils_js_1.enCalcType.BestSingleHoursLTF:
await this.executeCalculatorBestSingleHours(parseInt(channel), true);
break;
case projectUtils_js_1.enCalcType.BestHoursBlockLTF:
await this.executeCalculatorBestHoursBlock(parseInt(channel), true);
break;
case projectUtils_js_1.enCalcType.SmartBatteryBuffer:
await this.executeCalculatorSmartBatteryBuffer(parseInt(channel));
break;
case projectUtils_js_1.enCalcType.BestPercentage:
await this.executeCalculatorBestPercentage(parseInt(channel));
break;
case projectUtils_js_1.enCalcType.BestPercentageLTF:
await this.executeCalculatorBestPercentage(parseInt(channel), true);
break;
case projectUtils_js_1.enCalcType.SmartBatteryBufferLTF:
await this.executeCalculatorSmartBatteryBuffer(parseInt(channel), true);
break;
default:
this.adapter.log.debug(`[tibberCalculator]: unknown value for type: ${this.adapter.config.CalculatorList[channel].chType}`);
}
}
else {
this.adapter.log.debug(`[tibberCalculator]: channel ${channel} - ${(0, projectUtils_js_1.getCalcTypeDescription)(this.adapter.config.CalculatorList[channel].chType)}; execution skipped because channel not set to active in channel states`);
}
}
catch (error) {
this.adapter.log.warn(`unhandled error ${error} while executing calculator channel ${channel}`);
}
}
}
updateCalculatorUsageStats() {
if (!this.adapter.config.UseCalculator) {
return;
}
this.initStats();
this.adapter.config.CalculatorList.forEach(channel => {
try {
this.increaseStatsValueByOne(channel.chType);
}
catch (error) {
this.adapter.log.debug(`[tibberCalculator]: unhandled error ${error} in calculator usage scan`);
}
});
}
async executeCalculatorBestCost(channel, modeLTF = false) {
const now = new Date();
const channelConfig = this.adapter.config.CalculatorList[channel];
let valueToSet = channelConfig.chValueOff;
try {
if (!channelConfig.chActive) {
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, `[]`, true);
}
else if (modeLTF && now < channelConfig.chStartTime) {
const filteredPrices = await this.getPricesLTF(channel, modeLTF, true);
const jsonOutput = filteredPrices
.map((entry) => ({
hour: new Date(entry.startsAt).getHours(),
startsAt: entry.startsAt,
total: entry.total,
output: channelConfig.chTriggerPrice > entry.total ? true : false,
}))
.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, JSON.stringify(jsonOutput, null, 2), true);
}
else if (modeLTF && now > channelConfig.chStopTime) {
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, `[]`, true);
this.handleAfterLTF(channel);
}
else {
const filteredPrices = await this.getPricesLTF(channel, modeLTF, true);
const currentPrice = await this.getStateValue(`Homes.${channelConfig.chHomeID}.CurrentPrice.total`);
if (channelConfig.chTriggerPrice > currentPrice) {
valueToSet = channelConfig.chValueOn;
}
const jsonOutput = filteredPrices
.map((entry) => ({
hour: new Date(entry.startsAt).getHours(),
startsAt: entry.startsAt,
total: entry.total,
output: channelConfig.chTriggerPrice > entry.total ? true : false,
}))
.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, JSON.stringify(jsonOutput, null, 2), true);
}
this.setChannelOutStates(channel, valueToSet);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `execute calculator for ${(0, projectUtils_js_1.getCalcTypeDescription)(channelConfig.chType)} in channel ${channel}`));
}
}
async executeCalculatorBestSingleHours(channel, modeLTF = false) {
const now = new Date();
const channelConfig = this.adapter.config.CalculatorList[channel];
let valueToSet = channelConfig.chValueOff;
try {
if (!channelConfig.chActive) {
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, `[]`, true);
}
else if (modeLTF && now < channelConfig.chStartTime) {
const filteredPrices = await this.getPricesLTF(channel, modeLTF);
filteredPrices.sort((a, b) => a.total - b.total);
const channelResult = filteredPrices.slice(0, channelConfig.chAmountHours).map((entry) => checkHourMatch(entry));
const jsonOutput = filteredPrices
.map((entry, index) => ({
hour: new Date(entry.startsAt).getHours(),
startsAt: entry.startsAt,
total: entry.total,
output: channelResult[index] !== undefined ? true : false,
}))
.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, JSON.stringify(jsonOutput, null, 2), true);
}
else if (modeLTF && now > channelConfig.chStopTime) {
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, `[]`, true);
this.handleAfterLTF(channel);
}
else {
const filteredPrices = await this.getPricesLTF(channel, modeLTF);
filteredPrices.sort((a, b) => a.total - b.total);
const channelResult = filteredPrices.slice(0, channelConfig.chAmountHours).map((entry) => checkHourMatch(entry));
if (channelResult.some(value => value)) {
valueToSet = channelConfig.chValueOn;
}
const jsonOutput = filteredPrices
.map((entry, index) => ({
hour: new Date(entry.startsAt).getHours(),
startsAt: entry.startsAt,
total: entry.total,
output: channelResult[index] !== undefined ? true : false,
}))
.sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime());
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, JSON.stringify(jsonOutput, null, 2), true);
}
this.setChannelOutStates(channel, valueToSet);
}
catch (error) {
this.adapter.log.warn(this.generateErrorMessage(error, `execute calculator for ${(0, projectUtils_js_1.getCalcTypeDescription)(channelConfig.chType)} in channel ${channel}`));
}
}
async executeCalculatorBestHoursBlock(channel, modeLTF = false) {
const now = new Date();
const channelConfig = this.adapter.config.CalculatorList[channel];
let valueToSet = channelConfig.chValueOff;
try {
if (!channelConfig.chActive) {
void this.adapter.setState(`Homes.${channelConfig.chHomeID}.Calculations.${channel}.OutputJSON`, `[]`, true);
this.setup_chBlockStartFullHour(channelConfig.chHomeID, channel, true);
this.setup_chBlockEndFullHour(channelConfig.chHomeID, channel, true);
this.setup_chBlockStart(channelConfig.chHomeID, channel, true);
this.setup_chBlockEnd(channelConfig.chHomeID, channel, true);
}
else if (modeLTF && now < channelConfig.chStartTime) {
this.setup_chBlockStartFullHour(channelConfig.chHomeID, channel, true);
this.setup_chBlockEndFullHour(channelConfig.chHomeID, channel, true);
this.setup_chBlockStart(channelConfig.chHomeID, channel, true);
this.setup_chBlockEnd(channelConfig.chHomeID, channel, true);
const filteredPrices = await this.getPricesLTF(channel, modeLTF);
let minSum = Number.MAX_VALUE;
let startIndex = 0;
const n = Math.min(channelConfig.chAmountHours, filteredPrices.length);
for (let i = 0; i < filteredPrices.length - n + 1; i++) {
let sum = 0;
for (let j = i; j < i + n; j++) {
sum += filteredPrices[j].total;
}
if (sum < minSum) {
minSum = sum;
startIndex = i;
}
}
const channelResult = filteredPrices.slice(startIndex, startIndex + n).map((entry) => checkHourMatch(entry));
const jsonOut