-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathelectricity-price-widget.lua
More file actions
189 lines (162 loc) · 5.13 KB
/
electricity-price-widget.lua
File metadata and controls
189 lines (162 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
-- name = "Electricity spot price"
-- description = "Day-ahead spot price of electricity"
-- data_source = "https://api.energy-charts.info/"
-- type = "widget"
-- foldable = "true"
-- author = "Hannu Hartikainen <hannu.hartikainen@gmail.com>"
-- version = "1.1"
json = require "json"
prefs = require "prefs"
url_base = "https://api.energy-charts.info/price?bzn=%s&end=%d"
price_data = nil
price_interval = nil
price_unit = nil
unit_multiplier = 1
next_fetch_ts = 0
next_redraw_ts = nil
-- parameters and default values
function on_load()
-- bidding zone: see "Available bidding zones" in https://api.energy-charts.info/
if not prefs.bidding_zone then
prefs.bidding_zone = "FI"
end
-- VAT percentage
if not prefs.vat_percentage then
prefs.vat_percentage = 25.5
end
-- change threshold percentages for showing changes in folded format
if not prefs.oneline_threshold_high then
prefs.oneline_threshold_high = 1.3
end
if not prefs.oneline_threshold_low then
prefs.oneline_threshold_low = 0.7
end
-- url to open when clicked
if not prefs.click_url then
prefs.click_url = "https://www.sahkonhintatanaan.fi/"
end
end
function get_url()
-- at most about 35 hours are known in advance; fetch all known prices
local end_offset = 2*24*60*60
local end_ts = os.time() + end_offset
return string.format(url_base, prefs.bidding_zone, end_ts)
end
function on_alarm()
if os.time() > next_fetch_ts then
http:get(get_url())
end
end
function on_network_result(result, code)
if code >= 200 and code < 299 then
parse_result(result)
draw_widget(true)
end
end
function on_tick(t)
local ts = os.time()
if next_redraw_ts and ts > next_redraw_ts then
draw_widget(true)
end
end
function on_click()
if not ui:is_folded() then
system:open_browser(prefs.click_url)
else
draw_widget(false)
end
end
function parse_result(result)
price_data = json.decode(result)
local price_count = #price_data.unix_seconds
if price_count < 2 then
return
end
price_interval = price_data.unix_seconds[2] - price_data.unix_seconds[1]
if price_data.unit == "EUR/MWh" or price_data.unit == "EUR / megawatt_hour" then
price_unit = "c/kWh"
unit_multiplier = 0.1
else
price_unit = price_data.unit
unit_multiplier = 1
end
-- assume next day is known 8 hours before it starts
-- (eg. Nord Pool Spot typically publishes dayahead prices at 14 local time)
local next_fetch_offset = 8*60*60
local end_of_data_ts = price_data.unix_seconds[price_count] + price_interval
next_fetch_ts = end_of_data_ts - next_fetch_offset
end
function get_current_idx()
local t = os.time()
for i = 1, #price_data.unix_seconds do
local ts = price_data.unix_seconds[i]
if ts < t and t < (ts+price_interval) then
return i
end
end
end
function price(i)
return price_data.price[i]
end
function get_display_price(idx)
local mul = (1.0 + (prefs.vat_percentage / 100.0)) * unit_multiplier
-- NOTE: float rounding in string.format doesn't work so do it here
return math.floor(100.0 * price(idx) * mul + 0.5) / 100.0
end
function get_display_time(idx)
return os.date("%H", price_data.unix_seconds[idx])
end
function format_price(idx)
return string.format("%0.2f", get_display_price(idx))
end
function format_price_and_unit(idx)
return string.format("%s %s", format_price(idx), price_unit)
end
function format_oneline(idx)
local more_prices = ""
local more_count = 0
local cur_price = price(idx)
for i = idx+1, #price_data.price do
if price(i) > prefs.oneline_threshold_high * cur_price
or price(i) < prefs.oneline_threshold_low * cur_price then
more_count = more_count + 1
more_prices = more_prices .. string.format("⋄ <i>%s′</i> <b>%s</b> ", get_display_time(i), get_display_price(i))
cur_price = price(i)
if more_count > 3 then
break
end
end
end
return string.format("<b>%s</b> %s", format_price_and_unit(idx), more_prices)
end
-- NOTE: using timestamps would be better than indices, but the chart element
-- doesn't support times spanning multiple days properly
function make_chart_data(idx)
local chart = {}
for i = idx, #price_data.price do
table.insert(chart, {
i-1,
get_display_price(i)
})
end
return chart
end
function draw_widget(fold)
ui:set_folding_flag(fold)
local idx = get_current_idx()
if not idx then
ui:show_text("Error: no current price data")
-- request fetch on next on_alarm and don't redraw before that
next_fetch_ts = 0
next_redraw_ts = nil
return
end
next_redraw_ts = price_data.unix_seconds[idx+1]
ui:set_title(string.format("Electricity spot price: %s", format_price_and_unit(idx)))
ui:show_chart(make_chart_data(idx), "x: int, y: float", "", true, format_oneline(idx))
end
function on_settings()
if (prefs.show_dialog) then
prefs:show_dialog()
end
end