use std::{collections::HashMap, fmt};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChipId {
All,
Id(u16),
}
impl ChipId {
pub const fn from_code(code: u16) -> Self {
if code == 999 {
Self::All
} else {
Self::Id(code)
}
}
pub const fn code(&self) -> u16 {
match self {
Self::All => 999,
Self::Id(id) => *id,
}
}
}
impl fmt::Debug for ChipId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => write!(f, "All"),
Self::Id(id) => write!(f, "{}", id),
}
}
}
macro_rules! define_device_errors {
(
exact {
$(
$e_variant:ident = $e_code:literal => {
reason: $e_reason:literal,
method: $e_method:literal,
}
),* $(,)?
}
range {
$(
$r_variant:ident { $r_field:ident: $r_ty:ty } = $( $r_start:literal ..= $r_end:literal )|+ , base: $r_base:literal => {
reason: $r_reason:literal,
method: $r_method:literal,
}
),* $(,)?
}
bbb {
$(
$b_variant:ident = $b_start:literal ..= $b_end:literal, base: $b_base:literal => {
reason: $b_reason:literal,
method: $b_method:literal,
}
),* $(,)?
}
) => {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeviceErrorCode {
$(
$e_variant,
)*
$(
$r_variant { $r_field: $r_ty },
)*
$(
$b_variant { board: u8, chip: ChipId },
)*
Unknown(u32),
}
impl DeviceErrorCode {
pub const fn from_code(code: u32) -> Self {
match code {
$(
$e_code => Self::$e_variant,
)*
$(
$( $r_start ..= $r_end )|+ => Self::$r_variant {
$r_field: (code - $r_base) as $r_ty,
},
)*
$(
$b_start ..= $b_end => {
let offset = code - $b_base;
Self::$b_variant {
board: (offset / 1000) as u8,
chip: ChipId::from_code((offset % 1000) as u16),
}
}
)*
_ => Self::Unknown(code),
}
}
pub const fn code(&self) -> u32 {
match self {
$(
Self::$e_variant => $e_code,
)*
$(
Self::$r_variant { $r_field } => $r_base + (*$r_field as u32),
)*
$(
Self::$b_variant { board, chip } => {
$b_base + (*board as u32) * 1000 + chip.code() as u32
}
)*
Self::Unknown(code) => *code,
}
}
pub const fn reason(&self) -> &'static str {
match self {
$(
Self::$e_variant => $e_reason,
)*
$(
Self::$r_variant { .. } => $r_reason,
)*
$(
Self::$b_variant { .. } => $b_reason,
)*
Self::Unknown(_) => "Unknown error",
}
}
pub const fn processing_method(&self) -> &'static str {
match self {
$(
Self::$e_variant => $e_method,
)*
$(
Self::$r_variant { .. } => $r_method,
)*
$(
Self::$b_variant { .. } => $b_method,
)*
Self::Unknown(_) => "N/A",
}
}
}
impl fmt::Display for DeviceErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reason())
}
}
impl fmt::Debug for DeviceErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E{}", self.code())
}
}
};
}
define_device_errors! {
exact {
FaninDetectSpeedError = 110 => {
reason: "Fanin detect speed error",
method: "Check fan connection, replace PSU or fan",
},
FanoutDetectSpeedError = 111 => {
reason: "Fanout detect speed error",
method: "Check fan connection, replace PSU or fan",
},
FaninSpeedError2k = 120 => {
reason: "Fanin speed error (deviation 2000+)",
method: "Check fan connection, replace PSU or fan",
},
FanoutSpeedError2k = 121 => {
reason: "Fanout speed error (deviation 2000+)",
method: "Check fan connection, replace PSU or fan",
},
FaninSpeedError3k = 130 => {
reason: "Fanin speed error (deviation 3000+)",
method: "Check fan connection, replace PSU or fan",
},
FanoutSpeedError3k = 131 => {
reason: "Fanout speed error (deviation 3000+)",
method: "Check fan connection, replace PSU or fan",
},
FanSpeedTooHigh = 140 => {
reason: "Fan speed is too high",
method: "Check ambient temperature",
},
PowerProbingError = 200 => {
reason: "Power probing error, no power found",
method: "Detect PSU flat cable, update firmware, or replace PSU",
},
PowerConfigMismatch = 201 => {
reason: "Power supply and configuration file mismatch",
method: "Confirm PSU type, replace with correct PSU",
},
PowerOutputVoltageError = 202 => {
reason: "Power output voltage error",
method: "Upgrade to latest firmware or check PSU",
},
PowerProtecting = 203 => {
reason: "Power protecting",
method: "Check ambient temperature",
},
PowerCurrentProtecting = 204 => {
reason: "Power current protecting",
method: "Check ambient temperature",
},
PowerCurrentError = 205 => {
reason: "Power current error",
method: "Check power supply of power grid",
},
PowerInputVoltageLow = 206 => {
reason: "Power input voltage is low",
method: "Improve power supply conditions, increase input voltage or replace PSU",
},
PowerInputCurrentProtecting = 207 => {
reason: "Power input current protecting",
method: "Improve power supply conditions and increase input voltage",
},
PowerSupplyChangesTooMuch = 208 => {
reason: "Power supply changes too much",
method: "Replace PSU",
},
PowerVoltageDeviation = 209 => {
reason: "Deviation between PSU setting voltage and actual voltage",
method: "Replace PSU",
},
PowerErrorStatus = 210 => {
reason: "Power error status",
method: "Check PSU failure code",
},
PowerOutputCurrentDeviation = 211 => {
reason: "Power output current deviation is too large",
method: "Replace PSU",
},
PowerOutputVoltageMarginInsufficient = 212 => {
reason: "Power output voltage margin is insufficient",
method: "Consult after-sales personnel",
},
PowerVoltageCurrentMismatch = 213 => {
reason: "Power input voltage and current do not match",
method: "Replace PSU",
},
PowerPinDidNotChange = 214 => {
reason: "Power pin did not change",
method: "Contact after-sales personnel",
},
PowerVoutSetError = 215 => {
reason: "Power vout set error",
method: "Contact after-sales personnel",
},
PowerRemainedUnchanged = 216 => {
reason: "Power remained unchanged for a long time",
method: "Replace PSU",
},
PowerSetEnableError = 217 => {
reason: "Power set enable error",
method: "Replace PSU",
},
PowerInputVoltageBelow230v = 218 => {
reason: "Power input voltage lower than 230V for high power mode",
method: "Improve power supply conditions, increase input voltage or replace PSU",
},
PowerInputCurrentIncorrect = 219 => {
reason: "Power input current is incorrect",
method: "Replace PSU",
},
PowerOutputOvervoltageProtection = 239 => {
reason: "Overvoltage protection of power output",
method: "Check power supply of power grid",
},
PowerOutputUndervoltageProtection = 240 => {
reason: "Low voltage protection for power output",
method: "Check power supply of power grid",
},
PowerOutputCurrentImbalance = 241 => {
reason: "Power output current imbalance",
method: "Replace PSU",
},
PowerPrimarySideOvercurrent = 257 => {
reason: "Input over current protection of PSU primary side",
method: "Power off and restart, replace PSU if no effect",
},
PowerInputVoltageImbalanceWarning = 258 => {
reason: "Power input three-phase voltage imbalance warning",
method: "Check power supply of power grid",
},
PowerInputVoltageUnbalance = 259 => {
reason: "Power input three-phase voltage unbalance protection",
method: "Check power supply of power grid",
},
PowerCommunicationWarning = 263 => {
reason: "Power communication warning",
method: "Check if control board screws are locked",
},
PowerCommunicationError = 264 => {
reason: "Power communication error",
method: "Check if control board screws are locked",
},
PowerWatchdogProtection = 267 => {
reason: "Power watchdog protection",
method: "Contact after-sales personnel",
},
PowerOutputOverCurrent = 268 => {
reason: "Power output over-current protection",
method: "Check ambient temperature and screws of copper bar",
},
PowerInputOverCurrent = 269 => {
reason: "Power input over-current protection",
method: "Improve power supply conditions and increase input voltage",
},
PowerInputOverVoltage = 270 => {
reason: "Power input over-voltage protection",
method: "Check power supply of power grid",
},
PowerInputUnderVoltage = 271 => {
reason: "Power input under-voltage protection",
method: "Check power supply of power grid",
},
PowerOutputExcessiveWarning = 272 => {
reason: "Warning of excessive power output",
method: "Check ambient temperature",
},
PowerInputPowerTooHighWarning = 273 => {
reason: "Power input power too high warning",
method: "Check ambient temperature",
},
PowerFanWarning = 274 => {
reason: "Power fan warning",
method: "Check if PSU fan is blocked or replace PSU",
},
PowerOverTemperatureWarning = 275 => {
reason: "Power over temperature warning",
method: "Check ambient temperature",
},
AllTempSensorDetectionError = 309 => {
reason: "All temperature sensor detection errors",
method: "Check the wiring of the hashboard",
},
LiquidCoolingTempSensorError = 326 => {
reason: "Liquid cooling temperature sensor communication error",
method: "Replace PSU",
},
ControlBoardTempSensorError = 329 => {
reason: "Control board temperature sensor communication error",
method: "Replace PSU",
},
HashboardOverheating = 360 => {
reason: "Hashboard temperature overheating",
method: "Check ambient temperature",
},
EnvTempFluctuationTooMuch = 370 => {
reason: "Environment temperature fluctuates too much",
method: "Check ambient temperature, wind direction and speed",
},
NoSoftwareConfig = 500 => {
reason: "No software configuration added for model",
method: "Upgrade to latest firmware",
},
EnvTempHigh = 600 => {
reason: "Environment temperature is high",
method: "Check ambient temperature (below 35°C in normal mode)",
},
EnvTempTooHighPerfMode = 610 => {
reason: "Ambient temperature too high in high performance mode",
method: "Check ambient temperature (below 30°C in performance mode)",
},
LiquidCoolingTempProtection = 620 => {
reason: "Liquid cooling liquid temperature protection",
method: "Check liquid temperature",
},
LiquidTempFluctuationTooLarge = 652 => {
reason: "Liquid temperature fluctuation is too large",
method: "Check liquid temperature",
},
ControlBoardNoSupportChip = 701 => {
reason: "Control board no support chip",
method: "Upgrade firmware of corresponding type",
},
ControlBoardVersionUnknown = 702 => {
reason: "Control board version unknown",
method: "Contact after-sales personnel",
},
ControlBoardRebootedException0 = 710 => {
reason: "Control board rebooted as exception",
method: "Update firmware, check control board screws, replace if needed",
},
ControlBoardRebootedException1 = 712 => {
reason: "Control board rebooted as exception",
method: "Update firmware, check control board screws, replace if needed",
},
NetworkConnectionUnstable = 714 => {
reason: "Network connection is seriously unstable",
method: "Check network cable or replace control board",
},
CgminerChecksumError = 800 => {
reason: "Cgminer checksum error",
method: "Re-upgrade firmware",
},
SystemMonitorChecksumError = 801 => {
reason: "System-monitor checksum error",
method: "Re-upgrade firmware",
},
RemoteDaemonChecksumError = 802 => {
reason: "Remote-daemon checksum error",
method: "Re-upgrade firmware",
},
AirToLiquidPcbsnMismatch = 810 => {
reason: "Air to liquid or frequency and PCBSN mismatch",
method: "Re-upgrade air to liquid cooling firmware or re-adjust frequency",
},
AirToLiquidPsusnMismatch = 820 => {
reason: "Air to liquid or frequency and PSUSN mismatch",
method: "Re-upgrade air to liquid cooling firmware or re-adjust frequency",
},
PowerRateError = 901 => {
reason: "Power rate error",
method: "Check if miner has been modified",
},
NoPoolConfigured = 2000 => {
reason: "No pool information configured",
method: "Check pool configuration",
},
AllPoolsDisabled = 2010 => {
reason: "All pools are disabled",
method: "Check network environment or pool configuration",
},
Pool0ConnectFailed = 2020 => {
reason: "Pool0 connect failed",
method: "Check network environment or pool configuration",
},
Pool1ConnectFailed = 2021 => {
reason: "Pool1 connect failed",
method: "Check network environment or pool configuration",
},
Pool2ConnectFailed = 2022 => {
reason: "Pool2 connect failed",
method: "Check network environment or pool configuration",
},
PoolHighRejectionRate = 2030 => {
reason: "High rejection rate of pool",
method: "Check network, pool configuration, and mining currency",
},
PoolNoAsicboost = 2040 => {
reason: "Pool does not support asicboost mode",
method: "Check pool configuration",
},
PoolSwitchFailed = 2050 => {
reason: "Failed to switch to new pool",
method: "Check network environment or pool configuration",
},
HashRateTooLow0 = 2310 => {
reason: "Hash rate is too low",
method: "Check power supply, network, and ambient temperature",
},
HashRateTooLow1 = 2320 => {
reason: "Hash rate is too low",
method: "Check power supply, network, and ambient temperature",
},
HashRateLossTooHigh0 = 2340 => {
reason: "Loss of hash rate is too high",
method: "Check power supply, network, and ambient temperature",
},
HashRateLossTooHigh1 = 2350 => {
reason: "Loss of hash rate is too high",
method: "Check power supply, network, and ambient temperature",
},
ChipTempCalibrationAbnormal = 5090 => {
reason: "Chip temperature calibration abnormality",
method: "Stop miner, restart, or restore factory settings",
},
ToolVersionTooLow = 8000 => {
reason: "WhatsminerTool version is too low",
method: "Download latest WhatsminerTool",
},
FrequencyNotStandard = 8010 => {
reason: "Frequency is not up to standard",
method: "Upgrade firmware or restore factory settings",
},
HashrateNotStandard = 8020 => {
reason: "Hashrate is not up to standard",
method: "Upgrade firmware or restore factory settings",
},
WrongSoftwareVersion = 8400 => {
reason: "Wrong software version",
method: "Upgrade to correct official firmware",
},
WrongSoftwareVersionM2xM3x = 8410 => {
reason: "Wrong software version (M2x/M3x mismatch)",
method: "Upgrade to correct firmware version",
},
MinerPsuModelMismatch = 8700 => {
reason: "Miner and PSU model mismatch",
method: "Replace with correct PSU",
},
SecurityLibraryError = 100000 => {
reason: "Security library error",
method: "Upgrade firmware or burn card",
},
SecuritySignatureIllegal = 100001 => {
reason: "/antiv/signature illegal",
method: "Upgrade firmware or burn card",
},
SecurityDigInitdIllegal = 100002 => {
reason: "/antiv/dig/initd.dig illegal",
method: "Upgrade firmware or burn card",
},
SecurityDigPfPartialIllegal = 100003 => {
reason: "/antiv/dig/pf_partial.dig illegal",
method: "Upgrade firmware or burn card",
},
SecurityBtminerRemoved = 100100 => {
reason: "Security btminer be removed",
method: "Upgrade firmware or burn card",
},
SecurityIllegalFile = 110000 => {
reason: "Security illegal file",
method: "Upgrade firmware or burn card",
},
SecurityVirus0001 = 110001 => {
reason: "Security virus 0001 is removed",
method: "Upgrade firmware or burn card",
},
}
range {
PowerOutputOverTempProtection { index: u8 } = 233 ..= 235, base: 233 => {
reason: "Power output over temperature protection",
method: "Check ambient temperature",
},
PowerOutputOvercurrentProtection { index: u8 } = 236 ..= 238, base: 236 => {
reason: "Overcurrent protection of power output",
method: "Check ambient temperature and screws of copper bar",
},
PowerInputOverTempProtection { index: u8 } = 243 ..= 245, base: 243 => {
reason: "Over-temperature protection for power input",
method: "Check ambient temperature",
},
PowerInputOvercurrentProtection { index: u8 } = 246 ..= 247, base: 246 => {
reason: "Overcurrent protection for power input",
method: "Replace PSU",
},
PowerInputOvervoltageProtection { index: u8 } = 248 ..= 249, base: 248 => {
reason: "Overvoltage protection for power input",
method: "Check power supply of power grid",
},
PowerInputUndervoltageProtection { index: u8 } = 250 ..= 251, base: 250 => {
reason: "Undervoltage protection for power input",
method: "Check power supply of power grid",
},
PowerFanError { index: u8 } = 253 ..= 254, base: 253 => {
reason: "Power fan error",
method: "Replace PSU",
},
PowerOutputOverpowerProtection { index: u8 } = 255 ..= 256, base: 255 => {
reason: "Protection of over power output",
method: "Check ambient temperature",
},
PowerCustomRegisterError { index: u8 } = 280 ..= 299, base: 280 => {
reason: "Power custom register error",
method: "Contact after-sales personnel",
},
SmxTempSensorDetection { board: u8 } = 300 ..= 309, base: 300 => {
reason: "SMX temperature sensor detection error",
method: "Check the wiring of the hashboard",
},
SmxTempReadingError { board: u8 } = 320 ..= 325 | 327 ..= 328, base: 320 => {
reason: "SMX temperature reading error",
method: "Check control board screws and adapter board contact",
},
SmxTempProtecting { board: u8 } = 350 ..= 359, base: 350 => {
reason: "SMX temperature protecting",
method: "Check ambient temperature",
},
SmxDetectEepromError { board: u8 } = 410 ..= 419, base: 410 => {
reason: "SMX detect eeprom error",
method: "Check adapter board and flat cable contact",
},
SmxParserEepromError { board: u8 } = 420 ..= 429, base: 420 => {
reason: "SMX parser eeprom error",
method: "Upgrade to latest firmware",
},
SmxChipBinTypeError { board: u8 } = 430 ..= 439, base: 430 => {
reason: "SMX chip bin type error",
method: "Upgrade to latest firmware",
},
SmxEepromChipNumError { board: u8 } = 440 ..= 449, base: 440 => {
reason: "SMX eeprom chip num error",
method: "Upgrade to latest firmware",
},
SmxEepromXferError { board: u8 } = 450 ..= 459, base: 450 => {
reason: "SMX eeprom xfer error",
method: "Check adapter board contact and upgrade firmware",
},
SmxMinerTypeError { board: u8 } = 510 ..= 519, base: 510 => {
reason: "SMX miner type error",
method: "Replace with correct hashboard",
},
SmxBinTypeError { board: u8 } = 520 ..= 529, base: 520 => {
reason: "SMX bin type error",
method: "Replace with correct hashboard",
},
SmxNotFound { board: u8 } = 530 ..= 539, base: 530 => {
reason: "SMX not found",
method: "Check adapter board connection and flat cable",
},
SmxReadingChipIdError { board: u8 } = 540 ..= 549, base: 540 => {
reason: "SMX reading chip id error",
method: "Power off, re-plug adapter board or swap hashboards",
},
SmxBadChips { board: u8 } = 550 ..= 559, base: 550 => {
reason: "SMX have bad chips",
method: "Contact after-sales personnel",
},
SmxLossBalance { board: u8 } = 560 ..= 569, base: 560 => {
reason: "SMX loss balance",
method: "Power off, re-plug adapter board or swap hashboards",
},
SmxXferErrorChip { board: u8 } = 570 ..= 579, base: 570 => {
reason: "SMX xfer error chip",
method: "Contact after-sales personnel",
},
SmxResetError { board: u8 } = 580 ..= 589, base: 580 => {
reason: "SMX reset error",
method: "If hashrate is abnormal, consult after-sales",
},
SmxFrequencyTooLow { board: u8 } = 590 ..= 599, base: 590 => {
reason: "SMX frequency too low",
method: "Contact after-sales personnel",
},
SmxSerialPortCommError { board: u8 } = 720 ..= 729, base: 720 => {
reason: "SMX serial port communication error",
method: "Upgrade firmware or replace control board",
},
ProcessExitedAbnormally { process: u8 } = 9000 ..= 9099, base: 9000 => {
reason: "The process exited abnormally",
method: "Upgrade newest firmware, consult after-sales if needed",
},
SmxChipVoltageTooLow { board: u8 } = 5010 ..= 5019, base: 5010 => {
reason: "SMX chip voltage too low",
method: "Contact after-sales personnel",
},
ChipVoltageChange { board: u8 } = 5020 ..= 5029, base: 5020 => {
reason: "Chip voltage change",
method: "Replace PSU",
},
SmxChipTempDiffTooLarge { board: u8 } = 5030 ..= 5039, base: 5030 => {
reason: "SMX chip max-min temperature difference too large",
method: "Check heat dissipation of miner",
},
SmxTempMaxChipTempDiff { board: u8 } = 5040 ..= 5049, base: 5040 => {
reason: "SMX temperature maximum chip temperature difference too large",
method: "Check heat dissipation of miner",
},
SmxChipTempProtection { board: u8 } = 5050 ..= 5059, base: 5050 => {
reason: "SMX chip temperature protection",
method: "Check ambient temperature",
},
SmxLiquidVelocityAbnormal { board: u8 } = 5070 ..= 5078, base: 5070 => {
reason: "SMX liquid velocity is abnormal",
method: "Check if water flow is normal",
},
SmxChipTempCalibrationFailed { board: u8 } = 5080 ..= 5089, base: 5080 => {
reason: "SMX chip temperature calibration failed",
method: "Restore factory settings and recalibrate",
},
SmxFrequencyUpTimeout { board: u8 } = 5110 ..= 5119, base: 5110 => {
reason: "SMX frequency up timeout",
method: "Reboot, tighten copper bar screws, or upgrade firmware",
},
}
bbb {
SmxChipBbbErrorNonce = 520000 ..= 529999, base: 520000 => {
reason: "SMX chip BBB error nonce",
method: "Contact after-sales personnel",
},
SmxChipBbbFewNonce = 530000 ..= 539999, base: 530000 => {
reason: "SMX chip BBB few nonce",
method: "Contact after-sales personnel",
},
SmxChipBbbTempProtected = 540000 ..= 549999, base: 540000 => {
reason: "SMX chip BBB temp protected",
method: "Restart miner, contact after-sales if persists",
},
SmxChipBbbReset = 550000 ..= 559999, base: 550000 => {
reason: "SMX chip BBB is reset",
method: "Replace PSU, contact after-sales if persists",
},
SmxChipBbbNoNonce = 560000 ..= 569999, base: 560000 => {
reason: "SMX chip BBB does not return nonce",
method: "Replace PSU, contact after-sales if persists",
},
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PsuErrorCode: u16 {
const INPUT_VOLTAGE_LOW = 0x0001;
const RADIATOR_OVER_TEMP_0 = 0x0002;
const RADIATOR_OVER_TEMP_1 = 0x0004;
const ENV_OVER_TEMP = 0x0008;
const PRIMARY_SIDE_OVER_CURRENT_0 = 0x0010;
const OUTPUT_UNDERVOLTAGE = 0x0020;
const OUTPUT_OVER_CURRENT_320A = 0x0040;
const PRIMARY_SIDE_OVER_CURRENT_1 = 0x0080;
const SINGLE_CIRCUIT_OVER_CURRENT_0 = 0x0100;
const SINGLE_CIRCUIT_OVER_CURRENT_1 = 0x0200;
const SINGLE_CIRCUIT_OVER_CURRENT_2 = 0x0400;
const FAN_FAILURE = 0x0800;
const OUTPUT_OVER_CURRENT_310A = 0x1000;
const OUTPUT_OVER_CURRENT_295A = 0x2000;
}
}
impl PsuErrorCode {
pub const fn from_code(code: u16) -> Self {
Self::from_bits_retain(code)
}
pub const fn code(&self) -> u16 {
self.bits()
}
pub fn reasons(&self) -> Vec<&'static str> {
PSU_ERROR_DESCRIPTIONS
.iter()
.filter_map(|(flag, reason, _)| self.contains(*flag).then_some(*reason))
.collect()
}
pub fn processing_methods(&self) -> Vec<&'static str> {
PSU_ERROR_DESCRIPTIONS
.iter()
.filter_map(|(flag, _, method)| self.contains(*flag).then_some(*method))
.collect()
}
pub fn reason(&self) -> String {
join_descriptions(self.reasons(), "Unknown PSU error")
}
pub fn processing_method(&self) -> String {
join_descriptions(self.processing_methods(), "N/A")
}
}
impl fmt::Display for PsuErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.reason())
}
}
impl fmt::Debug for PsuErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E0x{:04X}", self.code())
}
}
const PSU_ERROR_DESCRIPTIONS: &[(PsuErrorCode, &str, &str)] = &[
(
PsuErrorCode::INPUT_VOLTAGE_LOW,
"Input voltage is too low, need improvement",
"Check the power provided from the power supply",
),
(
PsuErrorCode::RADIATOR_OVER_TEMP_0,
"Temperature sampling over temperature protection of power radiator",
"Turn off the power for 10 minutes and then turn it back on. If the phenomenon recurs, replace the power supply",
),
(
PsuErrorCode::RADIATOR_OVER_TEMP_1,
"Temperature sampling over temperature protection of power radiator",
"Turn off the power for ten minutes and then turn it back on. If the phenomenon recurs, replace the power supply",
),
(
PsuErrorCode::ENV_OVER_TEMP,
"Over temperature protection of environmental temperature sampling in power supply",
"Turn off the power for ten minutes and then turn it back on. If the phenomenon recurs, replace the power supply",
),
(
PsuErrorCode::PRIMARY_SIDE_OVER_CURRENT_0,
"Primary side over current",
"Turn off the power for ten minutes and then turn it back on. If the phenomenon recurs, replace the power supply",
),
(
PsuErrorCode::OUTPUT_UNDERVOLTAGE,
"Output undervoltage",
"Check the power provided from the power supply",
),
(
PsuErrorCode::OUTPUT_OVER_CURRENT_320A,
"Output over current (continuous load 320A for more than 2S)",
"Tighten screws of the copper bar again",
),
(
PsuErrorCode::PRIMARY_SIDE_OVER_CURRENT_1,
"Primary side over current",
"Turn off the power for ten minutes and then turn it back on. If the phenomenon recurs, replace the power supply",
),
(
PsuErrorCode::SINGLE_CIRCUIT_OVER_CURRENT_0,
"Single circuit overcurrent (protection point 120A)",
"Tighten screws of the copper bar again",
),
(
PsuErrorCode::SINGLE_CIRCUIT_OVER_CURRENT_1,
"Single circuit overcurrent (protection point 120A)",
"Tighten screws of the copper bar again",
),
(
PsuErrorCode::SINGLE_CIRCUIT_OVER_CURRENT_2,
"Single circuit overcurrent (protection point 120A)",
"Tighten screws of the copper bar again",
),
(
PsuErrorCode::FAN_FAILURE,
"Fan failure",
"Replace the power supply",
),
(
PsuErrorCode::OUTPUT_OVER_CURRENT_310A,
"Output over current (continuous load of 310A for more than 5min)",
"Tighten screws of the copper bar again",
),
(
PsuErrorCode::OUTPUT_OVER_CURRENT_295A,
"Output over current (continuous load 295A for more than 10min)",
"Tighten screws of the copper bar again",
),
];
fn join_descriptions(parts: Vec<&'static str>, fallback: &'static str) -> String {
if parts.is_empty() {
fallback.to_string()
} else {
parts.join("; ")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCode {
Device(DeviceErrorCode),
Psu(PsuErrorCode),
}
impl ErrorCode {
pub fn reason(&self) -> String {
match self {
Self::Device(code) => code.reason().to_string(),
Self::Psu(code) => code.reason(),
}
}
pub fn processing_method(&self) -> String {
match self {
Self::Device(code) => code.processing_method().to_string(),
Self::Psu(code) => code.processing_method(),
}
}
fn parse_str(value: &str) -> Result<Self, String> {
let value = value.trim();
let value = value
.strip_prefix('E')
.or_else(|| value.strip_prefix('e'))
.unwrap_or(value);
if let Some(hex) = value
.strip_prefix("0x")
.or_else(|| value.strip_prefix("0X"))
{
let code = u16::from_str_radix(hex, 16)
.map_err(|err| format!("invalid PSU error code `{value}`: {err}"))?;
return Ok(Self::Psu(PsuErrorCode::from_code(code)));
}
let code = value
.parse::<u32>()
.map_err(|err| format!("invalid device error code `{value}`: {err}"))?;
Ok(Self::Device(DeviceErrorCode::from_code(code)))
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Device(code) => fmt::Display::fmt(code, f),
Self::Psu(code) => fmt::Display::fmt(code, f),
}
}
}
impl fmt::Debug for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Device(code) => fmt::Debug::fmt(code, f),
Self::Psu(code) => fmt::Debug::fmt(code, f),
}
}
}
impl<'de> serde::Deserialize<'de> for ErrorCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Visitor;
struct ErrorCodeVisitor;
impl Visitor<'_> for ErrorCodeVisitor {
type Value = ErrorCode;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("decimal device error code or hexadecimal PSU error code")
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let code = u32::try_from(value)
.map_err(|_| E::custom(format!("device error code out of range: {value}")))?;
Ok(ErrorCode::Device(DeviceErrorCode::from_code(code)))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
ErrorCode::parse_str(value).map_err(E::custom)
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&value)
}
}
deserializer.deserialize_any(ErrorCodeVisitor)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeviceErrorEntry {
pub code: ErrorCode,
pub timestamp: String,
pub reason: String,
}
impl<'de> serde::Deserialize<'de> for DeviceErrorEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error as _;
let map = HashMap::<String, String>::deserialize(deserializer)?;
let mut code: Option<ErrorCode> = None;
let mut timestamp = None;
let mut reason = None;
for (key, value) in map {
match key.as_str() {
"reason" => reason = Some(value),
_ => {
let code_deserializer =
serde::de::value::StrDeserializer::<D::Error>::new(&key);
code = Some(ErrorCode::deserialize(code_deserializer)?);
timestamp = Some(value);
}
}
}
Ok(Self {
code: code.ok_or_else(|| D::Error::custom("missing error code key"))?,
timestamp: timestamp.ok_or_else(|| D::Error::custom("missing timestamp"))?,
reason: reason.ok_or_else(|| D::Error::custom("missing reason"))?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_code_roundtrip() {
let err = DeviceErrorCode::from_code(110);
assert_eq!(err.code(), 110);
assert_eq!(err.reason(), "Fanin detect speed error");
assert!(err.processing_method().contains("fan"));
assert_eq!(format!("{err}"), "Fanin detect speed error");
assert_eq!(format!("{err:?}"), "E110");
}
#[test]
fn range_code_roundtrip() {
let err = DeviceErrorCode::from_code(531);
assert_eq!(err.code(), 531);
assert_eq!(err.reason(), "SMX not found");
}
#[test]
fn bbb_code_roundtrip() {
let err = DeviceErrorCode::from_code(550999);
assert_eq!(err.code(), 550999); assert!(err.reason().contains("BBB"));
}
#[test]
fn range_boundaries_roundtrip() {
let start = DeviceErrorCode::from_code(233);
assert_eq!(start.code(), 233);
assert!(matches!(
start,
DeviceErrorCode::PowerOutputOverTempProtection { index: 0 }
));
let end = DeviceErrorCode::from_code(235);
assert_eq!(end.code(), 235);
assert!(matches!(
end,
DeviceErrorCode::PowerOutputOverTempProtection { index: 2 }
));
let s1 = DeviceErrorCode::from_code(320);
assert_eq!(s1.code(), 320);
assert!(matches!(
s1,
DeviceErrorCode::SmxTempReadingError { board: 0 }
));
let e1 = DeviceErrorCode::from_code(325);
assert_eq!(e1.code(), 325);
assert!(matches!(
e1,
DeviceErrorCode::SmxTempReadingError { board: 5 }
));
assert_eq!(DeviceErrorCode::from_code(326).code(), 326);
assert!(matches!(
DeviceErrorCode::from_code(326),
DeviceErrorCode::LiquidCoolingTempSensorError
));
let s2 = DeviceErrorCode::from_code(327);
assert_eq!(s2.code(), 327);
assert!(matches!(
s2,
DeviceErrorCode::SmxTempReadingError { board: 7 }
));
let e2 = DeviceErrorCode::from_code(328);
assert_eq!(e2.code(), 328);
assert!(matches!(
e2,
DeviceErrorCode::SmxTempReadingError { board: 8 }
));
let p_start = DeviceErrorCode::from_code(9000);
assert_eq!(p_start.code(), 9000);
assert!(matches!(
p_start,
DeviceErrorCode::ProcessExitedAbnormally { process: 0 }
));
let p_end = DeviceErrorCode::from_code(9008);
assert_eq!(p_end.code(), 9008);
assert!(matches!(
p_end,
DeviceErrorCode::ProcessExitedAbnormally { process: 8 }
));
}
#[test]
fn bbb_boundaries_roundtrip() {
let b0 = DeviceErrorCode::from_code(520000);
assert_eq!(b0.code(), 520000);
assert!(matches!(
b0,
DeviceErrorCode::SmxChipBbbErrorNonce {
board: 0,
chip: ChipId::Id(0)
}
));
let b_all = DeviceErrorCode::from_code(520999);
assert_eq!(b_all.code(), 520999);
assert!(matches!(
b_all,
DeviceErrorCode::SmxChipBbbErrorNonce {
board: 0,
chip: ChipId::All
}
));
let b1_chip42 = DeviceErrorCode::from_code(521042);
assert_eq!(b1_chip42.code(), 521042);
assert!(matches!(
b1_chip42,
DeviceErrorCode::SmxChipBbbErrorNonce {
board: 1,
chip: ChipId::Id(42)
}
));
let b_last = DeviceErrorCode::from_code(569999);
assert_eq!(b_last.code(), 569999);
assert!(matches!(
b_last,
DeviceErrorCode::SmxChipBbbNoNonce {
board: 9,
chip: ChipId::All
}
));
}
#[test]
fn unknown_code() {
let err = DeviceErrorCode::from_code(99999);
assert_eq!(err.code(), 99999);
assert_eq!(err.reason(), "Unknown error");
assert_eq!(err.processing_method(), "N/A");
}
#[test]
fn deserialize_error_entry() {
let json = r#"{"531":"2025-03-12 14:52:35","reason":"Slot1 not found."}"#;
let entry: DeviceErrorEntry = serde_json::from_str(json).unwrap();
assert!(matches!(entry.code, ErrorCode::Device(code) if code.code() == 531));
assert_eq!(entry.timestamp, "2025-03-12 14:52:35");
assert_eq!(entry.reason, "Slot1 not found.");
}
#[test]
fn deserialize_error_entry_with_e_prefix() {
let json = r#"{"E2010":"2026-05-01 12:28:18","reason":"All pools disabled"}"#;
let entry: DeviceErrorEntry = serde_json::from_str(json).unwrap();
assert!(matches!(entry.code, ErrorCode::Device(code) if code.code() == 2010));
assert_eq!(entry.timestamp, "2026-05-01 12:28:18");
assert_eq!(entry.reason, "All pools disabled");
}
#[test]
fn deserialize_psu_error_code_with_e_prefix() {
let code: ErrorCode = serde_json::from_str(r#""E0x0001""#).unwrap();
assert!(
matches!(code, ErrorCode::Psu(psu) if psu.contains(PsuErrorCode::INPUT_VOLTAGE_LOW))
);
}
#[test]
fn deserialize_psu_error_code() {
let code: ErrorCode = serde_json::from_str(r#""0x0001""#).unwrap();
assert!(
matches!(code, ErrorCode::Psu(psu) if psu.contains(PsuErrorCode::INPUT_VOLTAGE_LOW))
);
assert_eq!(format!("{code:?}"), "E0x0001");
assert!(code.reason().contains("Input voltage"));
}
#[test]
fn deserialize_combined_psu_flags() {
let code: ErrorCode = serde_json::from_str(r#""0x0801""#).unwrap();
let ErrorCode::Psu(psu) = code else {
panic!("expected PSU code");
};
assert!(psu.contains(PsuErrorCode::INPUT_VOLTAGE_LOW));
assert!(psu.contains(PsuErrorCode::FAN_FAILURE));
assert!(code.reason().contains("Input voltage"));
assert!(code.reason().contains("Fan failure"));
}
#[test]
fn deserialize_psu_error_entry() {
let json = r#"{"0x0001":"2025-03-12 14:52:35","reason":"code: 0x1"}"#;
let entry: DeviceErrorEntry = serde_json::from_str(json).unwrap();
assert!(
matches!(entry.code, ErrorCode::Psu(psu) if psu.contains(PsuErrorCode::INPUT_VOLTAGE_LOW))
);
assert_eq!(entry.timestamp, "2025-03-12 14:52:35");
assert_eq!(entry.reason, "code: 0x1");
}
}