Skip to main content

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}