Der nächste Layer ist jetzt als echte gemeinsame Außenkante umgesetzt. Das neue API-Crate in [server.rs](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/crates/infinity_host_api/src/server.rs>), [dto.rs](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/crates/infinity_host_api/src/dto.rs>) und [main.rs](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/crates/infinity_host_api/src/main.rs>) stellt die geforderten versionierten Endpunkte bereit: GET /api/v1/snapshot, GET /api/v1/catalog, GET /api/v1/presets, GET /api/v1/groups, POST /api/v1/command und WS /api/v1/stream. Die API trennt jetzt sauber zwischen Command-, State-, Preview- und Event-Modell, inklusive stabiler Außen-Enums und dokumentierten Fehlerobjekten statt eines unreflektierten 1:1-Core-Leaks.
Darauf sitzt die erste Web-UI V1 direkt gegen diese API in [index.html](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/web/v1/index.html>), [app.js](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/web/v1/app.js>) und [styles.css](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/web/v1/styles.css>). Sie enthält Pattern-/Preset-Auswahl, globale Regler, Gruppenauswahl, Übergänge, Preview, Snapshot-Anzeige und Event-Feed. Die technische Desktop-GUI bleibt bewusst die Engineering-/Diagnoseoberfläche; die kreative Web-UI baut nicht an einer Parallelarchitektur vorbei. Contract-Tests liegen in [contract.rs](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/crates/infinity_host_api/tests/contract.rs>), und die API ist mit Beispiel-Responses in [docs/host_api.md](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/docs/host_api.md>) dokumentiert. Zusätzlich habe ich [README.md](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/README.md>), [docs/build_and_deploy.md](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/docs/build_and_deploy.md>) und [docs/architecture.md](</C:/Users/janni/Documents/RFP/Infinity_Vis _Rust/docs/architecture.md>) auf den neuen Stand gebracht. Verifiziert habe ich: - `cargo check -p infinity_host_api` - `cargo test -p infinity_host_api` - `cargo test -q` Nicht verifiziert habe ich eine separate JS-Syntaxprüfung mit `node --check`, weil `node` in dieser Umgebung nicht installiert ist.
This commit is contained in:
745
crates/infinity_host_api/src/dto.rs
Normal file
745
crates/infinity_host_api/src/dto.rs
Normal file
@@ -0,0 +1,745 @@
|
||||
use infinity_config::{ColorOrder, LedDirection, PanelPosition, ValidationState};
|
||||
use infinity_host::{
|
||||
HostCommand, HostSnapshot, NodeConnectionState, PreviewSource, SceneParameterKind,
|
||||
SceneParameterValue, SceneTransitionStyle, TestPatternKind,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const API_VERSION: &str = "v1";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiSnapshotResponse {
|
||||
pub api_version: &'static str,
|
||||
pub generated_at_millis: u64,
|
||||
pub state: ApiStateSnapshot,
|
||||
pub preview: ApiPreviewSnapshot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiCatalogResponse {
|
||||
pub api_version: &'static str,
|
||||
pub patterns: Vec<ApiPatternCatalogEntry>,
|
||||
pub presets: Vec<ApiPresetSummary>,
|
||||
pub groups: Vec<ApiGroupSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPresetListResponse {
|
||||
pub api_version: &'static str,
|
||||
pub presets: Vec<ApiPresetSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiGroupListResponse {
|
||||
pub api_version: &'static str,
|
||||
pub groups: Vec<ApiGroupSummary>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiCommandRequest {
|
||||
#[serde(default)]
|
||||
pub request_id: Option<String>,
|
||||
pub command: ApiCommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiCommandResponse {
|
||||
pub api_version: &'static str,
|
||||
pub accepted: bool,
|
||||
pub request_id: Option<String>,
|
||||
pub generated_at_millis: u64,
|
||||
pub summary: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiErrorResponse {
|
||||
pub api_version: &'static str,
|
||||
pub error: ApiErrorBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiErrorBody {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiStateSnapshot {
|
||||
pub system: ApiSystemInfo,
|
||||
pub global: ApiGlobalState,
|
||||
pub engine: ApiEngineState,
|
||||
pub active_scene: ApiActiveScene,
|
||||
pub nodes: Vec<ApiNodeStatus>,
|
||||
pub panels: Vec<ApiPanelStatus>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPreviewSnapshot {
|
||||
pub generated_at_millis: u64,
|
||||
pub panels: Vec<ApiPreviewPanel>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiStreamEnvelope {
|
||||
pub api_version: &'static str,
|
||||
pub sequence: u64,
|
||||
pub generated_at_millis: u64,
|
||||
pub message: ApiStreamMessage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case", tag = "type", content = "payload")]
|
||||
pub enum ApiStreamMessage {
|
||||
Snapshot(ApiStateSnapshot),
|
||||
Preview(ApiPreviewSnapshot),
|
||||
Event(ApiEventNotice),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiEventNotice {
|
||||
pub kind: ApiEventKind,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiEventKind {
|
||||
Info,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiSystemInfo {
|
||||
pub project_name: String,
|
||||
pub schema_version: u32,
|
||||
pub topology_label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiGlobalState {
|
||||
pub blackout: bool,
|
||||
pub master_brightness: f32,
|
||||
pub selected_pattern: String,
|
||||
pub selected_group: Option<String>,
|
||||
pub transition_duration_ms: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiEngineState {
|
||||
pub logic_hz: u16,
|
||||
pub frame_hz: u16,
|
||||
pub preview_hz: u16,
|
||||
pub uptime_ms: u64,
|
||||
pub frame_index: u64,
|
||||
pub dropped_frames: u64,
|
||||
pub active_transition: Option<ApiTransitionState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiTransitionState {
|
||||
pub style: ApiTransitionStyle,
|
||||
pub from_pattern_id: String,
|
||||
pub to_pattern_id: String,
|
||||
pub duration_ms: u32,
|
||||
pub progress: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiTransitionStyle {
|
||||
Snap,
|
||||
Crossfade,
|
||||
Chase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiActiveScene {
|
||||
pub preset_id: Option<String>,
|
||||
pub pattern_id: String,
|
||||
pub seed: u64,
|
||||
pub palette: Vec<String>,
|
||||
pub parameters: Vec<ApiSceneParameter>,
|
||||
pub target_group: Option<String>,
|
||||
pub blackout: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiSceneParameter {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub kind: ApiParameterKind,
|
||||
pub value: ApiParameterValue,
|
||||
pub min_scalar: Option<f32>,
|
||||
pub max_scalar: Option<f32>,
|
||||
pub step: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPatternCatalogEntry {
|
||||
pub pattern_id: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub parameters: Vec<ApiPatternParameter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPatternParameter {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub kind: ApiParameterKind,
|
||||
pub min_scalar: Option<f32>,
|
||||
pub max_scalar: Option<f32>,
|
||||
pub step: Option<f32>,
|
||||
pub default_value: ApiParameterValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPresetSummary {
|
||||
pub preset_id: String,
|
||||
pub pattern_id: String,
|
||||
pub target_group: Option<String>,
|
||||
pub transition_duration_ms: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiGroupSummary {
|
||||
pub group_id: String,
|
||||
pub member_count: usize,
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiNodeStatus {
|
||||
pub node_id: String,
|
||||
pub display_name: String,
|
||||
pub reserved_ip: Option<String>,
|
||||
pub connection: ApiConnectionState,
|
||||
pub last_contact_ms: u64,
|
||||
pub error_status: Option<String>,
|
||||
pub panel_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPanelStatus {
|
||||
pub node_id: String,
|
||||
pub panel_position: ApiPanelPosition,
|
||||
pub physical_output_name: String,
|
||||
pub driver_reference: String,
|
||||
pub led_count: u16,
|
||||
pub direction: ApiLedDirection,
|
||||
pub color_order: ApiColorOrder,
|
||||
pub enabled: bool,
|
||||
pub validation_state: ApiValidationState,
|
||||
pub connection: ApiConnectionState,
|
||||
pub last_test_trigger_ms: Option<u64>,
|
||||
pub error_status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ApiPreviewPanel {
|
||||
pub node_id: String,
|
||||
pub panel_position: ApiPanelPosition,
|
||||
pub representative_color_hex: String,
|
||||
pub sample_led_hex: Vec<String>,
|
||||
pub energy_percent: u8,
|
||||
pub source: ApiPreviewSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiPanelPosition {
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiConnectionState {
|
||||
Online,
|
||||
Degraded,
|
||||
Offline,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiPreviewSource {
|
||||
Scene,
|
||||
Transition,
|
||||
PanelTest,
|
||||
Blackout,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiParameterKind {
|
||||
Scalar,
|
||||
Toggle,
|
||||
Text,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiLedDirection {
|
||||
Forward,
|
||||
Reverse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiColorOrder {
|
||||
Rgb,
|
||||
Rbg,
|
||||
Grb,
|
||||
Gbr,
|
||||
Brg,
|
||||
Bgr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiValidationState {
|
||||
PendingHardwareValidation,
|
||||
Validated,
|
||||
Retired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
|
||||
pub enum ApiParameterValue {
|
||||
Scalar(f32),
|
||||
Toggle(bool),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case", tag = "type", content = "payload")]
|
||||
pub enum ApiCommand {
|
||||
SetBlackout {
|
||||
enabled: bool,
|
||||
},
|
||||
SetMasterBrightness {
|
||||
value: f32,
|
||||
},
|
||||
SelectPattern {
|
||||
pattern_id: String,
|
||||
},
|
||||
RecallPreset {
|
||||
preset_id: String,
|
||||
},
|
||||
SelectGroup {
|
||||
group_id: Option<String>,
|
||||
},
|
||||
SetSceneParameter {
|
||||
key: String,
|
||||
value: ApiParameterValue,
|
||||
},
|
||||
SetTransitionDurationMs {
|
||||
duration_ms: u32,
|
||||
},
|
||||
TriggerPanelTest {
|
||||
node_id: String,
|
||||
panel_position: ApiPanelPosition,
|
||||
pattern: ApiTestPattern,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApiTestPattern {
|
||||
WalkingPixel106,
|
||||
}
|
||||
|
||||
impl ApiSnapshotResponse {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
let state = ApiStateSnapshot::from_snapshot(snapshot);
|
||||
let preview = ApiPreviewSnapshot::from_snapshot(snapshot);
|
||||
Self {
|
||||
api_version: API_VERSION,
|
||||
generated_at_millis: snapshot.generated_at_millis,
|
||||
state,
|
||||
preview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiCatalogResponse {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
Self {
|
||||
api_version: API_VERSION,
|
||||
patterns: snapshot
|
||||
.catalog
|
||||
.patterns
|
||||
.iter()
|
||||
.map(|pattern| ApiPatternCatalogEntry {
|
||||
pattern_id: pattern.pattern_id.clone(),
|
||||
display_name: pattern.display_name.clone(),
|
||||
description: pattern.description.clone(),
|
||||
parameters: pattern
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|parameter| ApiPatternParameter {
|
||||
key: parameter.key.clone(),
|
||||
label: parameter.label.clone(),
|
||||
kind: map_parameter_kind(parameter.kind),
|
||||
min_scalar: parameter.min_scalar,
|
||||
max_scalar: parameter.max_scalar,
|
||||
step: parameter.step,
|
||||
default_value: map_parameter_value(¶meter.default_value),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
presets: snapshot
|
||||
.catalog
|
||||
.presets
|
||||
.iter()
|
||||
.map(|preset| ApiPresetSummary {
|
||||
preset_id: preset.preset_id.clone(),
|
||||
pattern_id: preset.pattern_id.clone(),
|
||||
target_group: preset.target_group.clone(),
|
||||
transition_duration_ms: preset.transition_duration_ms,
|
||||
})
|
||||
.collect(),
|
||||
groups: snapshot
|
||||
.catalog
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| ApiGroupSummary {
|
||||
group_id: group.group_id.clone(),
|
||||
member_count: group.member_count,
|
||||
tags: group.tags.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiPresetListResponse {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
Self {
|
||||
api_version: API_VERSION,
|
||||
presets: ApiCatalogResponse::from_snapshot(snapshot).presets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiGroupListResponse {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
Self {
|
||||
api_version: API_VERSION,
|
||||
groups: ApiCatalogResponse::from_snapshot(snapshot).groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiStateSnapshot {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
Self {
|
||||
system: ApiSystemInfo {
|
||||
project_name: snapshot.system.project_name.clone(),
|
||||
schema_version: snapshot.system.schema_version,
|
||||
topology_label: snapshot.system.topology_label.clone(),
|
||||
},
|
||||
global: ApiGlobalState {
|
||||
blackout: snapshot.global.blackout,
|
||||
master_brightness: snapshot.global.master_brightness,
|
||||
selected_pattern: snapshot.global.selected_pattern.clone(),
|
||||
selected_group: snapshot.global.selected_group.clone(),
|
||||
transition_duration_ms: snapshot.global.transition_duration_ms,
|
||||
},
|
||||
engine: ApiEngineState {
|
||||
logic_hz: snapshot.engine.logic_hz,
|
||||
frame_hz: snapshot.engine.frame_hz,
|
||||
preview_hz: snapshot.engine.preview_hz,
|
||||
uptime_ms: snapshot.engine.uptime_ms,
|
||||
frame_index: snapshot.engine.frame_index,
|
||||
dropped_frames: snapshot.engine.dropped_frames,
|
||||
active_transition: snapshot.engine.active_transition.as_ref().map(|transition| {
|
||||
ApiTransitionState {
|
||||
style: map_transition_style(transition.style),
|
||||
from_pattern_id: transition.from_pattern_id.clone(),
|
||||
to_pattern_id: transition.to_pattern_id.clone(),
|
||||
duration_ms: transition.duration_ms,
|
||||
progress: transition.progress,
|
||||
}
|
||||
}),
|
||||
},
|
||||
active_scene: ApiActiveScene {
|
||||
preset_id: snapshot.active_scene.preset_id.clone(),
|
||||
pattern_id: snapshot.active_scene.pattern_id.clone(),
|
||||
seed: snapshot.active_scene.seed,
|
||||
palette: snapshot.active_scene.palette.clone(),
|
||||
parameters: snapshot
|
||||
.active_scene
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|parameter| ApiSceneParameter {
|
||||
key: parameter.key.clone(),
|
||||
label: parameter.label.clone(),
|
||||
kind: map_parameter_kind(parameter.kind),
|
||||
value: map_parameter_value(¶meter.value),
|
||||
min_scalar: parameter.min_scalar,
|
||||
max_scalar: parameter.max_scalar,
|
||||
step: parameter.step,
|
||||
})
|
||||
.collect(),
|
||||
target_group: snapshot.active_scene.target_group.clone(),
|
||||
blackout: snapshot.active_scene.blackout,
|
||||
},
|
||||
nodes: snapshot
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|node| ApiNodeStatus {
|
||||
node_id: node.node_id.clone(),
|
||||
display_name: node.display_name.clone(),
|
||||
reserved_ip: node.reserved_ip.clone(),
|
||||
connection: map_connection_state(node.connection),
|
||||
last_contact_ms: node.last_contact_ms,
|
||||
error_status: node.error_status.clone(),
|
||||
panel_count: node.panel_count,
|
||||
})
|
||||
.collect(),
|
||||
panels: snapshot
|
||||
.panels
|
||||
.iter()
|
||||
.map(|panel| ApiPanelStatus {
|
||||
node_id: panel.target.node_id.clone(),
|
||||
panel_position: map_panel_position(&panel.target.panel_position),
|
||||
physical_output_name: panel.physical_output_name.clone(),
|
||||
driver_reference: panel.driver_reference.clone(),
|
||||
led_count: panel.led_count,
|
||||
direction: map_led_direction(panel.direction.clone()),
|
||||
color_order: map_color_order(panel.color_order.clone()),
|
||||
enabled: panel.enabled,
|
||||
validation_state: map_validation_state(panel.validation_state.clone()),
|
||||
connection: map_connection_state(panel.connection),
|
||||
last_test_trigger_ms: panel.last_test_trigger_ms,
|
||||
error_status: panel.error_status.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiPreviewSnapshot {
|
||||
pub fn from_snapshot(snapshot: &HostSnapshot) -> Self {
|
||||
Self {
|
||||
generated_at_millis: snapshot.generated_at_millis,
|
||||
panels: snapshot
|
||||
.preview
|
||||
.panels
|
||||
.iter()
|
||||
.map(|panel| ApiPreviewPanel {
|
||||
node_id: panel.target.node_id.clone(),
|
||||
panel_position: map_panel_position(&panel.target.panel_position),
|
||||
representative_color_hex: panel.representative_color_hex.clone(),
|
||||
sample_led_hex: panel.sample_led_hex.clone(),
|
||||
energy_percent: panel.energy_percent,
|
||||
source: map_preview_source(panel.preview_source),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiCommandRequest {
|
||||
pub fn into_host_command(self) -> Result<HostCommand, String> {
|
||||
match self.command {
|
||||
ApiCommand::SetBlackout { enabled } => Ok(HostCommand::SetBlackout(enabled)),
|
||||
ApiCommand::SetMasterBrightness { value } => {
|
||||
Ok(HostCommand::SetMasterBrightness(value))
|
||||
}
|
||||
ApiCommand::SelectPattern { pattern_id } => {
|
||||
Ok(HostCommand::SelectPattern(pattern_id))
|
||||
}
|
||||
ApiCommand::RecallPreset { preset_id } => {
|
||||
Ok(HostCommand::RecallPreset { preset_id })
|
||||
}
|
||||
ApiCommand::SelectGroup { group_id } => {
|
||||
Ok(HostCommand::SelectGroup { group_id })
|
||||
}
|
||||
ApiCommand::SetSceneParameter { key, value } => Ok(HostCommand::SetSceneParameter {
|
||||
key,
|
||||
value: map_command_parameter_value(value),
|
||||
}),
|
||||
ApiCommand::SetTransitionDurationMs { duration_ms } => {
|
||||
Ok(HostCommand::SetTransitionDurationMs(duration_ms))
|
||||
}
|
||||
ApiCommand::TriggerPanelTest {
|
||||
node_id,
|
||||
panel_position,
|
||||
pattern,
|
||||
} => Ok(HostCommand::TriggerPanelTest {
|
||||
target: infinity_host::PanelTarget {
|
||||
node_id,
|
||||
panel_position: map_command_panel_position(panel_position),
|
||||
},
|
||||
pattern: match pattern {
|
||||
ApiTestPattern::WalkingPixel106 => TestPatternKind::WalkingPixel106,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn summary(&self) -> String {
|
||||
self.command.summary()
|
||||
}
|
||||
}
|
||||
|
||||
fn map_panel_position(position: &PanelPosition) -> ApiPanelPosition {
|
||||
match position {
|
||||
PanelPosition::Top => ApiPanelPosition::Top,
|
||||
PanelPosition::Middle => ApiPanelPosition::Middle,
|
||||
PanelPosition::Bottom => ApiPanelPosition::Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_command_panel_position(position: ApiPanelPosition) -> PanelPosition {
|
||||
match position {
|
||||
ApiPanelPosition::Top => PanelPosition::Top,
|
||||
ApiPanelPosition::Middle => PanelPosition::Middle,
|
||||
ApiPanelPosition::Bottom => PanelPosition::Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_connection_state(state: NodeConnectionState) -> ApiConnectionState {
|
||||
match state {
|
||||
NodeConnectionState::Online => ApiConnectionState::Online,
|
||||
NodeConnectionState::Degraded => ApiConnectionState::Degraded,
|
||||
NodeConnectionState::Offline => ApiConnectionState::Offline,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_led_direction(direction: LedDirection) -> ApiLedDirection {
|
||||
match direction {
|
||||
LedDirection::Forward => ApiLedDirection::Forward,
|
||||
LedDirection::Reverse => ApiLedDirection::Reverse,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_color_order(color_order: ColorOrder) -> ApiColorOrder {
|
||||
match color_order {
|
||||
ColorOrder::Rgb => ApiColorOrder::Rgb,
|
||||
ColorOrder::Rbg => ApiColorOrder::Rbg,
|
||||
ColorOrder::Grb => ApiColorOrder::Grb,
|
||||
ColorOrder::Gbr => ApiColorOrder::Gbr,
|
||||
ColorOrder::Brg => ApiColorOrder::Brg,
|
||||
ColorOrder::Bgr => ApiColorOrder::Bgr,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_validation_state(state: ValidationState) -> ApiValidationState {
|
||||
match state {
|
||||
ValidationState::PendingHardwareValidation => ApiValidationState::PendingHardwareValidation,
|
||||
ValidationState::Validated => ApiValidationState::Validated,
|
||||
ValidationState::Retired => ApiValidationState::Retired,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_preview_source(source: PreviewSource) -> ApiPreviewSource {
|
||||
match source {
|
||||
PreviewSource::Scene => ApiPreviewSource::Scene,
|
||||
PreviewSource::Transition => ApiPreviewSource::Transition,
|
||||
PreviewSource::PanelTest => ApiPreviewSource::PanelTest,
|
||||
PreviewSource::Blackout => ApiPreviewSource::Blackout,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_transition_style(style: SceneTransitionStyle) -> ApiTransitionStyle {
|
||||
match style {
|
||||
SceneTransitionStyle::Snap => ApiTransitionStyle::Snap,
|
||||
SceneTransitionStyle::Crossfade => ApiTransitionStyle::Crossfade,
|
||||
SceneTransitionStyle::Chase => ApiTransitionStyle::Chase,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_parameter_kind(kind: SceneParameterKind) -> ApiParameterKind {
|
||||
match kind {
|
||||
SceneParameterKind::Scalar => ApiParameterKind::Scalar,
|
||||
SceneParameterKind::Toggle => ApiParameterKind::Toggle,
|
||||
SceneParameterKind::Text => ApiParameterKind::Text,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_parameter_value(value: &SceneParameterValue) -> ApiParameterValue {
|
||||
match value {
|
||||
SceneParameterValue::Scalar(value) => ApiParameterValue::Scalar(*value),
|
||||
SceneParameterValue::Toggle(value) => ApiParameterValue::Toggle(*value),
|
||||
SceneParameterValue::Text(value) => ApiParameterValue::Text(value.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_command_parameter_value(value: ApiParameterValue) -> SceneParameterValue {
|
||||
match value {
|
||||
ApiParameterValue::Scalar(value) => SceneParameterValue::Scalar(value),
|
||||
ApiParameterValue::Toggle(value) => SceneParameterValue::Toggle(value),
|
||||
ApiParameterValue::Text(value) => SceneParameterValue::Text(value),
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiCommand {
|
||||
pub fn summary(&self) -> String {
|
||||
match self {
|
||||
Self::SetBlackout { enabled } => {
|
||||
if *enabled {
|
||||
"blackout enabled".to_string()
|
||||
} else {
|
||||
"blackout released".to_string()
|
||||
}
|
||||
}
|
||||
Self::SetMasterBrightness { value } => {
|
||||
format!("master brightness set to {:.0}%", value.clamp(0.0, 1.0) * 100.0)
|
||||
}
|
||||
Self::SelectPattern { pattern_id } => format!("pattern selected: {pattern_id}"),
|
||||
Self::RecallPreset { preset_id } => format!("preset recalled: {preset_id}"),
|
||||
Self::SelectGroup { group_id } => format!(
|
||||
"group selected: {}",
|
||||
group_id.as_deref().unwrap_or("all_panels")
|
||||
),
|
||||
Self::SetSceneParameter { key, .. } => format!("scene parameter updated: {key}"),
|
||||
Self::SetTransitionDurationMs { duration_ms } => {
|
||||
format!("transition duration set to {duration_ms} ms")
|
||||
}
|
||||
Self::TriggerPanelTest {
|
||||
node_id,
|
||||
panel_position,
|
||||
pattern,
|
||||
} => format!(
|
||||
"panel test {} on {}:{}",
|
||||
pattern.label(),
|
||||
node_id,
|
||||
panel_position.label()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiPanelPosition {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Top => "top",
|
||||
Self::Middle => "middle",
|
||||
Self::Bottom => "bottom",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiTestPattern {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::WalkingPixel106 => "walking_pixel_106",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiErrorResponse {
|
||||
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
api_version: API_VERSION,
|
||||
error: ApiErrorBody {
|
||||
code: code.into(),
|
||||
message: message.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user