#pragma once // xlidar-driver — device self-diagnostics decoded from the data stream. #include #include #include #include namespace xlidar { struct ExtraInfo; // lidar_interface.hpp // OLEI Family A (0xFAF0) error_status bits, header byte [5]. Bits 3-7 are // reserved on the wire; a nonzero reserved bit is still reported as a fault. inline constexpr uint8_t kFaultMonitor = 1u << 0; // monitor / motor abnormal inline constexpr uint8_t kFaultVoltage = 1u << 1; // supply voltage out of range inline constexpr uint8_t kFaultTemperature = 1u << 2; // internal temperature abnormal // SICK TiM LMDscandata device status (low word; Telegram Listing). inline constexpr uint16_t kSickStatusError = 1u << 0; inline constexpr uint16_t kSickStatusPollutionWarning = 1u << 1; inline constexpr uint16_t kSickStatusPollutionError = 1u << 2; // SICK nanoScan3 General System State byte 0 (layout from sick_safetyscanners; // NOT verified on real hardware). inline constexpr uint8_t kNanoStateRunMode = 1u << 0; inline constexpr uint8_t kNanoStateStandby = 1u << 1; inline constexpr uint8_t kNanoStateContaminationWarning = 1u << 2; inline constexpr uint8_t kNanoStateContaminationError = 1u << 3; inline constexpr uint8_t kNanoStateReferenceContour = 1u << 4; inline constexpr uint8_t kNanoStateManipulation = 1u << 5; // RPLIDAR SDK health status values (sl_lidar_response_device_health_t.status). inline constexpr uint8_t kRplidarHealthOk = 0; inline constexpr uint8_t kRplidarHealthWarning = 1; inline constexpr uint8_t kRplidarHealthError = 2; // Device self-diagnostics decoded from the data stream. Fields the device // family doesn't carry stay std::nullopt (see docs/diagnostics.md for the // per-family wire layout). valid stays false until the driver has decoded one // full scan. struct Diagnostics { bool valid = false; std::string model = "AUTO"; std::string firmware; // e.g. "fw 1.32 hw 18"; empty if unknown uint32_t device_timestamp_ms = 0; // device clock; 0 if not on the wire // OLEI Family A error byte (0 = no fault; Family B/C don't carry it) uint8_t error_status = 0; bool monitor_fault() const { return (error_status & kFaultMonitor) != 0; } bool voltage_fault() const { return (error_status & kFaultVoltage) != 0; } bool temperature_fault() const { return (error_status & kFaultTemperature) != 0; } // OLEI Family A only — raw motor speed field, unit unverified std::optional rotation_raw; // OLEI Family C / V3 (GS1-5) only — raw passthroughs, bit meanings unverified std::optional scan_frequency_raw; std::optional input_status; std::optional output_status; std::optional field_status; std::optional status_flags; // SICK TiM — LMDscandata status pair (word0<<8)|word1 std::optional sick_device_status; bool sick_error() const { return sick_device_status && (*sick_device_status & kSickStatusError); } bool pollution_warning() const { return sick_device_status && (*sick_device_status & kSickStatusPollutionWarning); } bool pollution_error() const { return sick_device_status && (*sick_device_status & kSickStatusPollutionError); } // SICK nanoScan3 — General System State byte 0 std::optional nano_general_state; bool contamination_warning() const { return nano_general_state && (*nano_general_state & kNanoStateContaminationWarning); } bool contamination_error() const { return nano_general_state && (*nano_general_state & kNanoStateContaminationError); } bool manipulation() const { return nano_general_state && (*nano_general_state & kNanoStateManipulation); } // ESPE LGA60 — raw fault word from area frames (bit meanings unverified); // only present when the host polls area data. std::optional espe_error_status; bool espe_fault() const { return espe_error_status && *espe_error_status != 0; } // RPLIDAR — SDK getHealth() status (refreshed at open(); the streaming // protocol carries no health) plus the device error code that goes with it. std::optional rplidar_health_status; std::optional rplidar_error_code; bool rplidar_fault() const { return rplidar_health_status && *rplidar_health_status == kRplidarHealthError; } bool rplidar_warning() const { return rplidar_health_status && *rplidar_health_status == kRplidarHealthWarning; } // Fault = device says something is wrong now; warning = degraded but // still measuring (dirty optics, weak motor) — schedule cleaning/service. bool has_fault() const { return error_status != 0 || sick_error() || pollution_error() || contamination_error() || manipulation() || espe_fault() || rplidar_fault(); } bool has_warning() const { return pollution_warning() || contamination_warning() || rplidar_warning(); } bool healthy() const { return valid && !has_fault(); } }; // Decode the diagnostic fields of one scan; sets valid = true. // Defined inline in lidar_interface.hpp (needs the ExtraInfo definition, and // every plugin .so must carry its own copy). Diagnostics decode_diagnostics(const ExtraInfo& info); // One-line log summary: "no data" / "ok" / "WARN: pollution" / // "FAULT: voltage temperature". inline std::string to_string(const Diagnostics& d) { if (!d.valid) return "no data"; if (!d.has_fault()) return d.has_warning() ? std::string("WARN:") + (d.pollution_warning() ? " pollution" : "") + (d.contamination_warning() ? " contamination" : "") + (d.rplidar_warning() ? " rplidar" : "") : "ok"; std::string s = "FAULT:"; if (d.monitor_fault()) s += " monitor"; if (d.voltage_fault()) s += " voltage"; if (d.temperature_fault()) s += " temperature"; if (d.sick_error()) s += " device"; if (d.pollution_error()) s += " pollution"; if (d.contamination_error()) s += " contamination"; if (d.manipulation()) s += " manipulation"; if (d.espe_fault()) { char buf[24]; std::snprintf(buf, sizeof(buf), " espe(0x%04X)", *d.espe_error_status); s += buf; } if (d.rplidar_fault()) { char buf[32]; std::snprintf(buf, sizeof(buf), " rplidar(0x%04X)", d.rplidar_error_code ? *d.rplidar_error_code : 0); s += buf; } if (uint8_t rest = d.error_status & ~(kFaultMonitor | kFaultVoltage | kFaultTemperature)) { char buf[24]; std::snprintf(buf, sizeof(buf), " reserved(0x%02X)", rest); s += buf; } return s; } } // namespace xlidar