internal data sources (clock, static text), connection indicator, rendering fixes
DataSourceConfig refactored to enum: External/Clock/StaticText. Clock generates formatted time via chrono, static text emits configured string. ESP32: connection status indicator (green/red dot bottom-right), per-widget clear before redraw, RenderEvent enum for local + server messages. Polling uses DataUpdate instead of ScreenUpdate to avoid wiping widget state. Empty mappings passthrough raw source data for internal sources.
This commit is contained in:
@@ -19,3 +19,5 @@ anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
dotenvy.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use anyhow::Result;
|
||||
use application::DataProjection;
|
||||
use chrono::Utc;
|
||||
use chrono_tz::Tz;
|
||||
use config_sqlite::SqliteConfigStore;
|
||||
use domain::{
|
||||
BroadcastPort, ConfigRepository, DataSource, DataSourcePort, DataSourceType, Value, WidgetState,
|
||||
BroadcastPort, ConfigRepository, DataSource, DataSourceConfig, DataSourcePort, DataSourceType,
|
||||
Value, WidgetState,
|
||||
};
|
||||
use http_json::HttpJsonAdapter;
|
||||
use media_adapter::MediaAdapter;
|
||||
use rss_adapter::RssAdapter;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tcp_server::TcpBroadcaster;
|
||||
@@ -117,14 +120,6 @@ async fn poll_loop(
|
||||
}
|
||||
};
|
||||
|
||||
let layout = match config.get_layout().await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
warn!(error = %e, "failed to fetch layout");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let changed: Vec<(u16, WidgetState)> = projection
|
||||
.apply_poll_result(source.id, &result, &widgets)
|
||||
.await;
|
||||
@@ -137,9 +132,7 @@ async fn poll_loop(
|
||||
Some((*id, hint, state.clone()))
|
||||
})
|
||||
.collect();
|
||||
if let Some(l) = &layout
|
||||
&& let Err(e) = broadcaster.push_screen_update(l, &with_hints).await
|
||||
{
|
||||
if let Err(e) = broadcaster.push_data_update(&with_hints).await {
|
||||
warn!(error = %e, "failed to push update");
|
||||
}
|
||||
info!(source = %source.name, count = changed.len(), "pushed widget updates");
|
||||
@@ -166,8 +159,33 @@ async fn poll_source(
|
||||
.poll(source)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("{e}")),
|
||||
DataSourceType::Clock => Ok(generate_clock(&source.config)),
|
||||
DataSourceType::StaticText => Ok(generate_static_text(&source.config)),
|
||||
DataSourceType::Webhook => Err(anyhow::anyhow!(
|
||||
"webhook sources are push-based, not polled"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_clock(config: &DataSourceConfig) -> Value {
|
||||
let (fmt, tz_name) = match config {
|
||||
DataSourceConfig::Clock { format, timezone } => (format.as_str(), timezone.as_str()),
|
||||
_ => ("%H:%M:%S", "UTC"),
|
||||
};
|
||||
let tz: Tz = tz_name.parse().unwrap_or(chrono_tz::UTC);
|
||||
let now = Utc::now().with_timezone(&tz);
|
||||
let formatted = now.format(fmt).to_string();
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert("time".into(), Value::String(formatted));
|
||||
Value::Object(map)
|
||||
}
|
||||
|
||||
fn generate_static_text(config: &DataSourceConfig) -> Value {
|
||||
let text = match config {
|
||||
DataSourceConfig::StaticText { text } => text.clone(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let mut map = BTreeMap::new();
|
||||
map.insert("text".into(), Value::String(text));
|
||||
Value::Object(map)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user