Add Electricity spot price widget
This widget supports showing electricity spot price for many areas in Europe. However, there's no configuration UI yet. I'm hoping there will be native support for editing the values in `prefs` later, but I'm also open to implementing the configuration UI in the widget if I get some help. The chart would be better if displayed with timestamps, but that doesn't currently work when the timestamps span two days.
This commit is contained in:
183
community/electricity-price-widget.lua
Normal file
183
community/electricity-price-widget.lua
Normal file
@@ -0,0 +1,183 @@
|
||||
-- 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.0"
|
||||
|
||||
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 = 24
|
||||
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:folding_flag() 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" 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
|
||||
Reference in New Issue
Block a user