Skip to content

Commit

Permalink
removed forecast fallback; added wattpilot data
Browse files Browse the repository at this point in the history
  • Loading branch information
chpro committed Jun 13, 2024
1 parent 2546b9e commit 48bd8d5
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 79 deletions.
1 change: 1 addition & 0 deletions app/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const CONFIG = {
influxBaseUrl: process.env.INFLUX_BASE_URL || "http://tig:8086",
influxToken: process.env.INFLUX_TOKEN,
inverterPowerFlowUrl: process.env.INVERTER_POWER_FLOW_URL || "http://inverter.localdomain/status/powerflow",
wattpilotMetricsUrl: process.env.WATTPILOT_METRICS_URL || "http://microservices.localdomain:9101/metrics",

// shelly switch
switch0Host: process.env.SWITCH0_HOST || "heatingrod.localdomain",
Expand Down
30 changes: 3 additions & 27 deletions app/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ function determineNewSwitchStatus(processedStatusValues) {
}
}

// determinSwitchStatusByGriByGridUsage then determineNewSwitchStatusByCharge and last determineNewSwitchStatusByForecast
// determinSwitchStatusByGriByGridUsage then determineNewSwitchStatusByCharge
return determinSwitchStatusByGridUsage(processedStatusValues, function(){return SWITCH_STATUS.ON_ENERGY}, determineNewSwitchStatusByCharge);
}

Expand Down Expand Up @@ -167,12 +167,11 @@ function determinSwitchStatusByGridUsage(statusValues, onStatusFunction, offStat
* @returns The SWITCH_STATUS which was determined due to the passed values
*/
function determineNewSwitchStatusByCharge(statusValues) {
// check the forecast also take in cosideration current usage off net and a lower max watertemperature
if (CONFIG.minBatteryCharge !== 0 && canConsumeBatteryCharge(statusValues)) {
console.log(new Date(), "Calling determine switch status by grid usage status values for battery charge");
return determinSwitchStatusByGridUsage(shiftAvailableEnergy(statusValues), function(){return SWITCH_STATUS.ON_HIGH_BATTERY_CHARGE}, determineNewSwitchStatusByForecast);
return determinSwitchStatusByGridUsage(shiftAvailableEnergy(statusValues), function(){return SWITCH_STATUS.ON_HIGH_BATTERY_CHARGE}, function(){return SWITCH_STATUS.OFF_LOW_ENERGY});
} else {
return determineNewSwitchStatusByForecast(statusValues);
return SWITCH_STATUS.OFF_LOW_ENERGY;
}
}

Expand All @@ -192,29 +191,6 @@ function shiftAvailableEnergy(statusValues) {
return statusValues
}

/**
* The off status function to determine status by forecast in another step
* @param {Object} statusValues
* @returns The SWITCH_STATUS which was determined due to the passed values
*/
function determineNewSwitchStatusByForecast(statusValues) {
// check the forecast also take in cosideration current usage off net and a lower max watertemperature
if (isForcastFallbackEnabled(statusValues) &&
!isWaterTemperatureToHigh(statusValues, CONFIG.maxWaterTemperatureFallback , CONFIG.maxWaterTemperatureDelta) &&
isWithinOperatingHours(Math.min(statusValues.forecast.time.getHours(), CONFIG.startHourFallback), CONFIG.endHourFallback)) {
// shift available energy by part of the appliances watt usage except we alreasy use energy from grid
console.log(new Date(), "Calling determine switch status by grid usage status values for forecast");
return determinSwitchStatusByGridUsage(shiftAvailableEnergy(statusValues), function(){return SWITCH_STATUS.ON_FORECAST}, function(){return SWITCH_STATUS.OFF_FORECAST});
} else {
return SWITCH_STATUS.OFF_LOW_ENERGY;
}
}

function isForcastFallbackEnabled(statusValues) {
return statusValues.forecast && statusValues.forecast.value && statusValues.forecast.time &&
statusValues.forecast.value <= CONFIG.wattThresholdToSwitchOn
}

/**
* Checks if current water temperature is to high also implements temperature delta logic
* @param {Object} statusValues
Expand Down
53 changes: 18 additions & 35 deletions app/influxdataprovider.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ const axios = require('axios');
const CONFIG = require('./config').CONFIG


const INFLUX_FORECAST_PRODUCTION_LAST = function() {
let start = new Date();
start.setHours(0,0,0,0);
let end = new Date();
end.setHours(23, 59, 59, 999)
return `${CONFIG.influxBaseUrl}/query?pretty=true&db=pvforecast&q=SELECT max("value") FROM "autogen"."pv_forecast_watts" WHERE time >= '${start.toISOString()}' and time <= '${end.toISOString()}'`
}
const INFLUX_WATER_TEMPERATURE_LAST = `${CONFIG.influxBaseUrl}/query?pretty=true&db=prometheus&q=SELECT last("value") FROM "autogen"."eta_buffer_temperature_sensor_top_celsius" WHERE time >= now() - 5m and time <= now()`;
const INFLUX_GRID_USAGE_LAST = `${CONFIG.influxBaseUrl}/query?pretty=true&db=inverter&q=SELECT last("P_Grid") FROM "autogen"."powerflow" WHERE time >= now() - 5m and time <= now()`;
const INFLUX_GRID_USAGE_MEAN = `${CONFIG.influxBaseUrl}/query?pretty=true&db=inverter&q=SELECT mean("P_Grid") FROM "autogen"."powerflow" WHERE time >= now() - 10m and time <= now()`;
Expand Down Expand Up @@ -37,60 +30,52 @@ function getValue(result) {
}
}

/**
*
* @param {JSON} result The json where to get timestamp from
* @returns The parsed timestamp which should be a Date or null if an error occurs
*/
function getTimestamp(result) {
if (result === null) {
console.log(new Date(), "Could not get timestamp from null result")
return null;
}
try {
return new Date(result.results[0].series[0].values[0][0]);
} catch (error) {
console.log(new Date(), "Could not get timestamp from JSON", result);
return null;
function transformWattpilotResponse(response) {
var retVal = {power: 0}
// parse wattpilot_power{host="wattpilot.localdomain",serial="91036822"} 0 1718021949741
var regex = /^ *wattpilot_power\{.*\} ([0-9\.]*) .*$/gm;
var res = regex.exec(response)
if (res !== null && res[1]) {
retVal.power = Number(res[1])
}
return retVal;
}

function getCurrentStatusValues(switchOn, callback) {
axios.all([
axios.get(INFLUX_GRID_USAGE_LAST, {headers: INFLUX_REQUEST_HEADER}),
axios.get(INFLUX_GRID_USAGE_MEAN, {headers: INFLUX_REQUEST_HEADER}),
axios.get(INFLUX_WATER_TEMPERATURE_LAST, {headers: INFLUX_REQUEST_HEADER}),
axios.get(INFLUX_FORECAST_PRODUCTION_LAST(), {headers: INFLUX_REQUEST_HEADER}),
axios.get(INFLUX_BOILER_STATUS, {headers: INFLUX_REQUEST_HEADER}),
axios.get(INFLUX_BATTERY_CHARGE_LAST, {headers: INFLUX_REQUEST_HEADER}),
axios.get(INVERTER_POWER_FLOW)
]).then(axios.spread((gridLastRes, gridMeanRes, waterTemperatureRes, forecastRes, boilerStatusRes, batteryChargeRes, inverterPowerFlowRes) => {
axios.get(INVERTER_POWER_FLOW),
axios.get(CONFIG.wattpilotMetricsUrl, {transformResponse: transformWattpilotResponse})
]).then(axios.spread((gridLastRes, gridMeanRes, waterTemperatureRes, boilerStatusRes, batteryChargeRes, inverterPowerFlowRes, wattpilotRes) => {
callback(getStatusValues(
getValue(gridMeanRes.data),
getValue(gridLastRes.data),
getValue(waterTemperatureRes.data),
switchOn,
getValue(forecastRes.data),
getTimestamp(forecastRes.data),
getValue(boilerStatusRes.data),
getValue(batteryChargeRes.data),
inverterPowerFlowRes.data.site));
inverterPowerFlowRes.data.site,
wattpilotRes.data));
})).catch(err => {
console.log(new Date(), err);
callback(processStatusValues(null));
});
}

function getStatusValues(wattGridUsageMean = null, wattGridUsageLast = null, currentWaterTemperature = null, switchOn = null, forecastValue = null, forecastTime = null, boilerStatus = null, batteryCharge = null, inverterPowerFlow = null) {
function getStatusValues(wattGridUsageMean = null, wattGridUsageLast = null, currentWaterTemperature = null, switchOn = null, boilerStatus = null, batteryCharge = null, inverterPowerFlow = null, wattpilot = {power: 0}) {
let o = {};
o.wattGridUsageMean = wattGridUsageMean;
o.wattGridUsageLast = wattGridUsageLast;
o.currentWaterTemperature = currentWaterTemperature;
o.switchOn = switchOn;
o.boilerStatus = boilerStatus;
o.forecast = {value: forecastValue, time: forecastTime}
o.batteryCharge = batteryCharge
o.inverterPowerFlow = inverterPowerFlow
o.wattpilot = wattpilot
console.log(new Date(), "Raw status values: ", o)
return processStatusValues(o);
}
Expand All @@ -101,7 +86,6 @@ function processStatusValues(currentStatusValues) {
if (currentStatusValues === null) {
statusValues.gridEnergy = null;
statusValues.primarySourceActive = false;
statusValues.forecast = null;
statusValues.currentWaterTemperature = null;
statusValues.availableEnergy = null;
return statusValues;
Expand All @@ -114,9 +98,9 @@ function processStatusValues(currentStatusValues) {

var wattGridUsage = null
if (currentStatusValues.inverterPowerFlow !== null) {
// the usage is calculated without power flow to battery
console.log(new Date(), "WattGridUsage is calculated from inverterPowerFlow");
wattGridUsage = (currentStatusValues.inverterPowerFlow.P_PV - Math.abs(currentStatusValues.inverterPowerFlow.P_Load)) * -1
// the usage is calculated without power flow to battery and wattpilot usage
console.log(new Date(), "WattGridUsage is calculated from inverterPowerFlow and wattpilot usage");
wattGridUsage = (currentStatusValues.inverterPowerFlow.P_PV + currentStatusValues.wattpilot.power - Math.abs(currentStatusValues.inverterPowerFlow.P_Load)) * -1
} else {// fallback and also used for test cases
console.log(new Date(), "WattGridUsage is calculated from wattGridUsageMean/Max");
wattGridUsage = currentStatusValues.switchOn ? currentStatusValues.wattGridUsageLast : Math.max(currentStatusValues.wattGridUsageMean, currentStatusValues.wattGridUsageLast);
Expand All @@ -137,7 +121,6 @@ function processStatusValues(currentStatusValues) {
}

statusValues.primarySourceActive = CONFIG.offWhenPrimarySourceActive && currentStatusValues.boilerStatus && currentStatusValues.boilerStatus !== 0;
statusValues.forecast = currentStatusValues.forecast;
statusValues.currentWaterTemperature = currentStatusValues.currentWaterTemperature;
return statusValues;
}
Expand Down
2 changes: 0 additions & 2 deletions app/switchstatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const CONFIG = require('./config').CONFIG

const SWITCH_STATUS = {
ON_HIGH_BATTERY_CHARGE: {on: true, status: 6, message: "On high battery charge is used to cover low production", timerPeriod: CONFIG.timerPeriodOnEnergy},
ON_FORECAST: {on: true, status: 5, message: "On due to forecast fallback operating mode", timerPeriod: CONFIG.timerPeriodOnFallback},
ON_FALLBACK: {on: true, status: 4, message: "On due to no value for energy production was available and time within fallback operating hours", timerPeriod: CONFIG.timerPeriodOnFallback},
ON_MANUALLY: {on: true, status: 3, message: "On due to manual intervention", timerPeriod: CONFIG.timerPeriodManually},
ON_LOW_TEMPERATURE: {on: true, status: 2, message: "On due to low water temperature", timerPeriod: CONFIG.timerPeriodOnLowTemperature},
Expand All @@ -12,7 +11,6 @@ const SWITCH_STATUS = {
OFF_NIGHT: {on: false, status: -2, message: "Off due time outside normal operation hours", timerPeriod: CONFIG.timerPeriodOffNight},
OFF_MANUALLY: {on: false, status: -3, message: "Off due to manual intervention", timerPeriod: CONFIG.timerPeriodManually},
OFF_FALLBACK: {on: false, status: -4, message: "Off due to no value for energy production was available and time outside fallback operating hours", timerPeriod: CONFIG.timerPeriodOffFallback},
OFF_FORECAST: {on: false, status: -5, message: "Off due to too low energy production within the forecast fallback operating mode", timerPeriod: CONFIG.timerPeriodOffFallback},
OFF_PRIMARY_SOURCE_ACTIVE: {on: false, status: -6, message: "Off due to too another appliance is enabled and producing hot water", timerPeriod: CONFIG.timerPeriodOffPrimarySourceActive},
OFF_LOW_BATTERY_CHARGE: {on: false, status: -7, message: "Off due to too low battery charge", timerPeriod: CONFIG.timerPeriodOffLowBatteryCharge},
};
Expand Down
17 changes: 2 additions & 15 deletions test/control.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,6 @@ assertMeanLast(null, null, CONFIG.maxWaterTemperatureFallback + 1, SWITCH_STATUS
assertMeanLast(null, null, CONFIG.maxWaterTemperatureFallback - 1, SWITCH_STATUS.OFF_FALLBACK, false)
assertMeanLast(null, null, CONFIG.maxWaterTemperatureFallback - 1, SWITCH_STATUS.ON_FALLBACK, true)

// check forecast
console.log("test forecast")
testCount = 0
assertMeanLast(null, null, minWaterTemperature + 1, SWITCH_STATUS.ON_FALLBACK, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(excessEnergyOverThreshold, excessEnergyOverThreshold, minWaterTemperature + 1, SWITCH_STATUS.ON_ENERGY, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(excessEnergyUnderThreshold, excessEnergyUnderThreshold, CONFIG.maxWaterTemperatureFallback - CONFIG.maxWaterTemperatureDelta - 1, SWITCH_STATUS.ON_FORECAST, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(excessEnergyUnderThreshold + wattThreshold, excessEnergyUnderThreshold + wattThreshold, minWaterTemperature + 1, SWITCH_STATUS.OFF_FORECAST, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(excessEnergyOverThreshold + CONFIG.availableEnergyOffsetFallback, excessEnergyOverThreshold + CONFIG.availableEnergyOffsetFallback, minWaterTemperature + 1, SWITCH_STATUS.ON_FORECAST, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(excessEnergyOverThreshold + wattThreshold, excessEnergyOverThreshold + wattThreshold, CONFIG.maxWaterTemperatureFallback + 1, SWITCH_STATUS.OFF_LOW_ENERGY, false, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))

assertMeanLast(1, 1, CONFIG.maxWaterTemperatureFallback - 1, SWITCH_STATUS.ON_FORECAST, true, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))
assertMeanLast(1000, 1000, CONFIG.maxWaterTemperatureFallback - 1, SWITCH_STATUS.OFF_FORECAST, true, CONFIG.wattThresholdToSwitchOn - 10, new Date("2023-08-29T11:00:00Z"))

// check modified CONFIG.wattZeroGridUsageOffset value
console.log("test wattZeroGridUsageOffset")
testCount = 0
Expand Down Expand Up @@ -203,9 +190,9 @@ function assertMeanLast(wattGridUsageMean, wattGridUsageLast, currentWaterTemper
testCount++;
console.log("=".repeat(80))
console.log(" => expected Result: " + expectedResult.message)
result = control.determineNewSwitchStatus(DataProvider.getStatusValues(wattGridUsageMean, wattGridUsageLast, currentWaterTemperature, switchOn, forecastValue, forecastTime, boilerStatus, batteryCharge))
result = control.determineNewSwitchStatus(DataProvider.getStatusValues(wattGridUsageMean, wattGridUsageLast, currentWaterTemperature, switchOn, boilerStatus, batteryCharge))
assertlib.equal(result, expectedResult, "For mean/last -> Expected: " + JSON.stringify(expectedResult) + " but got " + JSON.stringify(result) + " in test nr.: " + testCount)
console.log("-".repeat(80))
result2 = control.determineNewSwitchStatus(DataProvider.getStatusValues(wattGridUsageMean, wattGridUsageLast, currentWaterTemperature, switchOn, forecastValue, forecastTime, boilerStatus, batteryCharge, {P_PV: Math.max(wattGridUsageLast, wattGridUsageMean) * -1, P_Load: 0}))
result2 = control.determineNewSwitchStatus(DataProvider.getStatusValues(wattGridUsageMean, wattGridUsageLast, currentWaterTemperature, switchOn, boilerStatus, batteryCharge, {P_PV: Math.max(wattGridUsageLast, wattGridUsageMean) * -1, P_Load: 0}))
assertlib.equal(result2, expectedResult, "For inverterPowerFlow -> Expected: " + JSON.stringify(expectedResult) + " but got " + JSON.stringify(result2) + " in test nr.: " + testCount)
}

0 comments on commit 48bd8d5

Please sign in to comment.