Doing things when the electricity price is low

Hi.
I have a challenging rule task here, hope anyone has any good input.

I have a dehumidifier which I want to be turned on a couple of hours a day and that should preferrably be when the electricity price is at the lowest. I already have an item for the price and it’s persisted at every change.

Approach one would be to simply turn the device on between like 23:00 and 06:00, but that would be a kinda boring solution.

Approach two would be to turn the device on when the price is below a static limit (ie a specific prize) but that would mean very many hours on some days and very few on others.

Approach three would be to turn the device on when the prize is below the average for the last 24 hours, that would mean it’s turned on at average 12 hours per day. I think that’s a bit too much though.

Anyone got any other ideas? Or maybe that has already solved this problem elsewhere? I guess what I’d really want is for the device to turn on if the prize is below something like the 25th percentile of the last 24 hours prizes but I don’t think there are any functions in the persistence system to get that, right?

Oh cool idea. I’m more focused on making stuff work when producing a certain amount of energy, but that’s cool as well.

My suggestion: add a humidity sensor, as a gate keeper. Dehumidifier will only work when electricity is cheap AND humidity is over x%.
To make it better, include temperature into the mix. Because 60% humidity in a hot climate is a lot different than 60% in the cold…

(So, at 30 degrees, make dehumidifier at 50% humidity for example.)

Oh, maybe I should have mentioned: The humidifier in question has built in sensor, so I’ve set it to only operate when the relative humidity is above 65%. It also has a built in temperature guard, so it does nothing when it’s below 5 degrees celsius.

The humidier in question is positioned in my boat, it’s function is to prohibit mold growth during the winter. The last few winters here in Sweden has been very damp and mold likes it very much when the relative humidity is above like 70%.

Hi,
This binding is for the Swedish market!. It’s still under progress so it works just with one block running.
I am in progress and I’m quit sure that it’s solved to have couple blocks running parallell from the very same Thing. But I need some more time for to have the next version finished.
I use it so far for my house-fan for test, so I run the fan with Block12 and the fan will run the 12 (almost cheapest hours a day, regarded to a schedule I have tried to figure out will be best for my house.
Please, have a look in README.md for more info. Any input is very helpfull!

Br Basse

1 Like

Strange things happens now and then, today you got paid some hours for power, 03:00 is like -‘money’ / kWh
This is not what the binding expect so I have to take care of this :wink:
Br Basse

This is how it is in Sweden today, regarded to Power-prices.

Aha! My first thought was that you misunderstood me, since I already have a binding for the electricity price (using Tibber binding), but I realize this has some very useful fucntions for exactly my problem! I think I just need to get my head around how those block settings work…

However, I don’t succeed installing at all. I just dropped the jar in /usr/share/openhab/addons (I have other working bindings in there so I’m positive it’s the right location) but nothing happens at all. It doesn’t turn up in the gui and not in bundle:list. Any idea what could be wrong? I’m on 3.3.0 release build.

I like boring. It’s simple, easy to understand, and easy to debug. Boring doesn’t fail as often.

But what about approach 4. Run it when the price is below a threshold (you could create another rule to calculate that threshold. But keep track of how long you’ve run it and stop when you hit the max runtime. When you get to midnight-max runtime in the day (or what ever time you choose to start the day with, you can decide to run it the remaining time even if the power cost is too high.

In short, break it up into at least three parts.

  1. calculate the threshold based on current and historic prices
  2. schedule the device to run
  3. make up for times where the device didn’t run long enough

tackle each one separately perhaps in three or more separate rules.

1 Like

I’m glad you think it can be helpful. I will take a closer look in beginning of next week, I’m busy during the weekend.
My thought’s about this binding is to just deliver the info. about prices and let the decisions to any kind of rules. And this is how I use it ( Still Ver.1 ), I check the prices, let the binding know which of the 24 available prices I like to activate. Let the binding check and tell when any of these choosen occasions is actual and send a messages. Then there is a rule taking care of the rest and triggers mine Telldus-power-switch.

I am still not very sure that this is the best way to handle the problem, with these Blocks I mean, but I have found it helpful for my needs so far.

The binding can still just run one Block, but in my Eclipse is it possible to run several, so my plan is to have the fan running as today with Block12 and my Nibe ground-heater produce WV regarded to Block24, I just still need some more time to have it like that.

Thank’s a lot for your input, this is exactly what I need to not get blind of my own thougt’s. I will do my best to have them implemented when they occur, it could just take some time. I’m a nopro, just very interested and do like these kind of communitys very much.
Br Basse

Hi, just uploaded a new one, the very same I have running as above. Please try.
I had the Ver.0 running for a while with the 12 cheapest hour a day, but found out that these hours occur almost every day on the entire night an almost not at all during daytime. I like to have my fan running also during day time but not at the very most expensive hours so that’s the reason why I invented Blocks.
Have a nice weekend, we keep in touch!
Br Basse

From my openhab> bundle:list
233 │ Active │ 80 │ 3.3.0.202203101408 │ openHAB Add-ons :: Bundles :: ElenNu Binding
251 │ Active │ 80 │ 1.14.3 │ jsoup Java HTML Parser

So I’m leaving for the sun tonight, so I don’t dare to start testing new bindings :slight_smile: but this binding looks interesting! Perhaps it should have it’s own thread?

But reading about the blocks, I’m a bit lost… What is the purpose of having multiple parts? Will the parts contain specific hours of the day?
And, is it not possible to work on tomorrows prices?

Ok. That feels kinda TOO unboring :grinning_face_with_smiling_eyes:

I’ll try to solve it with @Basse_03’s binding first, just need to succeed with the installation. I guess the problem is that it’s made for 3.4 and I’m on 3.3, so I might need to take the step up to milestones…

Hi all,
@DanielMalmgren do you maybe have done any progress?

About Blocks, in my case I like to have my house-fan running like 12 hours a day, if I then setup the binding to set the Block12 Switch ON like: use the 12 cheapest hours it’s almost certain that they will be during nighttime in an compound couple of hours. So I made the Blocks for to be able to spread the cheaper hours easier over day. You can avoid to use the very most expencive hour’s but still be able to spread the ON-period(s) over 24 hours.

Each Block size has it’s own number of Part’s ( Block12 has 12 block parts) and each Part has it’s own number of Cell’s( Block12 has 2 cells in each part). So for all Blocks is it possible to have 24 different cell’s, and they will be fulfilled with price and time of day every 60 minutes regared to the scraped info. from elen.nu. At the same time will the binding check if there is any of the chosen cells (asO’s) that is actual at this very hour, and if it is will the binding set the BlockX switch to on.

The binding is using elen.nu for price.info. It update prices and do checks at default each 60 minutes, I have to have a closer look when elen.nu updates, I think it is at midnight.
Br Basse

Nope, haven’t had time looking at it yet. I think I’ll need to update from 3.3 to milestones to install the addon…

Oki, take the time you need. Just curios.

Nope, I’m sorry… Upgraded to 3.4.0.M4 now and it still doesn’t load the binding. Tried both the one I downloaded last week and redownloaded it. Don’t really know where to search for the problem… It is in /usr/share/openhab/addons and other addons have loaded successfully from the same directory…

Hi,
I have just uploaded the latest version.
This one with possibilities to have several Blocks at the same time with different setup.
I have it running on a OH3.3.4.0 as below.
_ _ _ ____
___ ___ ___ ___ | | | | / \ | __ )
/ _ \ / _ \ / _ \ / _ \ | || | / _ \ | _ \
| (
) | () | __/| | | || _ | / ___ \ | |) )
___/| / _/|| |||| ||// _|__/
|
| 3.4.0.M4 - Milestone Build
241 │ Active │ 80 │ 3.4.0.202211171319 │ openHAB Add-ons :: Bundles :: ElenNu Binding

Block X is the switch. Next ON today for Block12 regarded to my scheme will be at 19:00 to a price of 54.17öre

Average Price is the average price from elennu.

Calculated Average Price is the average price for the elected hour’s, in both these cases 12 hours / day but regarded to different schemas differs the Calculated Average Price between Block12 and Block24.

Br Basse

I do something similar to this in NZ, but for my freezer and fridge (not the main one in the kitchen, the infrequently used ones). They can tolerate 6-8 hours of being off, but not a lot more than that.

I hold a running average of the prices, and they run when the price is below a threshold, and below both the current average and the forecast average for the next few hours (I can get forecast power prices from scraping a web page that our electricity commission run).

Basically I have it calculate when it thinks the price is about as low as it’ll be. And then I have a failsafe - if in any block of time it hasn’t run at least X hours of the last 24 hours, then it runs irrespective.

That exactly logic wouldn’t work for you, but similar logic could. And it’s been a long time since I wrote it…so that description is approximate. But my code is somewhat readable, as below.

For whatever reason, at the time I didn’t want to use a proper array type (might have been before I worked out how to, or might have been because I wanted it persisted across reboots). So I split the array of prices into Prices A, Prices B, Prices C, then I concatenate it back together to use it. It’s awfully ugly, so I presume I could refactor that now to be a lot smarter. Anyway, whatever, it’s working.

rule "fridges turn off"
when
  Item swFridges changed to OFF
then
  dtFridgesLastTurnOff.postUpdate( new DateTimeType( ZonedDateTime.now() ))

  if( fridgeTimer !== null ){
    fridgeTimer.cancel()
    logError( 'plug', 'Fridges just turned off, but there was a fridge timer, so why didn\'t that get cancelled when fridges turned on?')
  }

  logDebug( 'plug', 'Fridges turned off, setting a 5 hour timer to turn them back on if power doesn\'t get cheap by then' )
  fridgeTimer = createTimer( ZonedDateTime.now().plusSeconds( 5 * 60 * 60 + 300 ) ) [ |
    logInfo( 'plug', 'Fridges have been off for 5 hours, turning back on on presumption we\'ve forgotten about them' )
    swFridges.sendCommand( ON )
    fridgeTimer = null
  ]
end

rule "fridges turn on"
when
  Item swFridges changed to ON
then
  dtFridgesLastTurnOn.postUpdate( new DateTimeType( ZonedDateTime.now() ) )
  if( fridgeTimer !== null ){
    fridgeTimer.cancel()
    fridgeTimer = null
    logDebug( 'plug', 'Cancelling timer, not needed as we turned on anyway' )
  } else {
    logError( 'plug', 'Fridges turned on and there wasn\'t a timer, that\'s a defect' )
  }
end

rule "reset power settings"
when
  Item swPowerReset changed to ON
then
  fridgeTimer = null
  swPowerReset.postUpdate( OFF )
end

rule "prices change"
when
  Item numberPowerPriceNow received update
then
  logDebug( 'plug', 'Running price calc' )

  // Our logic is that we want to find a threshold where we run at least 1.5 hours in the next 5, and 4 hours in the next 10
  // We calculate the average of the values, and then we iterate find a percentage adjustor where we will run at least that much
  // We also check that we've run at least 1 hour in the last 5, if we haven't then we run anyway

  // Get our prices into a number array by joining then splitting the strings, and casting to a number
  var String priceString = stringPowerPricesA.state.toString() + ',' + stringPowerPricesB.state.toString() + ',' + stringPowerPricesC.state.toString()
  val List<Number> numberPriceArray = newArrayList()
  var int i

  for( i = 0; i < priceString.split(",").length(); i++ ){
    if( priceString.split(",").get(i) == "" ){
      numberPriceArray.add(i, 0 )
    } else {
      numberPriceArray.add(i, Double::parseDouble(priceString.split(",").get(i)) )
    }
  }

  // get the average
  var Number priceAverage = 0
  for( i = 0; i < numberPriceArray.size(); i++ ){
    priceAverage = priceAverage + numberPriceArray.get(i)
  }
  priceAverage = priceAverage / numberPriceArray.size()


  // iterate to find a percentage that gets us enough run time
  var Number percentage = -0.6
  var Number periodsIn5Hours = 0    // 5 hours from last turn off if we're off
  var Number periodsIn10Hours = 0
  var Number period5Hours = 10

  if( swFridges.state == OFF ){
    var Number secondsOff = ( new DateTimeType(ZonedDateTime.now() )).getZonedDateTime().toInstant.getEpochSecond() - (dtFridgesLastTurnOff.state as DateTimeType).getZonedDateTime().toInstant.getEpochSecond()
    period5Hours = ( 5 * 60 * 60 - secondsOff ) / ( 30 * 60 )
  }

  while( percentage < 1 && ( periodsIn5Hours < 5 || periodsIn10Hours < 11 ) ){
    percentage = percentage + 0.05
    periodsIn5Hours = 0
    periodsIn10Hours = 0
    for( i = 0; i < numberPriceArray.size(); i++ ){
      if( numberPriceArray.get(i) < priceAverage * ( 1 + percentage) ){
        periodsIn10Hours = periodsIn10Hours + 1
        if( i < period5Hours ){
          periodsIn5Hours = periodsIn5Hours + 1
        }
      }
    }
  }

  if( percentage >= 1 ){
    logError( 'plug', 'Percentage is 1 or greater, which isn\'t expected.  Input prices were ' + priceString + ' and priceAverage is ' + priceAverage )
  }

  // find out how many 10 minute periods we've run in last 5 hours
  var Number runTimeHistory   // number of ten minute periods in last 5 hours we've been on for
  var String hist = stringPowerOnHistory.state.toString()
  var String newHist

  while( hist.length() > 0 ){
    if( hist.substring(0,1) == '1' ){
      runTimeHistory = runTimeHistory + 1
    }
    hist = hist.substring(1)
  }

  var Number priceNow = numberPowerPriceNow.state as Number
  var Number priceTarget = priceAverage * (1 + percentage)

  if( priceNow < numberPowerLowEnough.state as Number ){
    if( swFridges.state == OFF ){
      logInfo( 'plug', 'Turn fridges on, price of ' + priceNow + ' is low enough' )
      swFridges.sendCommand( ON )
      newHist = "1"
    } else {
      logDebug( 'plug', 'Leave fridges on, price of ' + priceNow + ' is low enough' )
      newHist = "1"
    }
  }
  else if( runTimeHistory < 13 ){
    if( swFridges.state == OFF ){
      logInfo( 'plug', 'Turn fridges on, they\'ve been on for less than two hours in the last 5 hours' )
      swFridges.sendCommand( ON )
      newHist = "1"
    } else {
      logDebug( 'plug', 'Leave fridges on, they\'ve been on for less than two hours in the last 5 hours' )
      newHist = "1"
    }
  } else if( swFridges.state == OFF ){
    if( priceNow < priceTarget ){
      logInfo( 'plug', 'Turn fridges on, price of ' + priceNow + ' is lower than target price of ' + priceTarget + ' based on average of ' + priceAverage + ' and percentage of ' + percentage )
      swFridges.sendCommand( ON )
      newHist = "1"
    } else {
      logDebug( 'plug', 'Leave fridges off, price of ' + priceNow + ' is not lower than target price of ' + priceTarget + ' based on average of ' + priceAverage + ' and percentage of ' + percentage )
      newHist = "0"
    }
  } else {
    if( priceNow < priceTarget * 1.15 ){
      logDebug( 'plug', 'Leave fridges on, price of ' + priceNow + ' is lower than target price x 1.15 of ' + priceTarget * 1.15 + ' based on average of ' + priceAverage + ' and percentage of ' + percentage )
      newHist = "1"
    } else {
      logInfo( 'plug', 'Turn fridges off, price of ' + priceNow + ' is not lower than target price x 1.15 of ' + priceTarget * 1.15 + ' based on average of ' + priceAverage + ' and percentage of ' + percentage )
      swFridges.sendCommand( OFF )
      newHist = "0"
    }
  }

  // update power history
  if( stringPowerOnHistory.state.toString().length() < 1 ){
    stringPowerOnHistory.postUpdate( newHist )
  } else if ( stringPowerOnHistory.state.toString().length() < 30 ) {
    stringPowerOnHistory.postUpdate( stringPowerOnHistory.state.toString().concat(newHist) )
  } else {
    stringPowerOnHistory.postUpdate( stringPowerOnHistory.state.toString().substring(1).concat(newHist) )
  }

  numberPowerPriceTarget.postUpdate( priceTarget )
  numberPowerPriceAverage.postUpdate( priceAverage )
  numberPowerPricePercentage.postUpdate( percentage * 100 )

  logDebug( 'plug', 'Finished running price calc' )
end

Items file

Group Power (All)

Switch swFridges                     "Fridges [%s]"                          (Power, RestoreGroup, PowerGraphs) { channel="tplinksmarthome:hs110:FC1930:switch" }
Number numberFridgesPower            "Fridges Power [%d watts]"              (Power, PowerGraphs)               { channel="tplinksmarthome:hs110:FC1930:power" }
Number numberFridgesEnergyUsage      "Fridges Energy Usage [%.2f kWh]"       (Power, PowerGraphs)               { channel="tplinksmarthome:hs110:FC1930:energyUsage" }

Number numberPowerPriceNow           "Price Now [$%.2f]"                     (Power, RestoreGroup, PowerGraphs) { channel="mqtt:topic:pi2:Power:priceNow" }
String stringPowerPricesA            "Prices A [%s]"                         (Power, RestoreGroup)              { channel="mqtt:topic:pi2:Power:pricesA" }
String stringPowerPricesB            "Prices B [%s]"                         (Power, RestoreGroup)              { channel="mqtt:topic:pi2:Power:pricesB" }
String stringPowerPricesC            "Prices C [%s]"                         (Power, RestoreGroup)              { channel="mqtt:topic:pi2:Power:pricesC" }
Number numberPowerPriceTarget        "Target next 10 hours [$%.2f]"          (Power, RestoreGroup)
Number numberPowerPriceAverage       "Average next 10 hours [$%.2f]"         (Power, RestoreGroup)
Number numberPowerPricePercentage    "Percentage Adjustor [%d%%]"            (Power, RestoreGroup)
String stringPowerOnHistory          "History of Fridge Power [%s]"          (Power, RestoreGroup)

DateTime dtFridgesLastTurnOn  "Last Turn On [%1$td %1$tb %1$tH:%1$tM:%1$tS]"  <clock> (Power, RestoreGroup)
DateTime dtFridgesLastTurnOff "Last Turn Off [%1$td %1$tb %1$tH:%1$tM:%1$tS]" <clock> (Power, RestoreGroup)

Number numberPowerLowEnough          "Low enough to run [$%.2f]"             (Power, RestoreGroup)
Switch swPowerReset                  "Reset power rules [%s]"                (Power)

And a picture of what that looks like in my sitemap, to give you an idea of what the data looks like: