From 9de0f5fd606a5aaddd43fea63d0a9eafb1c3b215 Mon Sep 17 00:00:00 2001 From: Franziska Kunsmann Date: Fri, 22 Dec 2023 14:37:08 +0100 Subject: [PATCH] node.lua: add basic fahrplan display for standalone usage --- node.lua | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 200 insertions(+), 3 deletions(-) diff --git a/node.lua b/node.lua index 45a613c..5f4aeab 100644 --- a/node.lua +++ b/node.lua @@ -1,7 +1,204 @@ -util.init_hosted() +local json = require "json" + +local TOPBAR_FONT_SIZE = 70 +local TALK_FONT_SIZE = 50 +local PADDING = 20 + +local font_clock +local font_day +local font_room +local font_talk +local font_text + +local day = 0 +local time = 0 +local clock = "??" +local schedule = {} +local all_next_talks = {} +local show_language = true +local show_track = true gl.setup(NATIVE_WIDTH, NATIVE_HEIGHT) -function node.render() - CONFIG.font:write(10, 10, "This is a plugin for scheduled player, don't use it individually", 30, 1,1,1,1) +local function log(what) + return print("[pretalx] " .. what) +end + +util.file_watch("schedule.json", function(new_schedule) + log("new schedule") + schedule = json.decode(new_schedule).talks +end) + +util.file_watch("config.json", function(content) + local config = json.decode(content) + + log("running on device ".. tostring(sys.get_env "SERIAL")) + show_language = config.show_language + show_track = config.show_track + + font_clock = resource.load_font(config.font_clock.asset_name) + font_day = resource.load_font(config.font_day.asset_name) + font_room = resource.load_font(config.font_room.asset_name) + font_talk = resource.load_font(config.font_talk.asset_name) + font_text = resource.load_font(config.font_text.asset_name) +end) + +util.data_mapper{ + ["(.*)"] = function(path, data) + log("received data '" .. data .. "' on " .. path) + if path == "day" then + day = tonumber(data) + elseif path == "clock" then + clock = data + elseif path == "time" then + time = tonumber(data) + end + end, +} + +local function parse_rgb(hex) + hex = hex:gsub("#","") + return tonumber("0x"..hex:sub(1,2))/255, tonumber("0x"..hex:sub(3,4))/255, tonumber("0x"..hex:sub(5,6))/255 +end + +local function check_next_talks() + if time == 0 then + log("No time info yet, cannot check for next talks") + return + end + + all_next_talks = {} + + local min_start = time - 25 * 60 + + for idx = 1, #schedule do + local talk = schedule[idx] + + -- Ignore all talks that have already ended here. We don't want + -- to announce these. + if talk.end_ts > time then + all_next_talks[#all_next_talks+1] = talk + end + end + + local function sort_talks(a, b) + return a.start_ts < b.start_ts or (a.start_ts == b.start_ts and a.room < b.room) + end + + table.sort(all_next_talks, sort_talks) +end + +local function wrap(str, font, size, max_w) + local lines = {} + local space_w = font:width(" ", size) + + local remaining = max_w + local line = {} + for non_space in str:gmatch("%S+") do + local w = font:width(non_space, size) + if remaining - w < 0 then + lines[#lines+1] = table.concat(line, "") + line = {} + remaining = max_w + end + line[#line+1] = non_space + line[#line+1] = " " + remaining = remaining - w - space_w + end + if #line > 0 then + lines[#lines+1] = table.concat(line, "") + end + return lines +end + +function node.render() + gl.clear(0, 0, 0, 1) + + y = PADDING + font_day:write(PADDING, y, string.format("Day %d", day), TOPBAR_FONT_SIZE, 1, 1, 1, 1) + + local clock_width = font_clock:width(clock, TOPBAR_FONT_SIZE) + font_clock:write(NATIVE_WIDTH-PADDING-clock_width, y, clock, TOPBAR_FONT_SIZE, 1, 1, 1, 1) + + y = y + TOPBAR_FONT_SIZE + PADDING*2 + check_next_talks() + + local time_size = TALK_FONT_SIZE + local info_size = math.floor(TALK_FONT_SIZE * 0.8) + + local col1 = PADDING + local col2 = PADDING*2 + 15 + font_text:width("XXX min ago", time_size) + + if #schedule == 0 then + font_text:write(col2, y, "Fetching talks...", TALK_FONT_SIZE, 1, 1, 1, 1) + elseif #all_next_talks == 0 and #schedule > 0 and sys.now() > 30 then + font_text:write(col2, y, "Nope. That's it.", TALK_FONT_SIZE, 1, 1, 1, 1) + end + + for idx = 1, #all_next_talks do + local talk = all_next_talks[idx] + + local title = talk.title + if show_language and talk.locale then + title = title .. " (" .. talk.locale .. ")" + end + + local title_lines = wrap( + title, + font_talk, TALK_FONT_SIZE, NATIVE_WIDTH - col2 - PADDING + ) + + local info_line = talk.room + + if #talk.persons > 0 then + local joiner = ({ + de = "mit", + })[talk.locale] or "with" + info_line = info_line .. " " .. joiner .. " " .. table.concat(talk.persons, ", ") + end + + local info_lines = wrap( + info_line, + font_text, info_size, NATIVE_WIDTH - col2 - PADDING + ) + + if y + #title_lines * TALK_FONT_SIZE + 3 + #info_lines * info_size > NATIVE_HEIGHT then + break + end + + -- time + local talk_time + local delta = talk.start_ts - time + if delta > -60 and delta < 60 then + talk_time = "Now" + elseif delta > 30*60 then + talk_time = talk.start_str + elseif delta > 0 then + talk_time = string.format("in %d min", math.floor(delta/60)+1) + else + talk_time = string.format("%d min ago", math.ceil(-delta/60)) + end + local time_width = font_text:width(talk_time, time_size) + font_text:write(col2 - 15 - PADDING - time_width, y, talk_time, time_size, 1, 1, 1, 1) + + -- track + if talk.track ~= json.null and talk.track.color ~= json.null then + local r,g,b = parse_rgb(talk.track.color) + resource.create_colored_texture(r,g,b,1):draw(col2 - 5 - PADDING, y, col2 - 10, y + #title_lines*TALK_FONT_SIZE + 3 + #info_lines*info_size) + end + + -- title + for idx = 1, #title_lines do + font_talk:write(col2, y, title_lines[idx], TALK_FONT_SIZE, 1, 1, 1, 1) + y = y + TALK_FONT_SIZE + end + y = y + 3 + + -- info + for idx = 1, #info_lines do + font_text:write(col2, y, info_lines[idx], info_size, 1, 1, 1, .8) + y = y + info_size + end + y = y + PADDING + end end