Add Electricity spot price widget #24

Merged
dancek merged 1 commits from electricity-price-widget into master 2024-05-06 10:08:27 -04:00

View 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