1use std::sync::Arc;
2use std::{convert::TryFrom, net::SocketAddr};
3
4use thiserror::Error;
5
6use super::types::i32_to_duration;
7
8use crate::{
9 component::ComponentName,
10 global::{InputMessage, InputMessageData, InputSourceHandle, PriorityGuard},
11 image::{RawImage, RawImageError},
12 models::Color,
13};
14
15pub mod message;
17use message::HyperionRequest;
18
19#[derive(Debug, Error)]
20pub enum ProtoApiError {
21 #[error("error decoding image: {0}")]
22 RawImageError(#[from] RawImageError),
23 #[error("error broadcasting update: {0}")]
24 Broadcast(#[from] tokio::sync::broadcast::error::SendError<InputMessage>),
25 #[error("missing command data in protobuf frame")]
26 MissingCommand,
27 #[error("the priority {0} is not in the valid range between 100 and 199")]
28 InvalidPriority(i32),
29}
30
31fn validate_priority(
32 priority: i32,
33 source: &InputSourceHandle<InputMessage>,
34 priority_guard: &mut PriorityGuard,
35) -> Result<i32, ProtoApiError> {
36 if !(100..200).contains(&priority) {
37 return Err(ProtoApiError::InvalidPriority(priority));
38 }
39
40 *priority_guard = PriorityGuard::new_broadcast(source);
42
43 Ok(priority)
44}
45
46#[instrument(skip(request, source, priority_guard))]
47pub fn handle_request(
48 peer_addr: SocketAddr,
49 request: HyperionRequest,
50 source: &InputSourceHandle<InputMessage>,
51 priority_guard: &mut PriorityGuard,
52) -> Result<(), ProtoApiError> {
53 match request.command() {
54 message::hyperion_request::Command::Clearall => {
55 source.send(ComponentName::ProtoServer, InputMessageData::ClearAll)?;
57 }
58
59 message::hyperion_request::Command::Clear => {
60 let clear_request = request
61 .clear_request
62 .ok_or_else(|| ProtoApiError::MissingCommand)?;
63
64 source.send(
66 ComponentName::ProtoServer,
67 InputMessageData::Clear {
68 priority: clear_request.priority,
69 },
70 )?;
71 }
72
73 message::hyperion_request::Command::Color => {
74 let color_request = request
75 .color_request
76 .ok_or_else(|| ProtoApiError::MissingCommand)?;
77
78 let color = color_request.rgb_color;
79 let color = (
80 (color & 0x000_000FF) as u8,
81 ((color & 0x0000_FF00) >> 8) as u8,
82 ((color & 0x00FF_0000) >> 16) as u8,
83 );
84
85 let priority = validate_priority(color_request.priority, source, priority_guard)?;
86
87 source.send(
89 ComponentName::ProtoServer,
90 InputMessageData::SolidColor {
91 priority,
92 duration: i32_to_duration(color_request.duration),
93 color: Color::from_components(color),
94 },
95 )?;
96 }
97
98 message::hyperion_request::Command::Image => {
99 let image_request = request
100 .image_request
101 .ok_or_else(|| ProtoApiError::MissingCommand)?;
102
103 let width =
104 u32::try_from(image_request.imagewidth).map_err(|_| RawImageError::InvalidWidth)?;
105 let height = u32::try_from(image_request.imageheight)
106 .map_err(|_| RawImageError::InvalidHeight)?;
107 let raw_image = RawImage::try_from((image_request.imagedata.to_vec(), width, height))?;
108
109 let priority = validate_priority(image_request.priority, source, priority_guard)?;
110
111 source.send(
113 ComponentName::ProtoServer,
114 InputMessageData::Image {
115 priority,
116 duration: i32_to_duration(image_request.duration),
117 image: Arc::new(raw_image),
118 },
119 )?;
120 }
121 }
122
123 Ok(())
124}