diff --git a/flake.lock b/flake.lock index c59cb0a..ece6424 100644 --- a/flake.lock +++ b/flake.lock @@ -54,16 +54,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750259320, - "narHash": "sha256-H8J4H2XCIMEJ5g6fZ179QfQvsc2dUqhqfBjC8RAHNRY=", + "lastModified": 1749995256, + "narHash": "sha256-LEGfcombb0otUf23oAmYCXR4+lMQKa49XmU0G5HItGI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9ba04bda9249d5d5e5238303c9755de5a49a79c5", + "rev": "daa45f10955cc2207ac9c5f0206774d2f757c162", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-25.05", + "ref": "nixos-24.11", "repo": "nixpkgs", "type": "github" } @@ -128,11 +128,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1750379715, - "narHash": "sha256-R7yDQlTHvARic5adjg7e/rT22m0mDME90mEgGM6thic=", + "lastModified": 1750293335, + "narHash": "sha256-qAX+hIcUqVl8BzLHTRBrceF2vGeht+gfhvvUZgKe/98=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "f69e9790793d81a8f32af8426008cb2a3871bad5", + "rev": "3453b39a83069811d077a0bc696849c6907112d5", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 27d553c..8427c41 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; flake-utils.url = "github:/numtide/flake-utils"; zig.url = "github:mitchellh/zig-overlay"; }; diff --git a/src/eink_feed_render/api/efa/models.zig b/src/eink_feed_render/api/efa/models.zig deleted file mode 100644 index 37c51e0..0000000 --- a/src/eink_feed_render/api/efa/models.zig +++ /dev/null @@ -1,90 +0,0 @@ -pub const TransportationProductClass = enum(u32) { - zug = 0, - s_bahn = 1, - u_bahn = 2, - stadtbahn = 3, - strassen_trambahn = 4, - stadtbus = 5, - regionalbus = 6, - schnellbus = 7, - seil_zahnradbahn = 8, - schiff = 9, - anruf_sammel_taxi = 10, - sonstige = 11, - flugzeug = 12, - zug_nv = 13, - zug_fv = 14, - zug_fv_m_zuschlag = 15, - zug_fv_m_spez_fpr = 16, - sev = 17, - zug_shuttle = 18, - buergerbus = 19, - rufbus_liniengebunden = 20, - rufbus = 21, - _, - - pub fn toString(self: TransportationProductClass) ?[]const u8 { - return switch (self) { - .zug => "Zug", - .s_bahn => "S-Bahn", - .u_bahn => "U-Bahn", - .stadtbahn => "Stadtbahn", - .strassen_trambahn => "Straßen-/Trambahn", - .stadtbus => "Stadtbus", - .regionalbus => "Regionalbus", - .schnellbus => "Schnellbus", - .seil_zahnradbahn => "Seil-/Zahnradbahn", - .schiff => "Schiff", - .anruf_sammel_taxi => "Anruf-Sammel-Taxi", - .sonstige => "Sonstige", - .flugzeug => "Flugzeug", - .zug_nv => "Zug (Nahverkehr)", - .zug_fv => "Zug (Fernverkehr)", - .zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag", - .zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr", - .sev => "SEV Schnienenersatzverkehr", - .zug_shuttle => "Zug Shuttle", - .buergerbus => "Bürgerbus", - .rufbus_liniengebunden => "Rufbus (liniengebunden)", - .rufbus => "Rufbus", - _ => null, - }; - } -}; - -pub const Location = struct { - id: []const u8, - name: []const u8, - properties: struct { - platform: ?[]const u8 = null, - }, -}; - -pub const StopEvent = struct { - location: Location, - departureTimePlanned: []const u8, - departureTimeBaseTimetable: []const u8, - departureTimeEstimated: ?[]const u8 = null, - transportation: struct { - name: []const u8, - product: struct { - class: TransportationProductClass, - }, - operator: ?struct { - name: []const u8, - } = null, - destination: struct { - name: []const u8, - }, - }, - hints: ?[]const struct { - content: []const u8, - type: []const u8, - } = null, - isCancelled: bool = false, -}; - -pub const DmResponse = struct { - locations: []const Location, - stopEvents: []const StopEvent, -}; diff --git a/src/eink_feed_render/api/root.zig b/src/eink_feed_render/api/root.zig deleted file mode 100644 index 8d90898..0000000 --- a/src/eink_feed_render/api/root.zig +++ /dev/null @@ -1 +0,0 @@ -pub const efa = @import("efa/root.zig"); diff --git a/src/eink_feed_render/api/efa/root.zig b/src/eink_feed_render/apps/Departures/efa.zig similarity index 61% rename from src/eink_feed_render/api/efa/root.zig rename to src/eink_feed_render/apps/Departures/efa.zig index 0d02df0..b184d70 100644 --- a/src/eink_feed_render/api/efa/root.zig +++ b/src/eink_feed_render/apps/Departures/efa.zig @@ -2,10 +2,99 @@ const std = @import("std"); const escaper = @import("../../escaper.zig"); -pub const models = @import("models.zig"); - const log = std.log.scoped(.efa); +pub const ApiTransportationProductClass = enum(u32) { + zug = 0, + s_bahn = 1, + u_bahn = 2, + stadtbahn = 3, + strassen_trambahn = 4, + stadtbus = 5, + regionalbus = 6, + schnellbus = 7, + seil_zahnradbahn = 8, + schiff = 9, + anruf_sammel_taxi = 10, + sonstige = 11, + flugzeug = 12, + zug_nv = 13, + zug_fv = 14, + zug_fv_m_zuschlag = 15, + zug_fv_m_spez_fpr = 16, + sev = 17, + zug_shuttle = 18, + buergerbus = 19, + rufbus_liniengebunden = 20, + rufbus = 21, + _, + + pub fn toString(self: ApiTransportationProductClass) ?[]const u8 { + return switch (self) { + .zug => "Zug", + .s_bahn => "S-Bahn", + .u_bahn => "U-Bahn", + .stadtbahn => "Stadtbahn", + .strassen_trambahn => "Straßen-/Trambahn", + .stadtbus => "Stadtbus", + .regionalbus => "Regionalbus", + .schnellbus => "Schnellbus", + .seil_zahnradbahn => "Seil-/Zahnradbahn", + .schiff => "Schiff", + .anruf_sammel_taxi => "Anruf-Sammel-Taxi", + .sonstige => "Sonstige", + .flugzeug => "Flugzeug", + .zug_nv => "Zug (Nahverkehr)", + .zug_fv => "Zug (Fernverkehr)", + .zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag", + .zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr", + .sev => "SEV Schnienenersatzverkehr", + .zug_shuttle => "Zug Shuttle", + .buergerbus => "Bürgerbus", + .rufbus_liniengebunden => "Rufbus (liniengebunden)", + .rufbus => "Rufbus", + _ => null, + }; + } +}; + +pub const ApiLocation = struct { + id: []const u8, + name: []const u8, + properties: struct { + platform: ?[]const u8 = null, + }, +}; + +pub const ApiStopEvent = struct { + location: ApiLocation, + departureTimePlanned: []const u8, + departureTimeBaseTimetable: []const u8, + departureTimeEstimated: ?[]const u8 = null, + transportation: struct { + name: []const u8, + product: struct { + class: ApiTransportationProductClass, + }, + operator: ?struct { + name: []const u8, + } = null, + destination: struct { + name: []const u8, + }, + }, + hints: ?[]const struct { + content: []const u8, + type: []const u8, + } = null, + isCancelled: bool = false, +}; + +pub const DmResponse = struct { + locations: []const ApiLocation, + stopEvents: []const ApiStopEvent, +}; + pub const Client = struct { pub const Error = error{ InvalidResponse, @@ -81,7 +170,7 @@ pub const Client = struct { try std.io.getStdOut().writeAll(body); } - pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(models.DmResponse) { + pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(DmResponse) { const escaped_stop_id = try escaper.escapeUriComponent(self.allocator, stop_id); defer if (escaped_stop_id) |s| self.allocator.free(s); const url_stop_id = escaped_stop_id orelse stop_id; @@ -115,7 +204,7 @@ pub const Client = struct { defer self.allocator.free(body); const response = try std.json.parseFromSlice( - models.DmResponse, + DmResponse, self.allocator, body, .{ .allocate = .alloc_always, .ignore_unknown_fields = true }, diff --git a/src/eink_feed_render/apps/Departures/root.zig b/src/eink_feed_render/apps/Departures/root.zig index 460cacc..bb6f620 100644 --- a/src/eink_feed_render/apps/Departures/root.zig +++ b/src/eink_feed_render/apps/Departures/root.zig @@ -5,7 +5,7 @@ const zdt = @import("zdt"); const Dimensions = @import("../../Dimensions.zig"); const renderer = @import("../../renderer.zig"); -pub const api = @import("../../api/root.zig"); +pub const efa = @import("efa.zig"); const template = @import("template/root.zig"); @@ -28,7 +28,7 @@ pub const RenderOptions = struct { show_operator: bool, }; -pub fn render(self: *const Self, efa_dm_resp: *const api.efa.models.DmResponse, options: RenderOptions) ![]const u8 { +pub fn render(self: *const Self, efa_dm_resp: *const efa.DmResponse, options: RenderOptions) ![]const u8 { const now_local = try options.now.tzConvert(.{ .tz = options.tz }); var arena = std.heap.ArenaAllocator.init(self.allocator); diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/boat.png b/src/eink_feed_render/apps/Departures/template/icons/img/boat.png new file mode 100644 index 0000000..f49f410 Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/boat.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/bus.png b/src/eink_feed_render/apps/Departures/template/icons/img/bus.png new file mode 100644 index 0000000..5598cff Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/bus.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/flight.png b/src/eink_feed_render/apps/Departures/template/icons/img/flight.png new file mode 100644 index 0000000..f6416af Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/flight.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png b/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png new file mode 100644 index 0000000..ead5e99 Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png b/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png new file mode 100644 index 0000000..ee12871 Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png b/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png new file mode 100644 index 0000000..bc9b2e9 Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/subway.png b/src/eink_feed_render/apps/Departures/template/icons/img/subway.png new file mode 100644 index 0000000..ad532af Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/subway.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/train.png b/src/eink_feed_render/apps/Departures/template/icons/img/train.png new file mode 100644 index 0000000..d482e8d Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/train.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/tram.png b/src/eink_feed_render/apps/Departures/template/icons/img/tram.png new file mode 100644 index 0000000..718586e Binary files /dev/null and b/src/eink_feed_render/apps/Departures/template/icons/img/tram.png differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/root.zig b/src/eink_feed_render/apps/Departures/template/icons/root.zig index e4e554a..bfd53c1 100644 --- a/src/eink_feed_render/apps/Departures/template/icons/root.zig +++ b/src/eink_feed_render/apps/Departures/template/icons/root.zig @@ -1,31 +1,8 @@ const std = @import("std"); -const api = @import("../../../../api/root.zig"); -const embed = @import("../../../../embed.zig"); +const efa = @import("../../efa.zig"); -pub const data = embed.embedBase64Files( - (struct { - fn inner(comptime path: []const u8) []const u8 { - return @embedFile(path); - } - }).inner, - "image/svg+xml", - "svg", - "svg", - &.{ - "train", - "subway", - "tram", - "bus", - "shuttle", - "gondola", - "flight", - "boat", - "no_transfer", - }, -){}; - -pub fn iconFromTransportationProductClass(class: api.efa.models.TransportationProductClass) ?embed.EncodedFile { +pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductClass) ?[]const u8 { return switch (class) { .zug, .s_bahn, @@ -34,11 +11,11 @@ pub fn iconFromTransportationProductClass(class: api.efa.models.TransportationPr .zug_fv_m_zuschlag, .zug_fv_m_spez_fpr, .zug_shuttle, - => data.train, + => train, - .u_bahn => data.subway, + .u_bahn => subway, - .stadtbahn, .strassen_trambahn => data.tram, + .stadtbahn, .strassen_trambahn => tram, .stadtbus, .regionalbus, @@ -47,18 +24,40 @@ pub fn iconFromTransportationProductClass(class: api.efa.models.TransportationPr .rufbus_liniengebunden, .rufbus, .anruf_sammel_taxi, - => data.bus, + => bus, - .seil_zahnradbahn => data.gondola, + .seil_zahnradbahn => gondola, - .schiff => data.boat, + .schiff => boat, .sonstige => null, - .flugzeug => data.flight, + .flugzeug => flight, - .sev => data.no_transfer, + .sev => no_transfer, _ => null, }; } + +fn encodePng(comptime path: []const u8) []const u8 { + const raw = @embedFile(path); + const len = std.base64.standard.Encoder.calcSize(raw.len); + var buf: [len]u8 = undefined; + { + @setEvalBranchQuota(1_000_000); + _ = std.base64.standard.Encoder.encode(&buf, raw); + } + const final = buf; + return &final; +} + +pub const train = encodePng("img/train.png"); +pub const subway = encodePng("img/subway.png"); +pub const tram = encodePng("img/tram.png"); +pub const bus = encodePng("img/bus.png"); +pub const shuttle = encodePng("img/shuttle.png"); +pub const gondola = encodePng("img/gondola.png"); +pub const flight = encodePng("img/flight.png"); +pub const boat = encodePng("img/boat.png"); +pub const no_transfer = encodePng("img/no_transfer.png"); diff --git a/src/eink_feed_render/apps/Departures/template/root.zig b/src/eink_feed_render/apps/Departures/template/root.zig index 382bcfb..862dc59 100644 --- a/src/eink_feed_render/apps/Departures/template/root.zig +++ b/src/eink_feed_render/apps/Departures/template/root.zig @@ -1,7 +1,7 @@ const std = @import("std"); const escaper = @import("../../../escaper.zig"); -const api = @import("../../../api/root.zig"); +const efa = @import("../efa.zig"); const icons = @import("icons/root.zig"); @@ -9,7 +9,7 @@ pub const Departure = struct { time: []const u8, time_unix: i128, line: []const u8, - transportation_product_class: api.efa.models.TransportationProductClass, + transportation_product_class: efa.ApiTransportationProductClass, operator: ?[]const u8, destination: []const u8, platform: ?[]const u8, @@ -188,16 +188,13 @@ pub fn render(context: *const Context, writer: anytype) !void { ); } - if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |file| { + if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |img| { try writer.print( \\