mx_remote/types/unit.rs
1// Author: Lars Op den Kamp (lars@opdenkamp-it.nl)
2// Copyright (c) 2026 Op den Kamp IT Solutions
3
4//! A unit's health and IP configuration, and the commands that change them.
5
6use std::net::Ipv4Addr;
7
8use crate::wire::{DeviceUid, UnitCommandFlag, UnitCommandKind, UnitFlag};
9
10/// The most temperature sensors a unit reports.
11pub const UNIT_MAX_TEMPERATURES: usize = 6;
12
13/// A unit's health and IP configuration, as it last reported them.
14///
15/// A unit reports this about itself with its periodic broadcast, and within a
16/// few seconds of a change to its flags, its status or its addresses.
17#[derive(Clone, Debug, Default, PartialEq, Eq)]
18pub struct UnitStatus {
19 /// The report's flags.
20 pub flags: UnitFlag,
21 /// Seconds since the unit booted.
22 pub uptime: u32,
23 /// How often the unit has booted, when `flags` has
24 /// [`BOOT_COUNT`](UnitFlag::BOOT_COUNT).
25 pub boot_count: u32,
26 /// How often the unit has rebooted.
27 pub reboot_count: u32,
28 /// The encoder's health, a mask of the unit's encoder status bits.
29 pub encoder_health: u32,
30 /// The decoder's health, a mask of the unit's decoder status and state
31 /// bits.
32 pub decoder_health: u32,
33 /// The IP configuration in use.
34 pub network: UnitNetConfig,
35 /// The fan speed in RPM, when `flags` has
36 /// [`FAN_SPEED`](UnitFlag::FAN_SPEED).
37 pub fan_rpm: u16,
38 /// The fan duty cycle, in percent.
39 pub fan_duty: u8,
40 /// The unit's system status.
41 pub status: u8,
42 /// Seconds before a pending IP configuration is reverted.
43 pub net_revert_s: u8,
44 /// Temperatures, in degrees Celsius, at most
45 /// [`UNIT_MAX_TEMPERATURES`] of them.
46 pub temperatures: Vec<i8>,
47 /// The message of the current system status.
48 pub status_message: String,
49}
50
51impl UnitStatus {
52 /// How often the unit has booted, `None` while it does not report it.
53 pub const fn boot_count(&self) -> Option<u32> {
54 if !self.flags.has(UnitFlag::BOOT_COUNT) {
55 return None;
56 }
57 Some(self.boot_count)
58 }
59
60 /// The fan speed in RPM, `None` while the unit does not report it.
61 pub const fn fan_rpm(&self) -> Option<u16> {
62 if !self.flags.has(UnitFlag::FAN_SPEED) {
63 return None;
64 }
65 Some(self.fan_rpm)
66 }
67
68 /// Whether an IP configuration changed through the mesh waits for the
69 /// mesh controller to confirm it.
70 pub const fn network_pending(&self) -> bool {
71 self.flags.has(UnitFlag::NET_PENDING)
72 }
73}
74
75/// An IP configuration: the addresses a unit uses, or is told to use.
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct UnitNetConfig {
78 /// The address.
79 pub ip: Ipv4Addr,
80 /// The netmask.
81 pub netmask: Ipv4Addr,
82 /// The gateway.
83 pub gateway: Ipv4Addr,
84 /// The primary DNS server.
85 pub dns1: Ipv4Addr,
86 /// The secondary DNS server.
87 pub dns2: Ipv4Addr,
88}
89
90impl Default for UnitNetConfig {
91 fn default() -> Self {
92 Self {
93 ip: Ipv4Addr::UNSPECIFIED,
94 netmask: Ipv4Addr::UNSPECIFIED,
95 gateway: Ipv4Addr::UNSPECIFIED,
96 dns1: Ipv4Addr::UNSPECIFIED,
97 dns2: Ipv4Addr::UNSPECIFIED,
98 }
99 }
100}
101
102impl UnitNetConfig {
103 /// Whether the address is one a unit can hold and the netmask is a run of
104 /// ones from the top.
105 pub fn is_valid(&self) -> bool {
106 let ip = self.ip;
107 let mask = u32::from(self.netmask);
108 !ip.is_unspecified()
109 && !ip.is_broadcast()
110 && !ip.is_multicast()
111 && mask != 0
112 && mask.leading_ones() + mask.trailing_zeros() == 32
113 }
114}
115
116/// A command for one unit, as a peer sent it.
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub struct UnitCommand {
119 /// The unit the command is for.
120 pub target: DeviceUid,
121 /// What it asks.
122 pub command: UnitCommandKind,
123 /// The command's flags.
124 pub flags: UnitCommandFlag,
125 /// The IP configuration a [`NET_CONFIG`](UnitCommandKind::NET_CONFIG)
126 /// carries, when the frame holds one.
127 pub network: Option<UnitNetConfig>,
128}