ottopaulsen / node-red-contrib-power-saver

A Node-RED node to saver money by turning off when the power is most expensive
Other
70 stars 17 forks source link

Capacity part of grid tariff - Not getting actions triggered #146

Closed haavardNO closed 1 year ago

haavardNO commented 1 year ago

I have over the last month tried an played with PS and the capacity flow. My problem is that I do not seem to get any actions triggered. At jan 1 the cap was reset so I was expecting PS to try to keep within the 5 kwh limit. However, I cannot see any actions. I have an example that is very similar to the example used in the documentation where I want to bypass strategy nodes. Is it possible that some of you that is more familiar with the logic have a brief look, and share some thoughts?

image

image

ottopaulsen commented 1 year ago

How is yor config for the reduction actions?

haavardNO commented 1 year ago

I have this code for start:

// You MUST edit the actions array with your own actions.

const actions = [
    { 
        consumption: "sensor.stromtrekk_bereder_kw",
        name: "Varmtvannsbereder",
        id: "vvb",
        minAlarmLevel: 8,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "best_save_bereder",
    },
    { 
        consumption: "sensor.stromtrekk_avfukter_garasje_kw",
        name: "Avfukter garasje",
        id: "avfukter",
        minAlarmLevel: 8,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "best_save_avfukter",

    },
    { 
        consumption: "sensor.heat_pump_calibrated_kw",
        name: "Luft/vann VP",
        id: "vbv",
        minAlarmLevel: 3,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "merger_vbv"

    },  
]
// End of actions array

// DO NOT DELETE THE CODE BELOW

// Set default values for all actions
actions.forEach(a => {
    a.actionTaken = false
    a.savedConsumption = 0
})

flow.set("actions", actions)

And this for message:

const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W
const MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION = 5

const actions = flow.get("actions")
const ha = global.get("homeassistant").homeAssistant

let reductionRequired = msg.payload.reductionRequired
let reductionRecommended = msg.payload.reductionRecommended

node.status({})

if(reductionRecommended <= 0 ) {
  return null
}

if (3600 - msg.payload.timeLeftSec < MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION * 60) {
  node.status({ fill: "yellow", shape: "ring", text: "No action during first " + MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION + " minutes"});
  return
}

function takeAction(action, consumption ) {
  const info = {
    time: new Date().toISOString(),
    name: "Reduction action",
    data: msg.payload,
    action
  }

  // output1 is for actions
  const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null
  // output 2 is for overriding PS strategies
  const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: "off" }, name: action.nameOfStrategyToOverride} } : null
  // output 3 is for logging
  const output3 = { payload: info }

  node.send([output1, output2, output3])
  reductionRequired = Math.max(0, reductionRequired - consumption)
  reductionRecommended = Math.max(0, reductionRecommended - consumption)
  action.actionTaken = true
  action.actionTime = Date.now()
  action.savedConsumption = consumption
  flow.set("actions", actions)
}

function getConsumption(consumption) {
  if(typeof consumption === "string") {
    const sensor = ha.states[consumption]
    return sensor.state / 1000
  } else if (typeof consumption === "number") {
    return consumption
  } else if(typeof consumption === "function") {
    return consumption()
  } else {
    node.warn("Config error: consumption has illegal type: " + typeof consumption)
    return 0
  }
}

actions
.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)
.forEach(a => {
  const consumption = getConsumption(a.consumption)
  if (consumption < MIN_CONSUMPTION_TO_CARE) {
    return
  }
  if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {
    takeAction(a, consumption)
  }
})
ottopaulsen commented 1 year ago

I can see that your sensor seems to be kW, but in the code it looks like it is divided on 1000: return sensor.state / 1000. That looks like a bug. Try to not divide by 1000.

What happens is that it thinks the consumption is too small to bother (less than 50W).

haavardNO commented 1 year ago

Thanks for your input. I am very eager to get this to work in my setup, so I have played a lot back and forth to try to get it to run. I actually created template sensors in homeassistant with kw instead of w not long ago when I spotted in the docs that the input should be in kW. I have now changed back to sensors that report in W since it is divided by 1000 in the code.

Should it now in theory disable the strategy nodes if the alarm level is reached, and it is after the time limit and the consumption is higher than 50w? And is it a way to test this, other than turning on all applications physically at the same time?

const actions = [
    { 
        consumption: "sensor.bryter_bereder_electric_consumption_w",
        name: "Varmtvannsbereder",
        id: "vvb",
        minAlarmLevel: 8,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "best_save_bereder",
    },
    { 
        consumption: "sensor.avfukter_garasje_power_9",
        name: "Avfukter garasje",
        id: "avfukter",
        minAlarmLevel: 8,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "best_save_avfukter",

    },
    { 
        consumption: "sensor.heat_pump_calibrated_w",
        name: "Luft/vann VP",
        id: "vbv",
        minAlarmLevel: 3,
        reduceWhenRecommended: true,
        minTimeOffSec: 300,
        nameOfStrategyToOverride: "merger_vbv"

    },  
]
// End of actions array

// DO NOT DELETE THE CODE BELOW

// Set default values for all actions
actions.forEach(a => {
    a.actionTaken = false
    a.savedConsumption = 0
})

flow.set("actions", actions)
const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W
const MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION = 5

const actions = flow.get("actions")
const ha = global.get("homeassistant").homeAssistant

let reductionRequired = msg.payload.reductionRequired
let reductionRecommended = msg.payload.reductionRecommended

node.status({})

if(reductionRecommended <= 0 ) {
  return null
}

if (3600 - msg.payload.timeLeftSec < MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION * 60) {
  node.status({ fill: "yellow", shape: "ring", text: "No action during first " + MIN_MINUTES_INTO_HOUR_TO_TAKE_ACTION + " minutes"});
  return
}

function takeAction(action, consumption ) {
  const info = {
    time: new Date().toISOString(),
    name: "Reduction action",
    data: msg.payload,
    action
  }

  // output1 is for actions
  const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null
  // output 2 is for overriding PS strategies
  const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: "off" }, name: action.nameOfStrategyToOverride} } : null
  // output 3 is for logging
  const output3 = { payload: info }

  node.send([output1, output2, output3])
  reductionRequired = Math.max(0, reductionRequired - consumption)
  reductionRecommended = Math.max(0, reductionRecommended - consumption)
  action.actionTaken = true
  action.actionTime = Date.now()
  action.savedConsumption = consumption
  flow.set("actions", actions)
}

function getConsumption(consumption) {
  if(typeof consumption === "string") {
    const sensor = ha.states[consumption]
    return sensor.state / 1000
  } else if (typeof consumption === "number") {
    return consumption
  } else if(typeof consumption === "function") {
    return consumption()
  } else {
    node.warn("Config error: consumption has illegal type: " + typeof consumption)
    return 0
  }
}

actions
.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)
.forEach(a => {
  const consumption = getConsumption(a.consumption)
  if (consumption < MIN_CONSUMPTION_TO_CARE) {
    return
  }
  if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {
    takeAction(a, consumption)
  }
})

I have done some small cleaning in my flow, so it now looks like this: image

haavardNO commented 1 year ago

I did some more testing over the weekend, and was able to get the flow to work with the settings above. I testet by actually applying all the loads at the beginning of an hour, and was able to see that it actually reduced and re applied the loads. Thanks for you help :)