diff --git a/Cargo.lock b/Cargo.lock
index 22271ef..01aec1e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -397,6 +397,15 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b"
+[[package]]
+name = "chrono"
+version = "0.4.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ciborium"
version = "0.2.2"
@@ -2628,6 +2637,7 @@ dependencies = [
"serde",
"showbits-assets",
"showbits-typst",
+ "sunrise",
"tokio",
]
@@ -2782,6 +2792,15 @@ dependencies = [
"syn",
]
+[[package]]
+name = "sunrise"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3217c5830416956b1f2dc731f526150a82c144ebe83d2f0e78853c8356a22ada"
+dependencies = [
+ "chrono",
+]
+
[[package]]
name = "svgtypes"
version = "0.15.3"
diff --git a/Cargo.toml b/Cargo.toml
index 6b95eb5..f029ab9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ rust-embed = "8.6.0"
serde = { version = "1.0.218", features = ["derive"] }
showbits-assets.path = "./showbits-assets"
showbits-typst.path = "./showbits-typst"
+sunrise = "1.0.1"
tokio = "1.43.0"
typst = "0.13.0"
typst-assets = { version = "0.13.0", features = ["fonts"] }
diff --git a/showbits-thermal-printer-ui/src/components/CDocumentSunrise.vue b/showbits-thermal-printer-ui/src/components/CDocumentSunrise.vue
new file mode 100644
index 0000000..321d903
--- /dev/null
+++ b/showbits-thermal-printer-ui/src/components/CDocumentSunrise.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
diff --git a/showbits-thermal-printer-ui/src/components/CSelector.vue b/showbits-thermal-printer-ui/src/components/CSelector.vue
index ded5dc5..0dd9383 100644
--- a/showbits-thermal-printer-ui/src/components/CSelector.vue
+++ b/showbits-thermal-printer-ui/src/components/CSelector.vue
@@ -5,11 +5,19 @@ import CDocumentCells from "./CDocumentCells.vue";
import CDocumentChat from "./CDocumentChat.vue";
import CDocumentEgg from "./CDocumentEgg.vue";
import CDocumentImage from "./CDocumentImage.vue";
+import CDocumentSunrise from "./CDocumentSunrise.vue";
import CDocumentText from "./CDocumentText.vue";
import CDocumentTictactoe from "./CDocumentTictactoe.vue";
const mode = ref<
- "calendar" | "chat" | "cells" | "egg" | "image" | "text" | "tictactoe"
+ | "calendar"
+ | "cells"
+ | "chat"
+ | "egg"
+ | "image"
+ | "sunrise"
+ | "text"
+ | "tictactoe"
>();
@@ -17,10 +25,11 @@ const mode = ref<
-
+
+
@@ -32,6 +41,7 @@ const mode = ref<
+
Take a photo
diff --git a/showbits-thermal-printer/Cargo.toml b/showbits-thermal-printer/Cargo.toml
index f833909..c3ba575 100644
--- a/showbits-thermal-printer/Cargo.toml
+++ b/showbits-thermal-printer/Cargo.toml
@@ -18,6 +18,7 @@ rust-embed = { workspace = true }
serde = { workspace = true }
showbits-assets = { workspace = true }
showbits-typst = { workspace = true }
+sunrise = { workspace = true }
tokio = { workspace = true, features = ["full"] }
[lints]
diff --git a/showbits-thermal-printer/src/documents.rs b/showbits-thermal-printer/src/documents.rs
index 7584b72..9c78bd7 100644
--- a/showbits-thermal-printer/src/documents.rs
+++ b/showbits-thermal-printer/src/documents.rs
@@ -5,6 +5,7 @@ pub mod cells;
pub mod chat;
pub mod egg;
pub mod image;
+pub mod sunrise;
pub mod text;
pub mod tictactoe;
diff --git a/showbits-thermal-printer/src/documents/sunrise/data.json b/showbits-thermal-printer/src/documents/sunrise/data.json
new file mode 100644
index 0000000..5aa1790
--- /dev/null
+++ b/showbits-thermal-printer/src/documents/sunrise/data.json
@@ -0,0 +1,38 @@
+{
+ "year": 2025,
+ "month": 3,
+ "times": [
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"],
+ ["01:23", "45:67"]
+ ],
+ "feed": false
+}
diff --git a/showbits-thermal-printer/src/documents/sunrise/lib b/showbits-thermal-printer/src/documents/sunrise/lib
new file mode 120000
index 0000000..dc598c5
--- /dev/null
+++ b/showbits-thermal-printer/src/documents/sunrise/lib
@@ -0,0 +1 @@
+../lib
\ No newline at end of file
diff --git a/showbits-thermal-printer/src/documents/sunrise/main.typ b/showbits-thermal-printer/src/documents/sunrise/main.typ
new file mode 100644
index 0000000..b9eca3d
--- /dev/null
+++ b/showbits-thermal-printer/src/documents/sunrise/main.typ
@@ -0,0 +1,46 @@
+#import "@preview/oxifmt:0.2.1": strfmt
+#import "lib/main.typ" as lib;
+#show: it => lib.init(it)
+
+#let data = json("data.json")
+#let date = datetime(year: data.year, month: data.month, day: 1)
+
+#let month_length = 32 - (date + duration(days: 31)).day()
+
+#let head(name) = text(size: 32pt, name)
+#let empty = box()
+#let day(content) = box(
+ width: 100%,
+ height: 100%,
+ stroke: 2pt + black,
+ content,
+)
+
+#align(center + horizon)[
+ #set par(spacing: 8pt)
+
+ Sonnenauf- und Untergang
+ #strfmt("{:04}-{:02}", date.year(), date.month())
+
+ #set par(leading: 5pt)
+ #grid(
+ columns: (50pt,) * 7,
+ rows: 50pt,
+ gutter: 4pt,
+ head[Mo], head[Di], head[Mi], head[Do], head[Fr], head[Sa], head[So],
+ ..for _ in range(date.weekday() - 1) { (empty,) },
+ ..for (i, (sunrise, sunset)) in data.times.enumerate() {
+ (
+ day[
+ #strfmt("{:02}", i + 1) \
+ #sunrise \
+ #sunset
+ ],
+ )
+ },
+ )
+]
+
+#if data.feed {
+ lib.feed
+}
diff --git a/showbits-thermal-printer/src/documents/sunrise/mod.rs b/showbits-thermal-printer/src/documents/sunrise/mod.rs
new file mode 100644
index 0000000..089b250
--- /dev/null
+++ b/showbits-thermal-printer/src/documents/sunrise/mod.rs
@@ -0,0 +1,57 @@
+use axum::{Form, extract::State};
+use jiff::{Timestamp, ToSpan, Zoned, civil};
+use serde::{Deserialize, Serialize};
+
+use crate::server::{Server, somehow};
+
+#[derive(Serialize)]
+struct Data {
+ year: i16,
+ month: i8,
+ times: Vec<(String, String)>,
+ feed: bool,
+}
+
+#[derive(Deserialize)]
+pub struct FormData {
+ pub latitude: f64,
+ pub longitude: f64,
+ pub year: Option
,
+ pub month: Option,
+ pub feed: Option,
+}
+
+pub async fn post(server: State, Form(form): Form) -> somehow::Result<()> {
+ let now = Zoned::now().date();
+ let year = form.year.unwrap_or(now.year());
+ let month = form.month.unwrap_or(now.month());
+
+ let first = civil::Date::new(year, month, 1)?;
+ let mut times = vec![];
+ for day in 1..=first.days_in_month() {
+ let date = first + day.days();
+ let (rise, set) = sunrise::sunrise_sunset(
+ form.latitude,
+ form.longitude,
+ date.year() as i32,
+ date.month() as u32,
+ date.day() as u32,
+ );
+ let rise = Timestamp::new(rise, 0)?.strftime("%H:%M").to_string();
+ let set = Timestamp::new(set, 0)?.strftime("%H:%M").to_string();
+ times.push((rise, set));
+ }
+
+ let data = Data {
+ year,
+ month,
+ times,
+ feed: form.feed.unwrap_or(true),
+ };
+
+ let typst = super::typst_with_lib()
+ .with_json("/data.json", &data)
+ .with_main_file(include_str!("main.typ"));
+
+ server.print_typst(typst).await
+}
diff --git a/showbits-thermal-printer/src/server.rs b/showbits-thermal-printer/src/server.rs
index 8eb46f7..96a772f 100644
--- a/showbits-thermal-printer/src/server.rs
+++ b/showbits-thermal-printer/src/server.rs
@@ -41,6 +41,7 @@ pub async fn run(tx: mpsc::Sender, addr: String) -> anyhow::Result<()>
.route("/api/chat", post(documents::chat::post))
.route("/api/egg", post(documents::egg::post))
.route("/api/image", post(documents::image::post))
+ .route("/api/sunrise", post(documents::sunrise::post))
.route("/api/text", post(documents::text::post))
.route("/api/tictactoe", post(documents::tictactoe::post))
// Rest