iobroker.tibberlink
Version: 
links tibber API data to be used in ioBroker
780 lines (779 loc) • 71.5 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?.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?.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 jsonOutput = filteredPrices
                    .map((entry, index) => ({