hyperion/api/
proto.rs

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
15/// Schema definitions as Serde serializable structures and enums
16pub 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    // Re-creating the priority guard drops the old value, thus clearing the previous priority
41    *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            // Update state
56            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            // Update state
65            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            // Update state
88            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            // Update state
112            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}