Optimizing energy costs (washing machines, tumblers, dishwashers, ...)

What follows is a sketch on how to optimize the energy costs of white goods (it might be extended to meet more complex boundary conditions as required for optimizing the energy costs for heat pumps, EVs, …). But please don’t get too excited re white goods: the savings on white goods are marginal compared to charging your car during cheap hours - but many a mickle makes a muckle.

BOM

  • DUO (device under optimization)
  • plug to measure power (Zigbee, Z-Wave, …)
  • InfluxDB
  • Grafana (optional)
  • dynamically priced energy contract, e.g. Tibber
  • Lua and json-lua (sudo luarocks install json-lua).

Recipe:

  1. Use Grafana to find a nice power profile of your DUO (e.g., Bosch washing machine):

  2. Export the data from your InfluxDB (adjust the parameters accordingly, start time and stop time can be gleaned from the headline in Grafana:


    use the correct timezone in your query):

influx -username <username> -password <password> -database 'openhab' -execute "select * from ZBSteckdosemessend2Waschmaschine_Power WHERE time >= '2023-03-17T21:59:24+01:00' AND time <= '2023-03-17T23:22:00+01:00'" > bosch_program_1.power_profile
  1. Get the hourly energy rates for today and tomorrow from Tibber (note that the rates for tomorrow are availabe about 10 minutes to 1 pm (CET/CEST), insert your personal Tibber API token):
curl \
-H "Authorization: Bearer <your personal Tibber API token>>" \
-H "Content-Type: application/json" \
-X POST \
-d  '{ "query": "{ viewer { homes { currentSubscription{ priceInfo{ today { total } tomorrow { total } } } } } }" }' https://api.tibber.com/v1-beta/gql > tibber_rates_20230318.json
  1. Use the following Lua script to calculate the optimal starting time (simple mathematics and brute force to calculate the optimal starting time):

--json = require "json"
json = require "JSON"

tibber_rates  = 'tibber_rates_20230320_21.json'
power_profile = 'bosch_dishwasher_70_non_eco.power_profile'


function fmt( t )

 res = ''

 if t >= 24
  then
   t = t - 24
   res = '+'
  end

 tmp = math.ceil( ( t - math.floor( t ) ) * 60 )

 if tmp == 60
  then
   t = t + 1
   tmp = 0
  end

 return res .. math.floor( t ) .. ':' .. string.format( '%02d', tmp )

end -- function fmt


function to_eur( x )

 return math.floor( x * 100 + 0.5 ) / 100

end -- function fmt


file = io.open(  tibber_rates, 'rb' )
txt = file:read '*a'

--res = json.decode( txt )
res = json:decode( txt )

prices = {}

for x, y in pairs( res["data"]["viewer"]["homes"][1]["currentSubscription"]["priceInfo"][ "today" ] )
 do
  prices[ x - 1 ] = y[ "total" ]
end

for x, y in pairs( res["data"]["viewer"]["homes"][1]["currentSubscription"]["priceInfo"][ "tomorrow" ] )
 do
  prices[ x - 1 + 24 ] = y[ "total" ]
 end

min_start = math.huge
min_price = math.huge

max_start = -math.huge
max_price = -math.huge

for h = ( os.date("*t").hour + os.date("*t").min / 60 + os.date("*t").sec / 3600 ) * 10, 480 -- search from now, interval: 1/10 h = 6 min
 do

  i=0

  cost = 0

  last_p = 0
  last_t = 0

  for l in io.lines( power_profile )
   do

    if i > 2 -- skip header
     then

      tx, sx, px = string.match( l, '(.*) (.*) (.*)' )

      t =  tonumber( tx ) / 1E9
      p =  tonumber( px )

      if i == 3 then profile_start_time = t end -- shift power_profile to t = 0

      t = t - profile_start_time + h / 10 * 3600

          if last_t ~= 0
           then

            t_temp = math.floor( t / 3600 )

        if t_temp >= 48
                 then
                  cost = math.huge * 0  -- nan; not computable: no rate for the day after tomorrow ...
         else
          cost = cost + ( p + last_p ) / 2 * ( t - last_t ) *  prices[ t_temp ] / 1000 / 3600
                 end

       end

      last_p = p
          last_t = t

     end

    i = i +1

   end -- for l in io.lines

  if cost < min_price
   then

    min_price = cost
    min_start = h

   end

  if cost > max_price
   then

    max_price = cost
    max_start = h

   end

 end -- for h

print( '', 'hour', 'total cost' )

print( 'max: ', fmt( max_start/10 ), to_eur( max_price ) )
print( 'min: ', fmt( min_start/10 ), to_eur( min_price ) )

print ( 'max-min: ', to_eur( max_price - min_price ) )

Result:

        hour    total cost
max:    18.9    0.15
min:    +13     0.11
max-min:        0.04

Cost max for start at 18:54 today, cost min for start at 1 pm tomorrow. Maximal savings: 4 ct

  1. Repeat for different programs/different DUOs.

If you are worried about the energy required to brute force the optimization problem: :slight_smile:
Reduce

  • the data points in the power profile by averaging,
  • the search interval from 1/10 h = 6 min to 1 h; it shouldn’t make a big difference if the power profile isn’t too pathological.

Limitations:
The more intelligent the DUO is, the more variability will be seen in the power profile. This may invalidate the optimal solution to some extent.

Downloads:
bosch_program_1.power_profile.txt (28.9 KB)
tibber_rates_20230318.json (908 Bytes)
save_money.lua.txt (2.0 KB)

Questions:
By adding user configurable boundary conditions to the calculations (start not earlier than, end not later than, at least m times a day and at least every n-th hour, …) the script could be converted into a universal cost optimizer (-> new Binding?). What do you think? I would be particularly interested in your opinion on whether a list of boundary conditions can be created that covers most user stories.

Edit #1:
Improvements to Lua script.
Power profile of my dishwasher (Bosch, 70°C, non-eco):


bosch_dishwasher_70_non_eco.power_profile.txt (84.9 KB)
tibber_rates_20230320_21.json (907 Bytes)

Result:

        hour    total cost
max:    19:33   0.47
min:    +21:51  0.37
max-min:        0.1
1 Like

If you have PV power from the roof calculated @10ct, doing the washing and dishes will amount to almost 100 € on average (based on current pricing in Germany). Every year.
I’m selling an energy management system with exactly this prime motivation.
For those interested, here’s some more calculations on potential savings.
It’s of course less with dynamic tariffs but still well worth it.
I’ve recently built in support for that on heat pumps and EV charging, too, but haven’t gotten around to do the calculations yet.

Sure - but PV isn’t part of my BOM. :slight_smile:

Yes, but you can do your math now. 40ct - 10ct amount to 100€.
You can lookup average cheapest Tibber hours and calculate with that instead of my 10 PV cents.

I wonder why should I use an EMS when I can control my devices (Heatpump, Washing Machine, Dish Washer etc.) using openhab, depending on available PV-Power?

I also tried to use weather forecast, but this is in many cases too inaccurate.

Because it ain’t easy to get it all right.
I know, my EMS is using openHAB :wink:

In the end it’s lots of details, time consuming tweaking and testing.
That is what you might want to avoid going through by taking the purchase route.

That’s right, you need at least a proper solar forecast.

In the end it’s lots of details, time consuming tweaking and testing.
That is what you might want to avoid going through by taking the purchase route.

Thats absolutely right. It took me a lot of time to catch all possible conditions and the different behaviours of the devices, but now I’m pretty happy with my rules :slight_smile:

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.