Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions tasmota/berry/modules/cheap_power/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
## CheapPower Module

This fetches electricity prices and chooses the cheapest future time slots.
Currently, the following price data sources have been implemented:
* `cheap_power_dk_no.tapp`: Denmark, Norway (Nord Pool)
* `cheap_power_elering.tapp`: Estonia, Finland, Latvia, Lithuania (Nord Pool via [Elering](https://elering.ee/en), ex VAT)
* `cheap_power_fi.tapp`: Finland (Nord Pool)
* `cheap_power_se.tapp`: Sweden (Nord Pool); assuming Swedish time zone
* `cheap_power_smard.tapp`: https://smard.de (Central Europe; local time)
* `cheap_power_uk_octopus.tapp`: United Kingdom (Octopus Energy)

### Usage:

* copy the `cheap_power_*.tapp` for your data source to the file system
* Invoke the Tasmota command `BrRestart` or restart the entire firmware
* Invoke the Tasmota command `CheapPower1`, `CheapPower2`, … to
* download prices for some time into the future
* automatically choose the cheapest future time slots (default: 1)
* to schedule `Power1 ON`, `Power2 ON`, … at the chosen slots
* to install a Web UI in the main menu

### Timer Installation:
```
# Europe/Helsinki time zone; see https://tasmota.github.io/docs/Timezone-Table/
Backlog0 Timezone 99; TimeStd 0,0,10,1,4,120; TimeDst 0,0,3,1,3,180

# Detach Switch1 from Power1
Backlog0 SwitchMode1 15; SwitchTopic1 0
Backlog0 WebButton1 boiler; WebButton2 heat
## Power off after 900 seconds (15 minutes)
#PulseTime1 1000
# Power off after 3600 seconds (60 minutes, 1 hour)
PulseTime1 3700

Rule1 ON Clock#Timer DO CheapPower1 … ENDON
Timer {"Enable":1,"Mode":0,"Time":"18:00","Window":0,"Days":"1111111","Repeat":1,"Output":1,"Action":3}
Rule1 1
Timers 1
```
The download schedule can be adjusted in the timer configuration menu.
The prices for the next day will typically be updated in the afternoon
or evening of the previous day.

In case the prices cannot be downloaded, the download will be retried
in 1, 2, 4, 8, 16, 32, 64, 64, 64, … minutes until it succeeds.

### Additional Parameters

* `cheap_power_fi.tapp` (Finland): none
* `cheap_power_elering.tapp`: price zone FI, EE, LT, LV
* `cheap_power_dk_no.tapp` (Denmark, Norway): price zone DK1, DK2, NO1 to NO5
* `cheap_power_se.tapp` (Sweden): price zone SE1 to SE4
* `cheap_power_smard.tapp`: zone AT, BE, CH, CZ, DE, FR, HU, IT, LU, NL, PL, SI
```
CheapPower1 DE
```
* `cheap_power_uk_octopus.tapp` (United Kingdom): tariff name and price zone:
```
CheapPower AGILE-24-10-01 B
```
The default number of active price slots per day is 1. It can be set in the
web user interface.

### Web User Interface

The user interface in the main menu consists of the following buttons:
* ⏮ moves the first time slot earlier (or wraps from the beginning to the end)
* ⏭ moves the first time slot later (or wraps from the end to the beginning)
* ⏯ pauses (switches off) or chooses the optimal slots
* 🔄 requests the prices to be downloaded and the optimal slots to be chosen
* ➖ decreases the number of slots (minimum: 1)
* ➕ increases the number of slots (maximum: currently available price slots)

The status output above the buttons may indicate that the output
is paused until further command or price update:
```
(0≤1)0.299‥2.38 ¢/kWh ⭘
```
Or it may indicate the price and duration until the next active slot:
```
(1≤1)0.299‥2.38 ¢/kWh (0.299) ⭙ 5:05
```
Or it may indicate the price of the currently active time slot:
```
(1≤1)0.299‥2.38 ¢/kWh (0.299) ⭙
```
The first number indicates the number of remaining scheduled time slots,
and the second number indicates the maximum number of time slots per day
(default 1).

The two quantities around the ‥ are the minimum and maximum known prices
from the current time onwards.

The scheduled slots are also indicated by red bars in the line graph
of the available current and future prices.

### Application Contents

```bash
for i in */cheap_power.be
do
zip ../cheap_power_${i%/*}.tapp -j -0 autoexec.be \
cheap_power_base.be binary_heap.be "$i"
done
```
11 changes: 11 additions & 0 deletions tasmota/berry/modules/cheap_power/autoexec.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var wd = tasmota.wd
tasmota.add_cmd("CheapPower",
def (cmd, idx, payload)
import sys
var path = sys.path()
path.push(wd)
import cheap_power
path.pop()
cheap_power.start(idx, payload)
end
)
54 changes: 54 additions & 0 deletions tasmota/berry/modules/cheap_power/binary_heap.be
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# https://en.wikipedia.org/wiki/Binary_heap
# This allows to choose the M first elements of an N-sized array
# with respect to a comparison predicate cmp that defines a total order.
# This avoids the overhead of sorting the entire array and then picking
# the first elements. This is related to a priority queue.
# We also define a binary heap based sort() of an entire array.

var binary_heap = module("binary_heap")

binary_heap._heapify = def(array, cmp, i)
var m = i, child, e, am, ac
while true
child = 2 * i + 1
if child >= array.size() return end
ac = array[child]
am = array[m]
if cmp(ac, am) m = child am = ac end
child += 1
if child < array.size()
ac = array[child]
if cmp(ac, am) m = child am = ac end
end
if m == i break end
array[m] = array[i]
array[i] = am
i = m
end
end

# similar to C++11 std::make_heap
binary_heap.make_heap = def(array, cmp)
var i = size(array) / 2
while i >= 0 binary_heap._heapify(array, cmp, i) i -= 1 end
end

# similar to C++11 std::pop_heap, but removes and returns the element
binary_heap.remove_heap = def(array, cmp)
var m = array.size()
if m < 2 return m == 1 ? array.pop() : nil end
m = array[0]
array[0] = array.pop()
binary_heap._heapify(array, cmp, 0)
return m
end

# https://en.wikipedia.org/wiki/Heapsort
binary_heap.sort = def(array, cmp)
var i = array.size(), heap = array.copy()
binary_heap.make_heap(heap, cmp)
array.clear()
while i > 0 array.push(binary_heap.remove_heap(heap, cmp)) i -= 1 end
end

return binary_heap
Loading