1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! # trmnl
//!
//! A BYOS (Bring Your Own Server) framework for [TRMNL](https://usetrmnl.com) e-ink displays.
//!
//! TRMNL devices can operate in two modes:
//! - **Cloud mode**: Device polls TRMNL's servers, which poll your webhook
//! - **BYOS mode**: Device polls your server directly (this crate's focus)
//!
//! This crate provides everything you need to build a BYOS server:
//! - Protocol types that match firmware expectations
//! - Device info extraction from HTTP headers
//! - Optional axum integration for quick setup
//! - Optional HTML-to-PNG rendering via Chrome headless
//!
//! ## Quick Start (axum)
//!
//! ```rust,ignore
//! use axum::{Router, routing::get};
//! use trmnl::{DisplayResponse, DeviceInfo};
//!
//! async fn display(device: DeviceInfo) -> axum::Json<DisplayResponse> {
//! println!("Request from device: {}", device.mac_address);
//!
//! axum::Json(DisplayResponse::new(
//! "https://example.com/screen.png",
//! "screen.png",
//! ))
//! }
//!
//! let app = Router::new()
//! .route("/api/display", get(display));
//! ```
//!
//! ## Display Dimensions
//!
//! TRMNL displays are 800x480 pixels. Images must be:
//! - Exactly 800x480 PNG
//! - Under 90KB (firmware rejects larger files)
//! - 16 colors or less for optimal e-ink rendering
//!
//! ## BYOS Protocol
//!
//! Your server must implement these endpoints:
//!
//! | Endpoint | Method | Purpose |
//! |----------|--------|---------|
//! | `/api/setup` | GET | Device registration (optional) |
//! | `/api/display` | GET | Returns image URL and metadata |
//! | `/api/log` | POST | Receives device telemetry (optional) |
//!
//! The device sends these headers:
//! - `ID`: Device MAC address
//! - `Battery-Voltage`: Battery voltage (e.g., "4.2")
//! - `FW-Version`: Firmware version
//! - `RSSI`: WiFi signal strength
//! - `Refresh-Rate`: Current refresh rate
//!
//! ## Feature Flags
//!
//! - `axum` - Axum extractors and handlers
//! - `render` - HTML to PNG rendering via Chrome headless
//! - `schedule` - Time-based refresh rate scheduling (YAML config)
//! - `full` - All features
pub use TokenAuth;
pub use ;
pub use Error;
/// TRMNL display width in pixels
pub const DISPLAY_WIDTH: u32 = 800;
/// TRMNL display height in pixels
pub const DISPLAY_HEIGHT: u32 = 480;
/// Maximum image size in bytes (firmware rejects larger)
pub const MAX_IMAGE_SIZE: usize = 90 * 1024; // 90KB
/// LiPo battery minimum voltage (0%)
pub const BATTERY_MIN_MV: u32 = 3000;
/// LiPo battery maximum voltage (100%)
pub const BATTERY_MAX_MV: u32 = 4200;
// Optional modules
pub use ;
pub use ;
// Re-export axum integration
/// Convert battery voltage (in millivolts) to percentage.
///
/// Uses standard LiPo voltage curve: 3.0V (0%) to 4.2V (100%).
///
/// # Example
///
/// ```
/// use trmnl::battery_percentage;
///
/// assert_eq!(battery_percentage(4200), 100);
/// assert_eq!(battery_percentage(3600), 50);
/// assert_eq!(battery_percentage(3000), 0);
/// ```