add all crates: domain, protocol, application, client, adapters, ESP32 firmware
Server: domain (entities, value objects, ports), protocol (postcard wire types), application (config service, data projection), adapters (config-memory, tcp-server), bootstrap (composition root with fake data). Client: client-domain (layout engine, render tree, HAL ports), client-application (message handling, repaint commands), adapters (tcp-client, display-terminal), client-desktop (end-to-end working). ESP32: client-esp32 firmware with ILI9341 display over SPI, WiFi networking. Display test verified on hardware — landscape orientation, text rendering works. 60 workspace tests, all passing.
This commit is contained in:
94
crates/bootstrap/src/main.rs
Normal file
94
crates/bootstrap/src/main.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use domain::{
|
||||
ConfigRepository, BroadcastPort,
|
||||
WidgetConfig, DisplayHint, KeyMapping,
|
||||
Layout, LayoutNode, ContainerNode, LayoutChild, Direction, Sizing,
|
||||
Value, WidgetState,
|
||||
};
|
||||
use application::{ConfigService, DataProjection};
|
||||
use config_memory::MemoryConfigStore;
|
||||
use tcp_server::{TcpBroadcaster, TcpEventBus, run_tcp_server};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config_store = Arc::new(MemoryConfigStore::new());
|
||||
let event_bus = Arc::new(TcpEventBus::new(64));
|
||||
let broadcaster = Arc::new(TcpBroadcaster::new(64));
|
||||
|
||||
let service = ConfigService::new(config_store.as_ref(), event_bus.as_ref());
|
||||
|
||||
service.create_widget(WidgetConfig::new(
|
||||
1, "weather".into(), DisplayHint::IconValue, 1,
|
||||
vec![
|
||||
KeyMapping { source_path: "$.temperature".into(), target_key: "value".into() },
|
||||
KeyMapping { source_path: "$.icon".into(), target_key: "icon".into() },
|
||||
],
|
||||
)).await.unwrap();
|
||||
|
||||
service.create_widget(WidgetConfig::new(
|
||||
2, "portfolio".into(), DisplayHint::KeyValue, 2,
|
||||
vec![
|
||||
KeyMapping { source_path: "$.amount".into(), target_key: "value".into() },
|
||||
],
|
||||
)).await.unwrap();
|
||||
|
||||
let layout = Layout {
|
||||
root: LayoutNode::Container(ContainerNode {
|
||||
direction: Direction::Row,
|
||||
gap: 4,
|
||||
padding: 2,
|
||||
children: vec![
|
||||
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(1) },
|
||||
LayoutChild { sizing: Sizing::Flex(1), node: LayoutNode::Leaf(2) },
|
||||
],
|
||||
}),
|
||||
};
|
||||
service.update_layout(layout).await.unwrap();
|
||||
|
||||
let bc = broadcaster.clone();
|
||||
tokio::spawn(async move {
|
||||
run_tcp_server("0.0.0.0:2699", bc).await.unwrap();
|
||||
});
|
||||
|
||||
println!("Server running on :2699");
|
||||
println!("Sending fake data every 3 seconds...");
|
||||
|
||||
let mut projection = DataProjection::new();
|
||||
let mut counter = 0u32;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||
counter += 1;
|
||||
|
||||
let widgets = config_store.list_widgets().await.unwrap();
|
||||
let layout = config_store.get_layout().await.unwrap();
|
||||
|
||||
let weather_data = Value::Object(std::collections::BTreeMap::from([
|
||||
("temperature".into(), Value::String(format!("{}.{}°C", 5 + counter % 10, counter % 10))),
|
||||
("icon".into(), Value::String("sunny".into())),
|
||||
]));
|
||||
|
||||
let portfolio_data = Value::Object(std::collections::BTreeMap::from([
|
||||
("amount".into(), Value::String(format!("{}.{} PLN", 100 + counter, counter % 100))),
|
||||
]));
|
||||
|
||||
let changed_weather = projection.apply_poll_result(1, &weather_data, &widgets);
|
||||
let changed_portfolio = projection.apply_poll_result(2, &portfolio_data, &widgets);
|
||||
|
||||
let mut all_changed: Vec<(u16, WidgetState)> = Vec::new();
|
||||
all_changed.extend(changed_weather);
|
||||
all_changed.extend(changed_portfolio);
|
||||
|
||||
if !all_changed.is_empty() {
|
||||
if counter == 1 {
|
||||
if let Some(l) = &layout {
|
||||
broadcaster.push_screen_update(l, &all_changed).await.unwrap();
|
||||
}
|
||||
} else {
|
||||
broadcaster.push_data_update(&all_changed).await.unwrap();
|
||||
}
|
||||
println!("Pushed {} widget updates (tick {counter})", all_changed.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user