Skip to main content

linux_fan_utility/
protocol.rs

1// Copyright (c) 2026 Pegasus Heavy Industries LLC
2// Licensed under the MIT License
3
4//! Client-daemon protocol over Unix domain sockets.
5//!
6//! Messages are newline-delimited JSON. The client sends a [`Request`]
7//! and the daemon replies with a [`Response`].
8
9use crate::config::FanAssignment;
10use crate::curve::{CurvePoint, FanCurve};
11use crate::hwmon::{FanStatus, TempStatus};
12use serde::{Deserialize, Serialize};
13
14// ---------------------------------------------------------------------------
15// Requests (TUI -> Daemon)
16// ---------------------------------------------------------------------------
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type")]
20pub enum Request {
21    /// Request current status of all fans and temps.
22    #[serde(rename = "get_status")]
23    GetStatus,
24
25    /// Set a fan to manual PWM.
26    #[serde(rename = "set_manual")]
27    SetManual { fan_id: String, pwm: u8 },
28
29    /// Assign a curve to a fan.
30    #[serde(rename = "set_curve")]
31    SetCurve {
32        fan_id: String,
33        curve_name: String,
34        temp_sensor_id: String,
35    },
36
37    /// Set a fan to automatic (BIOS) control.
38    #[serde(rename = "set_auto")]
39    SetAuto { fan_id: String },
40
41    /// List all configured curves.
42    #[serde(rename = "list_curves")]
43    ListCurves,
44
45    /// Create or update a curve.
46    #[serde(rename = "upsert_curve")]
47    UpsertCurve {
48        name: String,
49        points: Vec<CurvePoint>,
50    },
51
52    /// Delete a curve by name.
53    #[serde(rename = "delete_curve")]
54    DeleteCurve { name: String },
55
56    /// Save current configuration to disk.
57    #[serde(rename = "save_config")]
58    SaveConfig,
59
60    /// Reload configuration from disk.
61    #[serde(rename = "reload_config")]
62    ReloadConfig,
63
64    /// Request the daemon to push periodic status updates.
65    #[serde(rename = "subscribe")]
66    Subscribe,
67
68    /// Stop receiving periodic status updates.
69    #[serde(rename = "unsubscribe")]
70    Unsubscribe,
71}
72
73// ---------------------------------------------------------------------------
74// Responses (Daemon -> TUI)
75// ---------------------------------------------------------------------------
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(tag = "type")]
79pub enum Response {
80    /// Current system status.
81    #[serde(rename = "status")]
82    Status {
83        fans: Vec<FanStatus>,
84        temps: Vec<TempStatus>,
85        assignments: Vec<FanAssignmentInfo>,
86    },
87
88    /// List of configured curves.
89    #[serde(rename = "curves")]
90    Curves { curves: Vec<FanCurve> },
91
92    /// Operation succeeded.
93    #[serde(rename = "ok")]
94    Ok { message: String },
95
96    /// Operation failed.
97    #[serde(rename = "error")]
98    Error { message: String },
99}
100
101/// Fan assignment info sent in status messages.
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct FanAssignmentInfo {
104    pub fan_id: String,
105    pub assignment: FanAssignment,
106}
107
108// ---------------------------------------------------------------------------
109// Serialization helpers
110// ---------------------------------------------------------------------------
111
112/// Encode a message as a newline-delimited JSON string.
113pub fn encode<T: Serialize>(msg: &T) -> Result<String, serde_json::Error> {
114    let mut s = serde_json::to_string(msg)?;
115    s.push('\n');
116    Ok(s)
117}
118
119/// Decode a message from a JSON string (newline-trimmed).
120pub fn decode<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T, serde_json::Error> {
121    serde_json::from_str(s.trim())
122}