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

Issues with capacy part of grid converted to not use tibber api #141

Closed torsteinelv closed 1 year ago

torsteinelv commented 1 year ago

Trying to get this one working. Issue is that it does not understand currentMonthlyMaxAverage in Find highest pr day when setting collecting to more than 12 hours.

Mabey someone else have this working?

[
   {
      "id":"bf2a2a83186e3eff",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Collect estimate for hour",
      "func":"\n// Number of minutes used to calculate assumed consumption:\nconst ESTIMATION_TIME_MINUTES = 1\n\nconst buffer = context.get(\"buffer\") || []\n\n// Add new record to buffer\nconst time = new Date(msg.payload.timestamp)\nconst timeMs = time.getTime()\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nbuffer.push({ timeMs, accumulatedConsumption })\n\nconst currentHour = new Date(msg.payload.timestamp)\ncurrentHour.setMinutes(0)\ncurrentHour.setSeconds(0)\n\n// Remove too old records from buffer\nconst maxAgeMs = ESTIMATION_TIME_MINUTES * 60 * 1000\nlet oldest = buffer[0]\nwhile ((timeMs - oldest.timeMs) > maxAgeMs) {\n    buffer.splice(0, 1)\n    oldest = buffer[0]\n}\ncontext.set(\"buffer\", buffer)\n\n// Calculate buffer\nconst periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs\nlet consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption\nif (consumptionInPeriod < 0) {\n    consumptionInPeriod = 0\n}\nif (periodMs === 0) {\n    return // First item in buffer\n}\n\n// Estimate remaining of current hour\nconst timeLeftMs = (60 * 60 * 1000) - (time.getMinutes() * 60000 + time.getSeconds() * 1000 + time.getMilliseconds())\nconst consumptionLeft = consumptionInPeriod / periodMs * timeLeftMs\nconst averageConsumptionNow = consumptionInPeriod / periodMs * 60 * 60 * 1000\n\n// Estimate total hour\nconst hourEstimate = accumulatedConsumptionLastHour + consumptionLeft + 0 // Change for testing\n\nmsg.payload = {\n    accumulatedConsumption,\n    accumulatedConsumptionLastHour,\n    periodMs,\n    consumptionInPeriod,\n    averageConsumptionNow,\n    timeLeftMs,\n    consumptionLeft,\n    hourEstimate,\n    currentHour\n}\n\nreturn msg;",
      "outputs":1,
      "noerr":0,
      "initialize":"// Code added here will be run once\n// whenever the node is started.\ncontext.set(\"buffer\", [])",
      "finalize":"",
      "libs":[

      ],
      "x":210,
      "y":780,
      "wires":[
         [
            "ba65e3741dcfd9b2"
         ]
      ]
   },
   {
      "id":"c24c8da51344b9ba",
      "type":"api-current-state",
      "z":"e442ba1155240757",
      "name":"",
      "server":"a7c55e81aa13f774",
      "version":3,
      "outputs":1,
      "halt_if":"",
      "halt_if_type":"num",
      "halt_if_compare":"is",
      "entity_id":"sensor.energiforbruk_time",
      "state_type":"str",
      "blockInputOverrides":false,
      "outputProperties":[
         {
            "property":"payload.accumulatedConsumptionLastHour",
            "propertyType":"msg",
            "value":"",
            "valueType":"entityState"
         }
      ],
      "for":"0",
      "forType":"num",
      "forUnits":"minutes",
      "override_topic":false,
      "state_location":"payload",
      "override_payload":"msg",
      "entity_location":"data",
      "override_data":"msg",
      "x":260,
      "y":580,
      "wires":[
         [
            "7274c092da6d209e"
         ]
      ]
   },
   {
      "id":"6fd8ad5120a34504",
      "type":"api-current-state",
      "z":"e442ba1155240757",
      "name":"Grid Power",
      "server":"a7c55e81aa13f774",
      "version":3,
      "outputs":1,
      "halt_if":"",
      "halt_if_type":"num",
      "halt_if_compare":"is",
      "entity_id":"sensor.kamstrup_active_power_import",
      "state_type":"str",
      "blockInputOverrides":false,
      "outputProperties":[
         {
            "property":"payload.power",
            "propertyType":"msg",
            "value":"",
            "valueType":"entityState"
         }
      ],
      "for":"0",
      "forType":"num",
      "forUnits":"minutes",
      "override_topic":false,
      "state_location":"payload",
      "override_payload":"msg",
      "entity_location":"data",
      "override_data":"msg",
      "x":170,
      "y":700,
      "wires":[
         [
            "bf2a2a83186e3eff"
         ]
      ]
   },
   {
      "id":"7274c092da6d209e",
      "type":"api-current-state",
      "z":"e442ba1155240757",
      "name":"",
      "server":"a7c55e81aa13f774",
      "version":3,
      "outputs":1,
      "halt_if":"",
      "halt_if_type":"num",
      "halt_if_compare":"is",
      "entity_id":"sensor.energiforbruk",
      "state_type":"str",
      "blockInputOverrides":false,
      "outputProperties":[
         {
            "property":"payload.accumulatedConsumption",
            "propertyType":"msg",
            "value":"",
            "valueType":"entityState"
         }
      ],
      "for":"0",
      "forType":"num",
      "forUnits":"minutes",
      "override_topic":false,
      "state_location":"payload",
      "override_payload":"msg",
      "entity_location":"data",
      "override_data":"msg",
      "x":240,
      "y":640,
      "wires":[
         [
            "6fd8ad5120a34504"
         ]
      ]
   },
   {
      "id":"ba65e3741dcfd9b2",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Calculate values",
      "func":"const HA_NAME = \"homeAssistant\"; // Your HA name\nconst STEPS = [2, 5, 10, 15, 20]\nconst MAX_COUNTING = 3 // Number of days to calculate month\nconst BUFFER = 0.5 // Closer to limit increases level\nconst SAFE_ZONE = 3 // Further from limit reduces level\nconst ALARM = 8 // Min level that causes status to be alarm\n\n\nconst ha = global.get(\"homeassistant\")[HA_NAME];\nif(!ha.isConnected) {\n    return\n}\n\nfunction isNull(value) {\n    return value === null || value === undefined\n}\n\nfunction calculateLevel(hourEstimate, \n                        currentHourRanking,\n                        highestCountingAverageWithCurrent,\n                        nextStep) {\n    if(currentHourRanking === 0) {\n        return 0\n    }\n    if(highestCountingAverageWithCurrent > nextStep) {\n        return 9\n    }\n    if(highestCountingAverageWithCurrent > (nextStep - BUFFER)) {\n        return 8\n    }\n    if(hourEstimate > nextStep) {\n        return 7\n    }\n    if(hourEstimate > (nextStep - BUFFER)) {\n        return 6\n    }\n    if(currentHourRanking === 1 && (nextStep - hourEstimate) < SAFE_ZONE) {\n        return 5\n    }\n    if(currentHourRanking === 2 && (nextStep - hourEstimate) < SAFE_ZONE) {\n        return 4\n    }\n    if(currentHourRanking === 3 && (nextStep - hourEstimate) < SAFE_ZONE) {\n        return 3\n    }\n    if(currentHourRanking === 1) {\n        return 2\n    }\n    if(currentHourRanking === 2) {\n        return 1\n    }\n    return 0\n}\n\n\nif (msg.payload.highestPerDay) {\n    context.set(\"highestPerDay\", msg.payload.highestPerDay)\n    context.set(\"highestCounting\", msg.payload.highestCounting)\n    context.set(\"highestToday\", msg.payload.highestToday)\n    context.set(\"currentMonthlyMaxAverage\", msg.payload.currentMonthlyMaxAverage)\n    node.status({fill:\"green\",shape:\"ring\",text:\"Got ranking\"});\n    return\n}\n\nconst highestPerDay = context.get(\"highestPerDay\")\nconst highestCounting = context.get(\"highestCounting\")\nconst highestToday = context.get(\"highestToday\")\nconst currentMonthlyMaxAverage = context.get(\"currentMonthlyMaxAverage\")\nconst hourEstimate = msg.payload.hourEstimate\nconst timeLeftMs = msg.payload.timeLeftMs\nconst timeLeftSec = timeLeftMs / 1000\nconst periodMs = msg.payload.periodMs\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nconst consumptionLeft = msg.payload.consumptionLeft\nconst averageConsumptionNow = msg.payload.averageConsumptionNow\nconst currentHour = msg.payload.currentHour\n\nif (timeLeftSec === 0) {\n    return null\n}\n\nif (isNull(highestPerDay)) {\n    node.status({fill:\"red\",shape:\"dot\",text:\"No highest per day\"});\n    return\n}\nif (isNull(highestToday)) {\n    node.status({fill:\"red\",shape:\"dot\",text:\"No highest today\"});\n    return\n}\nif (isNull(hourEstimate)) {\n    node.status({fill:\"red\",shape:\"dot\",text:\"No estimate\"});\n    return\n}\n\nconst currentStep = STEPS.reduceRight((prev, val) => val > currentMonthlyMaxAverage ? val : prev, STEPS[STEPS.length - 1])\n\n// Set currentHourRanking\nlet currentHourRanking = MAX_COUNTING + 1\nfor(let i = highestCounting.length - 1; i >= 0; i--) {\n    if(hourEstimate > highestCounting[i].consumption) {\n        currentHourRanking = i + 1\n    }\n}\nif(hourEstimate < highestToday.consumption) {\n    currentHourRanking = 0\n}\n\nconst current = {from: currentHour, consumption: hourEstimate}\nconst highestCountingWithCurrent = [...highestCounting, current].sort((a, b) => b.consumption - a.consumption).slice(0, highestCounting.length)\nconst currentMonthlyEstimate = highestCountingWithCurrent.length === 0 ? 0 : highestCountingWithCurrent.reduce((prev, val) => prev + val.consumption, 0) / highestCountingWithCurrent.length\n\n// Set alarm level\nconst alarmLevel = calculateLevel(\n    hourEstimate,\n    currentHourRanking,\n    currentMonthlyEstimate,\n    currentStep)\n\n// Evaluate status\nconst status = alarmLevel >= ALARM ? \"Alarm\" : alarmLevel > 0 ? \"Warning\" : \"Ok\"\n\n// Calculate reduction\nconst reductionRequired = alarmLevel < ALARM ? 0 :\n    Math.max((currentMonthlyEstimate - currentStep) * highestCounting.length, 0)\n    * 3600 / timeLeftSec;\nconst reductionRecommended = alarmLevel < 3 ? 0 :\n    Math.max(hourEstimate + SAFE_ZONE - currentStep, 0)\n    * 3600 / timeLeftSec;\n\n// Calculate increase possible\nconst increasePossible = alarmLevel >= 3 ? 0 :\n    Math.max(currentStep - hourEstimate - SAFE_ZONE, 0)\n    * 3600 / timeLeftSec;\n\n// Create output\nconst fill = status === \"Ok\" ? \"green\" : status === \"Alarm\" ? \"red\" : \"yellow\";\nnode.status({fill,shape:\"dot\",text:\"Working\"});\n\nconst RESOLUTION = 1000\n\nconst payload = {\n    status, // Ok, Warning, Alarm\n    statusOk: status === \"Ok\",\n    statusWarning: status === \"Warning\",\n    statusAlarm: status === \"Alarm\",\n    alarmLevel,\n    highestPerDay,\n    highestCounting,\n    highestCountingWithCurrent,\n    highestToday,\n    highestTodayConsumption: highestToday.consumption,\n    highestTodayFrom: highestToday.from,\n    currentMonthlyEstimate: Math.round(currentMonthlyEstimate * RESOLUTION) / RESOLUTION,\n    accumulatedConsumptionLastHour: Math.round(accumulatedConsumptionLastHour * RESOLUTION) / RESOLUTION,\n    consumptionLeft: Math.round(consumptionLeft * RESOLUTION) / RESOLUTION,\n    hourEstimate: Math.round(hourEstimate * RESOLUTION) / RESOLUTION,\n    averageConsumptionNow: Math.round(averageConsumptionNow * RESOLUTION) / RESOLUTION,\n    reductionRequired: Math.round(reductionRequired * RESOLUTION) / RESOLUTION,\n    reductionRecommended: Math.round(reductionRecommended * RESOLUTION) / RESOLUTION,\n    increasePossible: Math.round(increasePossible * RESOLUTION) / RESOLUTION,\n    currentStep,\n    currentHourRanking,\n    timeLeftSec,\n    periodMs,\n    accumulatedConsumption\n}\n\nmsg.payload = payload\n\nreturn msg;",
      "outputs":1,
      "noerr":0,
      "initialize":"",
      "finalize":"",
      "libs":[

      ],
      "x":500,
      "y":700,
      "wires":[
         [
            "5cda10306f9d344c",
            "97210699c79c9f9f",
            "c1ce4bb3fc69a534"
         ]
      ]
   },
   {
      "id":"6238837c425a4bf8",
      "type":"ha-api",
      "z":"e442ba1155240757",
      "name":"Set entity",
      "server":"a7c55e81aa13f774",
      "version":1,
      "debugenabled":false,
      "protocol":"http",
      "method":"post",
      "path":"",
      "data":"",
      "dataType":"json",
      "responseType":"json",
      "outputProperties":[
         {
            "property":"payload",
            "propertyType":"msg",
            "value":"",
            "valueType":"results"
         }
      ],
      "x":1060,
      "y":680,
      "wires":[
         [

         ]
      ]
   },
   {
      "id":"5cda10306f9d344c",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Update sensors",
      "func":"const sensors = [\n    { id: \"sensor.ps_cap_status\", value: \"status\", uom: null },\n    { id: \"binary_sensor.ps_cap_ok\", value: \"statusOk\", uom: null },\n    { id: \"binary_sensor.ps_cap_warning\", value: \"statusWarning\", uom: null },\n    { id: \"binary_sensor.ps_cap_alarm\", value: \"statusAlarm\", uom: null },\n    { id: \"sensor.ps_cap_alarm_level\", value: \"alarmLevel\", uom: null },\n    { id: \"sensor.ps_cap_current_step\", value: \"currentStep\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_hour_estimate\", value: \"hourEstimate\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_current_hour_ranking\", value: \"currentHourRanking\", uom: null },\n    { id: \"sensor.ps_cap_monthly_estimate\", value: \"currentMonthlyEstimate\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_highest_today\", value: \"highestTodayConsumption\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_highest_today_time\", value: \"highestTodayFrom\", uom: null },\n    { id: \"sensor.ps_cap_reduction_required\", value: \"reductionRequired\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_reduction_recommended\", value: \"reductionRecommended\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_increase_possible\", value: \"increasePossible\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_estimate_rest_of_hour\", value: \"consumptionLeft\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_consumption_accumulated_hour\", value: \"accumulatedConsumptionLastHour\", uom: \"kW\" },\n    { id: \"sensor.ps_cap_time_left\", value: \"timeLeftSec\", uom: \"s\" },\n    { id: \"sensor.ps_cap_consumption_now\", value: \"averageConsumptionNow\", uom: \"kW\" },\n]\n\nsensors.forEach((sensor) => {\n    const payload = {\n        protocol: \"http\",\n        method: \"post\",\n        path: \"/api/states/\" + sensor.id,\n        data: {\n            state: msg.payload[sensor.value],\n            attributes: { unit_of_measurement: sensor.uom }\n        }\n    }\n    node.send({payload})\n})\n",
      "outputs":1,
      "noerr":0,
      "initialize":"",
      "finalize":"",
      "libs":[

      ],
      "x":760,
      "y":700,
      "wires":[
         [
            "6238837c425a4bf8"
         ]
      ]
   },
   {
      "id":"97210699c79c9f9f",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Reduction Actions",
      "func":"const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W\n\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nlet reductionRequired = msg.payload.reductionRequired\nlet reductionRecommended = msg.payload.reductionRecommended\n\nif(reductionRecommended <= 0) {\n  return null\n}\n\nfunction takeAction(action, consumption ) {\n  const info = {\n    time: new Date().toISOString(),\n    name: \"Reduction action\",\n    data: msg.payload,\n    action\n  }\n\n  // output1 is for actions\n  const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null\n  // output 2 is for overriding PS strategies\n  const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"off\" }, name: action.nameOfStrategyToOverride} } : null\n  // output 3 is for logging\n  const output3 = { payload: info }\n\n  node.send([output1, output2, output3])\n  reductionRequired = Math.max(0, reductionRequired - consumption)\n  reductionRecommended = Math.max(0, reductionRecommended - consumption)\n  action.actionTaken = true\n  action.actionTime = Date.now()\n  action.savedConsumption = consumption\n  flow.set(\"actions\", actions)\n}\n\nfunction getConsumption(consumption) {\n  if(typeof consumption === \"string\") {\n    const sensor = ha.states[consumption]\n    return sensor.state / 1000\n  } else if (typeof consumption === \"number\") {\n    return consumption\n  } else if(typeof consumption === \"function\") {\n    return consumption()\n  } else {\n    node.warn(\"Config error: consumption has illegal type: \" + typeof consumption)\n    return 0\n  }\n}\n\nactions\n.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)\n.forEach(a => {\n  const consumption = getConsumption(a.consumption)\n  if (consumption < MIN_CONSUMPTION_TO_CARE) {\n    return\n  }\n  if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {\n    takeAction(a, consumption)\n  }\n})\n    \n",
      "outputs":3,
      "noerr":0,
      "initialize":"// You MUST edit the actions array with your own actions.\n\nconst actions = [\n    {\n        consumption: \"sensor.varmeovn1\",\n        name: \"Varmeovn1\",\n        id: \"Varmeovn1\",\n        minAlarmLevel: 3,\n        reduceWhenRecommended: true,\n        minTimeOffSec: 300,\n        payloadToTakeAction: {\n            domain: \"climate\",\n            service: \"turn_off\",\n            target: {\n                entity_id: [\"climate.varmeovn1\"]\n            }\n        },\n        payloadToResetAction: {\n            domain: \"climate\",\n            service: \"turn_on\",\n            target: {\n                entity_id: [\"climate.varmeovn1\"]\n            }\n        }\n    },\n    {\n        consumption: \"sensor.heatit_z_trm3_olander_soverom_electric_consumption_w\",\n        name: \"Varmekabler gulv olander\",\n        id: \"gulvolander\",\n        minAlarmLevel: 3,\n        reduceWhenRecommended: true,\n        minTimeOffSec: 300,\n        payloadToTakeAction: {\n            domain: \"climate\",\n            service: \"turn_off\",\n            target: {\n                entity_id: [\"climate.heatit_z_trm3_olander_soverom\"]\n            }\n        },\n        payloadToResetAction: {\n            domain: \"climate\",\n            service: \"turn_on\",\n            target: {\n                entity_id: [\"climate.heatit_z_trm3_olander_soverom\"]\n            }\n        }\n    },\n    { \n        consumption: \"sensor.heatit_z_trm3_bad_hovedetasje_electric_consumption_w\",\n        name: \"Varmekabler gulv bad\",\n        id: \"gulvbad\",\n        minAlarmLevel: 3,\n        reduceWhenRecommended: true,\n        minTimeOffSec: 300,\n        payloadToTakeAction: {\n            domain: \"climate\",\n            service: \"turn_off\",\n            target: {\n                entity_id: [\"climate.heatit_z_trm3_bad_hovedetasje\"]\n            }\n        },\n        payloadToResetAction: {\n            domain: \"climate\",\n            service: \"turn_on\",\n            target: {\n                entity_id: [\"climate.heatit_z_trm3_bad_hovedetasje\"]\n            }\n        }\n    },\n    {\n        consumption: \"sensor.metered_wall_plug_switch_vvb_electric_consumption_w\",\n        name: \"Varmtvannsbereder\",\n        id: \"vvb\",\n        minAlarmLevel: 4,\n        reduceWhenRecommended: true,\n        minTimeOffSec: 300,\n        payloadToTakeAction: {\n            domain: \"climate\",\n            service: \"turn_off\",\n            target: {\n                entity_id: [\"switch.metered_wall_plug_switch_vvb\"]\n            }\n        },\n        payloadToResetAction: {\n            domain: \"climate\",\n            service: \"turn_on\",\n            target: {\n                entity_id: [\"switch.metered_wall_plug_switch_vvb\"]\n            }\n        }\n    },\n    {\n        consumption: \"sensor.volkedalsveien41stue_current_power\",\n        name: \"Varmepumpe Stue\",\n        id: \"varmepumpestue\",\n        minAlarmLevel: 4,\n        reduceWhenRecommended: true,\n        minTimeOffSec: 300,\n        payloadToTakeAction: {\n            domain: \"climate\",\n            service: \"turn_off\",\n            target: {\n                entity_id: [\"climate.volkedalsveien41stue\"]\n            }\n        },\n        payloadToResetAction: {\n            domain: \"climate\",\n            service: \"turn_on\",\n            target: {\n                entity_id: [\"climate.volkedalsveien41stue\"]\n            }\n        }\n    }\n]\n// End of actions array\n\n// DO NOT DELETE THE CODE BELOW\n\n// Set default values for all actions\nactions.forEach(a => {\n    a.actionTaken = false\n    a.savedConsumption = 0\n})\n\nflow.set(\"actions\", actions)\n",
      "finalize":"const actions = flow.get(\"actions\")\n\nactions\n    .filter(a => a.actionTaken)\n    .forEach(a => \n        node.send({ payload: a.payloadToResetAction })\n    )",
      "libs":[

      ],
      "x":510,
      "y":800,
      "wires":[
         [
            "caaed0baefd22047"
         ],
         [

         ],
         [
            "218931629f65e38f"
         ]
      ]
   },
   {
      "id":"c1ce4bb3fc69a534",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Reset Actions",
      "func":"\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nconst BUFFER_TO_RESET = 1 // Must have 1kW extra to perform reset\n\nlet increasePossible = msg.payload.increasePossible\n\nif (increasePossible <= 0) {\n  return null\n}\n\nfunction resetAction(action) {\n  const info = {\n    time: new Date().toISOString(),\n    name: \"Reset action\",\n    data: msg.payload,\n    action\n  }\n  const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null\n  const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"auto\" }, name: action.nameOfStrategyToOverride } } : null\n  const output3 = { payload: info }\n\n  node.send([output1, output2, output3])\n  increasePossible -= action.savedConsumption\n  action.actionTaken = false\n  action.savedConsumption = 0\n  flow.set(\"actions\", actions)\n}\n\nactions\n  .filter(a => a.actionTaken\n    && (a.savedConsumption + BUFFER_TO_RESET) <= increasePossible\n    && (Date.now() - a.actionTime > a.minTimeOffSec * 1000)\n  ).forEach(a => resetAction(a))\n",
      "outputs":3,
      "noerr":0,
      "initialize":"",
      "finalize":"",
      "libs":[

      ],
      "x":500,
      "y":860,
      "wires":[
         [
            "caaed0baefd22047"
         ],
         [

         ],
         [
            "218931629f65e38f"
         ]
      ]
   },
   {
      "id":"caaed0baefd22047",
      "type":"api-call-service",
      "z":"e442ba1155240757",
      "name":"Perform action",
      "server":"a7c55e81aa13f774",
      "version":5,
      "debugenabled":false,
      "domain":"",
      "service":"",
      "areaId":[

      ],
      "deviceId":[

      ],
      "entityId":[

      ],
      "data":"",
      "dataType":"jsonata",
      "mergeContext":"",
      "mustacheAltTags":false,
      "outputProperties":[
         {
            "property":"payload",
            "propertyType":"msg",
            "value":"payload",
            "valueType":"msg"
         }
      ],
      "queue":"none",
      "x":820,
      "y":890,
      "wires":[
         [

         ]
      ]
   },
   {
      "id":"218931629f65e38f",
      "type":"file",
      "z":"e442ba1155240757",
      "name":"Save actions to file",
      "filename":"/share/capacity-actions.txt",
      "filenameType":"str",
      "appendNewline":true,
      "createDir":false,
      "overwriteFile":"false",
      "encoding":"none",
      "x":830,
      "y":950,
      "wires":[
         [

         ]
      ]
   },
   {
      "id":"4236d1c26a146d47",
      "type":"catch",
      "z":"e442ba1155240757",
      "name":"Catch action errors",
      "scope":[
         "97210699c79c9f9f",
         "c1ce4bb3fc69a534"
      ],
      "uncaught":false,
      "x":510,
      "y":930,
      "wires":[
         [
            "218931629f65e38f"
         ]
      ]
   },
   {
      "id":"186f4bdccef3a36b",
      "type":"function",
      "z":"e442ba1155240757",
      "name":"Find highest per day",
      "func":"const MAX_COUNTING = 3\nconst hours = msg.payload\nconst days = new Map()\nhours.forEach (h => {\n    const date = (new Date(h.last_updated)).getDate()\n    if (!days.has(date) || h.state > days.get(date).consumption) {\n        days.set(date, { from: h.last_updated, consumption: h.state})\n    }\n})\nconst highestToday = days.get((new Date()).getDate()) ?? {\n    consumption: 0,\n    from: null\n}\nconst highestPerDay = [...days.values()].sort((a, b) => b.consumption - a.consumption)\nconst highestCounting = highestPerDay.slice(0, MAX_COUNTING)\nconst currentMonthlyMaxAverage = highestCounting.length === 0\n    ? 0\n    : highestCounting.reduce((prev, val) =>\n        prev + val.consumption, 0) / highestCounting.length\nmsg.payload = {\n    highestPerDay,\n    highestCounting,\n    highestToday,\n    currentMonthlyMaxAverage\n}\nreturn msg;",
      "outputs":1,
      "noerr":0,
      "initialize":"",
      "finalize":"",
      "libs":[

      ],
      "x":520,
      "y":520,
      "wires":[
         [
            "ba65e3741dcfd9b2"
         ]
      ]
   },
   {
      "id":"b9797512ef4afecc",
      "type":"api-get-history",
      "z":"e442ba1155240757",
      "name":"history home assistant ",
      "server":"a7c55e81aa13f774",
      "version":0,
      "startdate":"",
      "enddate":"",
      "entityid":"sensor.energiforbruk_time",
      "entityidtype":"is",
      "useRelativeTime":true,
      "relativeTime":"1month",
      "flatten":true,
      "output_type":"array",
      "output_location_type":"msg",
      "output_location":"payload",
      "x":420,
      "y":420,
      "wires":[
         [
            "186f4bdccef3a36b"
         ]
      ]
   },
   {
      "id":"31e13220c3ad46fc",
      "type":"inject",
      "z":"e442ba1155240757",
      "name":"1 time",
      "props":[
         {
            "p":"topic",
            "vt":"str"
         }
      ],
      "repeat":"3600",
      "crontab":"",
      "once":true,
      "onceDelay":"2",
      "topic":"",
      "x":160,
      "y":420,
      "wires":[
         [
            "b9797512ef4afecc"
         ]
      ]
   },
   {
      "id":"6d61563197130392",
      "type":"inject",
      "z":"e442ba1155240757",
      "name":"1 minutt",
      "props":[
         {
            "p":"payload.timestamp",
            "v":"",
            "vt":"date"
         }
      ],
      "repeat":"10",
      "crontab":"",
      "once":true,
      "onceDelay":"10",
      "topic":"",
      "x":160,
      "y":500,
      "wires":[
         [
            "c24c8da51344b9ba"
         ]
      ]
   },
   {
      "id":"a7c55e81aa13f774",
      "type":"server",
      "name":"Home Assistant",
      "version":5,
      "addon":true,
      "rejectUnauthorizedCerts":true,
      "ha_boolean":"y|yes|true|on|home|open",
      "connectionDelay":false,
      "cacheJson":true,
      "heartbeat":false,
      "heartbeatInterval":"30",
      "areaSelector":"friendlyName",
      "deviceSelector":"friendlyName",
      "entitySelector":"friendlyName",
      "statusSeparator":"at: ",
      "statusYear":"hidden",
      "statusMonth":"short",
      "statusDay":"numeric",
      "statusHourCycle":"h23",
      "statusTimeFormat":"h:m",
      "enableGlobalContextStore":true
   }
]
torsteinelv commented 1 year ago

Beleave its fixed: [{ "id": "bf2a2a83186e3eff", "type": "function", "z": "e442ba1155240757", "name": "Collect estimate for hour", "func": "\n// Number of minutes used to calculate assumed consumption:\nconst ESTIMATION_TIME_MINUTES = 1\n\nconst buffer = context.get(\"buffer\") || []\n\n// Add new record to buffer\nconst time = new Date(msg.payload.timestamp)\nconst timeMs = time.getTime()\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nbuffer.push({ timeMs, accumulatedConsumption })\n\nconst currentHour = new Date(msg.payload.timestamp)\ncurrentHour.setMinutes(0)\ncurrentHour.setSeconds(0)\n\n// Remove too old records from buffer\nconst maxAgeMs = ESTIMATION_TIME_MINUTES * 60 * 1000\nlet oldest = buffer[0]\nwhile ((timeMs - oldest.timeMs) > maxAgeMs) {\n buffer.splice(0, 1)\n oldest = buffer[0]\n}\ncontext.set(\"buffer\", buffer)\n\n// Calculate buffer\nconst periodMs = buffer[buffer.length - 1].timeMs - buffer[0].timeMs\nlet consumptionInPeriod = buffer[buffer.length - 1].accumulatedConsumption - buffer[0].accumulatedConsumption\nif (consumptionInPeriod < 0) {\n consumptionInPeriod = 0\n}\nif (periodMs === 0) {\n return // First item in buffer\n}\n\n// Estimate remaining of current hour\nconst timeLeftMs = (60 * 60 * 1000) - (time.getMinutes() * 60000 + time.getSeconds() * 1000 + time.getMilliseconds())\nconst consumptionLeft = consumptionInPeriod / periodMs * timeLeftMs\nconst averageConsumptionNow = consumptionInPeriod / periodMs * 60 * 60 * 1000\n\n// Estimate total hour\nconst hourEstimate = accumulatedConsumptionLastHour + consumptionLeft + 0 // Change for testing\n\nmsg.payload = {\n accumulatedConsumption,\n accumulatedConsumptionLastHour,\n periodMs,\n consumptionInPeriod,\n averageConsumptionNow,\n timeLeftMs,\n consumptionLeft,\n hourEstimate,\n currentHour\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "// Code added here will be run once\n// whenever the node is started.\ncontext.set(\"buffer\", [])", "finalize": "", "libs": [], "x": 210, "y": 780, "wires": [ ["ba65e3741dcfd9b2"] ] }, { "id": "c24c8da51344b9ba", "type": "api-current-state", "z": "e442ba1155240757", "name": "", "server": "a7c55e81aa13f774", "version": 3, "outputs": 1, "halt_if": "", "halt_if_type": "num", "halt_if_compare": "is", "entity_id": "sensor.energiforbruk_time", "state_type": "str", "blockInputOverrides": false, "outputProperties": [{ "property": "payload.accumulatedConsumptionLastHour", "propertyType": "msg", "value": "", "valueType": "entityState" }], "for": "0", "forType": "num", "forUnits": "minutes", "override_topic": false, "state_location": "payload", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "x": 260, "y": 580, "wires": [ ["7274c092da6d209e"] ] }, { "id": "6fd8ad5120a34504", "type": "api-current-state", "z": "e442ba1155240757", "name": "Grid Power", "server": "a7c55e81aa13f774", "version": 3, "outputs": 1, "halt_if": "", "halt_if_type": "num", "halt_if_compare": "is", "entity_id": "sensor.kamstrup_active_power_import", "state_type": "str", "blockInputOverrides": false, "outputProperties": [{ "property": "payload.power", "propertyType": "msg", "value": "", "valueType": "entityState" }], "for": "0", "forType": "num", "forUnits": "minutes", "override_topic": false, "state_location": "payload", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "x": 170, "y": 700, "wires": [ ["bf2a2a83186e3eff"] ] }, { "id": "7274c092da6d209e", "type": "api-current-state", "z": "e442ba1155240757", "name": "", "server": "a7c55e81aa13f774", "version": 3, "outputs": 1, "halt_if": "", "halt_if_type": "num", "halt_if_compare": "is", "entity_id": "sensor.energiforbruk", "state_type": "str", "blockInputOverrides": false, "outputProperties": [{ "property": "payload.accumulatedConsumption", "propertyType": "msg", "value": "", "valueType": "entityState" }], "for": "0", "forType": "num", "forUnits": "minutes", "override_topic": false, "state_location": "payload", "override_payload": "msg", "entity_location": "data", "override_data": "msg", "x": 240, "y": 640, "wires": [ ["6fd8ad5120a34504"] ] }, { "id": "ba65e3741dcfd9b2", "type": "function", "z": "e442ba1155240757", "name": "Calculate values", "func": "const HA_NAME = \"homeAssistant\"; // Your HA name\nconst STEPS = [2, 5, 10, 15, 20]\nconst MAX_COUNTING = 3 // Number of days to calculate month\nconst BUFFER = 0.5 // Closer to limit increases level\nconst SAFE_ZONE = 3 // Further from limit reduces level\nconst ALARM = 8 // Min level that causes status to be alarm\n\n\nconst ha = global.get(\"homeassistant\")[HA_NAME];\nif(!ha.isConnected) {\n return\n}\n\nfunction isNull(value) {\n return value === null || value === undefined\n}\n\nfunction calculateLevel(hourEstimate, \n currentHourRanking,\n highestCountingAverageWithCurrent,\n nextStep) {\n if(currentHourRanking === 0) {\n return 0\n }\n if(highestCountingAverageWithCurrent > nextStep) {\n return 9\n }\n if(highestCountingAverageWithCurrent > (nextStep - BUFFER)) {\n return 8\n }\n if(hourEstimate > nextStep) {\n return 7\n }\n if(hourEstimate > (nextStep - BUFFER)) {\n return 6\n }\n if(currentHourRanking === 1 && (nextStep - hourEstimate) < SAFE_ZONE) {\n return 5\n }\n if(currentHourRanking === 2 && (nextStep - hourEstimate) < SAFE_ZONE) {\n return 4\n }\n if(currentHourRanking === 3 && (nextStep - hourEstimate) < SAFE_ZONE) {\n return 3\n }\n if(currentHourRanking === 1) {\n return 2\n }\n if(currentHourRanking === 2) {\n return 1\n }\n return 0\n}\n\n\nif (msg.payload.highestPerDay) {\n context.set(\"highestPerDay\", msg.payload.highestPerDay)\n context.set(\"highestCounting\", msg.payload.highestCounting)\n context.set(\"highestToday\", msg.payload.highestToday)\n context.set(\"currentMonthlyMaxAverage\", msg.payload.currentMonthlyMaxAverage)\n node.status({fill:\"green\",shape:\"ring\",text:\"Got ranking\"});\n return\n}\n\nconst highestPerDay = context.get(\"highestPerDay\")\nconst highestCounting = context.get(\"highestCounting\")\nconst highestToday = context.get(\"highestToday\")\nconst currentMonthlyMaxAverage = context.get(\"currentMonthlyMaxAverage\")\nconst hourEstimate = msg.payload.hourEstimate\nconst timeLeftMs = msg.payload.timeLeftMs\nconst timeLeftSec = timeLeftMs / 1000\nconst periodMs = msg.payload.periodMs\nconst accumulatedConsumption = msg.payload.accumulatedConsumption\nconst accumulatedConsumptionLastHour = msg.payload.accumulatedConsumptionLastHour\nconst consumptionLeft = msg.payload.consumptionLeft\nconst averageConsumptionNow = msg.payload.averageConsumptionNow\nconst currentHour = msg.payload.currentHour\n\nif (timeLeftSec === 0) {\n return null\n}\n\nif (isNull(highestPerDay)) {\n node.status({fill:\"red\",shape:\"dot\",text:\"No highest per day\"});\n return\n}\nif (isNull(highestToday)) {\n node.status({fill:\"red\",shape:\"dot\",text:\"No highest today\"});\n return\n}\nif (isNull(hourEstimate)) {\n node.status({fill:\"red\",shape:\"dot\",text:\"No estimate\"});\n return\n}\n\nconst currentStep = STEPS.reduceRight((prev, val) => val > currentMonthlyMaxAverage ? val : prev, STEPS[STEPS.length - 1])\n\n// Set currentHourRanking\nlet currentHourRanking = MAX_COUNTING + 1\nfor(let i = highestCounting.length - 1; i >= 0; i--) {\n if(hourEstimate > highestCounting[i].consumption) {\n currentHourRanking = i + 1\n }\n}\nif(hourEstimate < highestToday.consumption) {\n currentHourRanking = 0\n}\n\nconst current = {from: currentHour, consumption: hourEstimate}\nconst highestCountingWithCurrent = [...highestCounting, current].sort((a, b) => b.consumption - a.consumption).slice(0, highestCounting.length)\nconst currentMonthlyEstimate = highestCountingWithCurrent.length === 0 ? 0 : highestCountingWithCurrent.reduce((prev, val) => prev + val.consumption, 0) / highestCountingWithCurrent.length\n\n// Set alarm level\nconst alarmLevel = calculateLevel(\n hourEstimate,\n currentHourRanking,\n currentMonthlyEstimate,\n currentStep)\n\n// Evaluate status\nconst status = alarmLevel >= ALARM ? \"Alarm\" : alarmLevel > 0 ? \"Warning\" : \"Ok\"\n\n// Calculate reduction\nconst reductionRequired = alarmLevel < ALARM ? 0 :\n Math.max((currentMonthlyEstimate - currentStep) * highestCounting.length, 0)\n * 3600 / timeLeftSec;\nconst reductionRecommended = alarmLevel < 3 ? 0 :\n Math.max(hourEstimate + SAFE_ZONE - currentStep, 0)\n * 3600 / timeLeftSec;\n\n// Calculate increase possible\nconst increasePossible = alarmLevel >= 3 ? 0 :\n Math.max(currentStep - hourEstimate - SAFE_ZONE, 0)\n * 3600 / timeLeftSec;\n\n// Create output\nconst fill = status === \"Ok\" ? \"green\" : status === \"Alarm\" ? \"red\" : \"yellow\";\nnode.status({fill,shape:\"dot\",text:\"Working\"});\n\nconst RESOLUTION = 1000\n\nconst payload = {\n status, // Ok, Warning, Alarm\n statusOk: status === \"Ok\",\n statusWarning: status === \"Warning\",\n statusAlarm: status === \"Alarm\",\n alarmLevel,\n highestPerDay,\n highestCounting,\n highestCountingWithCurrent,\n highestToday,\n highestTodayConsumption: highestToday.consumption,\n highestTodayFrom: highestToday.from,\n currentMonthlyEstimate: Math.round(currentMonthlyEstimate * RESOLUTION) / RESOLUTION,\n accumulatedConsumptionLastHour: Math.round(accumulatedConsumptionLastHour * RESOLUTION) / RESOLUTION,\n consumptionLeft: Math.round(consumptionLeft * RESOLUTION) / RESOLUTION,\n hourEstimate: Math.round(hourEstimate * RESOLUTION) / RESOLUTION,\n averageConsumptionNow: Math.round(averageConsumptionNow * RESOLUTION) / RESOLUTION,\n reductionRequired: Math.round(reductionRequired * RESOLUTION) / RESOLUTION,\n reductionRecommended: Math.round(reductionRecommended * RESOLUTION) / RESOLUTION,\n increasePossible: Math.round(increasePossible * RESOLUTION) / RESOLUTION,\n currentStep,\n currentHourRanking,\n timeLeftSec,\n periodMs,\n accumulatedConsumption\n}\n\nmsg.payload = payload\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 700, "wires": [ ["5cda10306f9d344c", "97210699c79c9f9f", "c1ce4bb3fc69a534"] ] }, { "id": "6238837c425a4bf8", "type": "ha-api", "z": "e442ba1155240757", "name": "Set entity", "server": "a7c55e81aa13f774", "version": 1, "debugenabled": false, "protocol": "http", "method": "post", "path": "", "data": "", "dataType": "json", "responseType": "json", "outputProperties": [{ "property": "payload", "propertyType": "msg", "value": "", "valueType": "results" }], "x": 1060, "y": 680, "wires": [ [] ] }, { "id": "5cda10306f9d344c", "type": "function", "z": "e442ba1155240757", "name": "Update sensors", "func": "const sensors = [\n { id: \"sensor.ps_cap_status\", value: \"status\", uom: null },\n { id: \"binary_sensor.ps_cap_ok\", value: \"statusOk\", uom: null },\n { id: \"binary_sensor.ps_cap_warning\", value: \"statusWarning\", uom: null },\n { id: \"binary_sensor.ps_cap_alarm\", value: \"statusAlarm\", uom: null },\n { id: \"sensor.ps_cap_alarm_level\", value: \"alarmLevel\", uom: null },\n { id: \"sensor.ps_cap_current_step\", value: \"currentStep\", uom: \"kW\" },\n { id: \"sensor.ps_cap_hour_estimate\", value: \"hourEstimate\", uom: \"kW\" },\n { id: \"sensor.ps_cap_current_hour_ranking\", value: \"currentHourRanking\", uom: null },\n { id: \"sensor.ps_cap_monthly_estimate\", value: \"currentMonthlyEstimate\", uom: \"kW\" },\n { id: \"sensor.ps_cap_highest_today\", value: \"highestTodayConsumption\", uom: \"kW\" },\n { id: \"sensor.ps_cap_highest_today_time\", value: \"highestTodayFrom\", uom: null },\n { id: \"sensor.ps_cap_reduction_required\", value: \"reductionRequired\", uom: \"kW\" },\n { id: \"sensor.ps_cap_reduction_recommended\", value: \"reductionRecommended\", uom: \"kW\" },\n { id: \"sensor.ps_cap_increase_possible\", value: \"increasePossible\", uom: \"kW\" },\n { id: \"sensor.ps_cap_estimate_rest_of_hour\", value: \"consumptionLeft\", uom: \"kW\" },\n { id: \"sensor.ps_cap_consumption_accumulated_hour\", value: \"accumulatedConsumptionLastHour\", uom: \"kW\" },\n { id: \"sensor.ps_cap_time_left\", value: \"timeLeftSec\", uom: \"s\" },\n { id: \"sensor.ps_cap_consumption_now\", value: \"averageConsumptionNow\", uom: \"kW\" },\n]\n\nsensors.forEach((sensor) => {\n const payload = {\n protocol: \"http\",\n method: \"post\",\n path: \"/api/states/\" + sensor.id,\n data: {\n state: msg.payload[sensor.value],\n attributes: { unit_of_measurement: sensor.uom }\n }\n }\n node.send({payload})\n})\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 760, "y": 700, "wires": [ ["6238837c425a4bf8"] ] }, { "id": "97210699c79c9f9f", "type": "function", "z": "e442ba1155240757", "name": "Reduction Actions", "func": "const MIN_CONSUMPTION_TO_CARE = 0.05 // Do not reduce unless at least 50W\n\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nlet reductionRequired = msg.payload.reductionRequired\nlet reductionRecommended = msg.payload.reductionRecommended\n\nif(reductionRecommended <= 0) {\n return null\n}\n\nfunction takeAction(action, consumption ) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reduction action\",\n data: msg.payload,\n action\n }\n\n // output1 is for actions\n const output1 = action.payloadToTakeAction ? { payload: action.payloadToTakeAction } : null\n // output 2 is for overriding PS strategies\n const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"off\" }, name: action.nameOfStrategyToOverride} } : null\n // output 3 is for logging\n const output3 = { payload: info }\n\n node.send([output1, output2, output3])\n reductionRequired = Math.max(0, reductionRequired - consumption)\n reductionRecommended = Math.max(0, reductionRecommended - consumption)\n action.actionTaken = true\n action.actionTime = Date.now()\n action.savedConsumption = consumption\n flow.set(\"actions\", actions)\n}\n\nfunction getConsumption(consumption) {\n if(typeof consumption === \"string\") {\n const sensor = ha.states[consumption]\n return sensor.state / 1000\n } else if (typeof consumption === \"number\") {\n return consumption\n } else if(typeof consumption === \"function\") {\n return consumption()\n } else {\n node.warn(\"Config error: consumption has illegal type: \" + typeof consumption)\n return 0\n }\n}\n\nactions\n.filter(a => msg.payload.alarmLevel >= a.minAlarmLevel && !a.actionTaken)\n.forEach(a => {\n const consumption = getConsumption(a.consumption)\n if (consumption < MIN_CONSUMPTION_TO_CARE) {\n return\n }\n if (reductionRequired > 0 || (reductionRecommended > 0 && a.reduceWhenRecommended)) {\n takeAction(a, consumption)\n }\n})\n \n", "outputs": 3, "noerr": 0, "initialize": "// You MUST edit the actions array with your own actions.\n\nconst actions = [\n {\n consumption: \"sensor.varmeovn1\",\n name: \"Varmeovn1\",\n id: \"Varmeovn1\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.varmeovn1\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.varmeovn1\"]\n }\n }\n },\n {\n consumption: \"sensor.heatit_z_trm3_olander_soverom_electric_consumption_w\",\n name: \"Varmekabler gulv olander\",\n id: \"gulvolander\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.heatit_z_trm3_olander_soverom\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.heatit_z_trm3_olander_soverom\"]\n }\n }\n },\n { \n consumption: \"sensor.heatit_z_trm3_bad_hovedetasje_electric_consumption_w\",\n name: \"Varmekabler gulv bad\",\n id: \"gulvbad\",\n minAlarmLevel: 3,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.heatit_z_trm3_bad_hovedetasje\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.heatit_z_trm3_bad_hovedetasje\"]\n }\n }\n },\n {\n consumption: \"sensor.metered_wall_plug_switch_vvb_electric_consumption_w\",\n name: \"Varmtvannsbereder\",\n id: \"vvb\",\n minAlarmLevel: 4,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"switch.metered_wall_plug_switch_vvb\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"switch.metered_wall_plug_switch_vvb\"]\n }\n }\n },\n {\n consumption: \"sensor.volkedalsveien41stue_current_power\",\n name: \"Varmepumpe Stue\",\n id: \"varmepumpestue\",\n minAlarmLevel: 4,\n reduceWhenRecommended: true,\n minTimeOffSec: 300,\n payloadToTakeAction: {\n domain: \"climate\",\n service: \"turn_off\",\n target: {\n entity_id: [\"climate.volkedalsveien41stue\"]\n }\n },\n payloadToResetAction: {\n domain: \"climate\",\n service: \"turn_on\",\n target: {\n entity_id: [\"climate.volkedalsveien41stue\"]\n }\n }\n }\n]\n// End of actions array\n\n// DO NOT DELETE THE CODE BELOW\n\n// Set default values for all actions\nactions.forEach(a => {\n a.actionTaken = false\n a.savedConsumption = 0\n})\n\nflow.set(\"actions\", actions)\n", "finalize": "const actions = flow.get(\"actions\")\n\nactions\n .filter(a => a.actionTaken)\n .forEach(a => \n node.send({ payload: a.payloadToResetAction })\n )", "libs": [], "x": 510, "y": 800, "wires": [ ["caaed0baefd22047"], [], ["218931629f65e38f"] ] }, { "id": "c1ce4bb3fc69a534", "type": "function", "z": "e442ba1155240757", "name": "Reset Actions", "func": "\nconst actions = flow.get(\"actions\")\nconst ha = global.get(\"homeassistant\").homeAssistant\n\nconst BUFFER_TO_RESET = 1 // Must have 1kW extra to perform reset\n\nlet increasePossible = msg.payload.increasePossible\n\nif (increasePossible <= 0) {\n return null\n}\n\nfunction resetAction(action) {\n const info = {\n time: new Date().toISOString(),\n name: \"Reset action\",\n data: msg.payload,\n action\n }\n const output1 = action.payloadToResetAction ? { payload: action.payloadToResetAction } : null\n const output2 = action.nameOfStrategyToOverride ? { payload: { config: { override: \"auto\" }, name: action.nameOfStrategyToOverride } } : null\n const output3 = { payload: info }\n\n node.send([output1, output2, output3])\n increasePossible -= action.savedConsumption\n action.actionTaken = false\n action.savedConsumption = 0\n flow.set(\"actions\", actions)\n}\n\nactions\n .filter(a => a.actionTaken\n && (a.savedConsumption + BUFFER_TO_RESET) <= increasePossible\n && (Date.now() - a.actionTime > a.minTimeOffSec * 1000)\n ).forEach(a => resetAction(a))\n", "outputs": 3, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 860, "wires": [ ["caaed0baefd22047"], [], ["218931629f65e38f"] ] }, { "id": "caaed0baefd22047", "type": "api-call-service", "z": "e442ba1155240757", "name": "Perform action", "server": "a7c55e81aa13f774", "version": 5, "debugenabled": false, "domain": "", "service": "", "areaId": [], "deviceId": [], "entityId": [], "data": "", "dataType": "jsonata", "mergeContext": "", "mustacheAltTags": false, "outputProperties": [{ "property": "payload", "propertyType": "msg", "value": "payload", "valueType": "msg" }], "queue": "none", "x": 820, "y": 890, "wires": [ [] ] }, { "id": "218931629f65e38f", "type": "file", "z": "e442ba1155240757", "name": "Save actions to file", "filename": "/share/capacity-actions.txt", "filenameType": "str", "appendNewline": true, "createDir": false, "overwriteFile": "false", "encoding": "none", "x": 830, "y": 950, "wires": [ [] ] }, { "id": "4236d1c26a146d47", "type": "catch", "z": "e442ba1155240757", "name": "Catch action errors", "scope": ["97210699c79c9f9f", "c1ce4bb3fc69a534"], "uncaught": false, "x": 510, "y": 930, "wires": [ ["218931629f65e38f"] ] }, { "id": "186f4bdccef3a36b", "type": "function", "z": "e442ba1155240757", "name": "Find highest per day", "func": "const MAX_COUNTING = 3\nconst hours = msg.payload\nconst days = new Map()\nhours.forEach (h => {\n const date = (new Date(h.last_updated)).getDate()\n if (!days.has(date) || h.state > days.get(date).consumption) {\n days.set(date, { from: h.last_updated, consumption: h.state})\n }\n})\nconst highestToday = days.get((new Date()).getDate()) ?? {\n consumption: 0,\n from: null\n}\nconst highestPerDay = [...days.values()].sort((a, b) => b.consumption - a.consumption)\nconst highestCounting = highestPerDay.slice(0, MAX_COUNTING)\nconst currentMonthlyMaxAverage = highestCounting.length === 0\n ? 0\n : highestCounting.reduce((prev, val) =>\n prev + parseFloat(val.consumption), 0) / highestCounting.length\nmsg.payload = {\n highestPerDay,\n highestCounting,\n highestToday,\n currentMonthlyMaxAverage\n}\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 520, "y": 520, "wires": [ ["ba65e3741dcfd9b2", "a825bc435bbda354"] ] }, { "id": "b9797512ef4afecc", "type": "api-get-history", "z": "e442ba1155240757", "name": "history home assistant ", "server": "a7c55e81aa13f774", "version": 0, "startdate": "", "enddate": "", "entityid": "sensor.energiforbruk_time", "entityidtype": "is", "useRelativeTime": true, "relativeTime": "1month", "flatten": true, "output_type": "array", "output_location_type": "msg", "output_location": "payload", "x": 420, "y": 420, "wires": [ ["186f4bdccef3a36b"] ] }, { "id": "31e13220c3ad46fc", "type": "inject", "z": "e442ba1155240757", "name": "1 time", "props": [{ "p": "topic", "vt": "str" }], "repeat": "3600", "crontab": "", "once": true, "onceDelay": "2", "topic": "", "x": 160, "y": 420, "wires": [ ["b9797512ef4afecc"] ] }, { "id": "6d61563197130392", "type": "inject", "z": "e442ba1155240757", "name": "1 minutt", "props": [{ "p": "payload.timestamp", "v": "", "vt": "date" }], "repeat": "10", "crontab": "", "once": true, "onceDelay": "10", "topic": "", "x": 160, "y": 500, "wires": [ ["c24c8da51344b9ba"] ] }, { "id": "a825bc435bbda354", "type": "function", "z": "e442ba1155240757", "name": "function 5", "func": "\nnode.warn(msg);\n\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 880, "y": 380, "wires": [ [] ] }, { "id": "a7c55e81aa13f774", "type": "server", "name": "Home Assistant", "version": 5, "addon": true, "rejectUnauthorizedCerts": true, "ha_boolean": "y|yes|true|on|home|open", "connectionDelay": false, "cacheJson": true, "heartbeat": false, "heartbeatInterval": "30", "areaSelector": "friendlyName", "deviceSelector": "friendlyName", "entitySelector": "friendlyName", "statusSeparator": "at: ", "statusYear": "hidden", "statusMonth": "short", "statusDay": "numeric", "statusHourCycle": "h23", "statusTimeFormat": "h:m", "enableGlobalContextStore": true }]

ottopaulsen commented 1 year ago

Ok, closing issue then.