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