mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-31 13:03:02 +02:00
statd: refactor, use parent node for sensor relationships
For more advanced hardware with multiple sensor types per device
(e.g., SFP modules with temperature, voltage, current, and power
sensors), use the YANG parent/child relationship to group related
sensors together for better presentation.
Changes:
- Remove parent/parent-rel-pos deviations from infix-hardware.yang
- Create parent components (class: module) for multi-sensor devices
- Add parent references to child sensor components
- Add human-readable descriptions from hwmon labels
- Extend hwmon discovery to support voltage, current, and power
- Normalize sensor names: strip vendor prefixes (mt7915_phy0 -> phy0)
- Remove redundant TYPE column, clarify units (V -> VDC, add spaces)
- Simplify child sensor display by stripping parent prefix
- Fix "show system" to only show CPU temperature and fan speed
Example output from "show hardware":
NAME VALUE STATUS
===================================================
sfp1:
Rx Power 0.000 W ok
Tx Power 0.001 W ok
Vcc 3.35 VDC ok
Bias 0.006 A ok
Temperature 30.3 °C ok
sfp2:
Rx Power 0.000 W ok
Tx Power 0.001 W ok
Vcc 3.34 VDC ok
Bias 0.006 A ok
Temperature 32.0 °C ok
cpu 42.8 °C ok
phy0 47.0 °C ok
phy1 53.0 °C ok
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -46,6 +46,10 @@ module infix-hardware {
|
||||
base iahw:hardware-class;
|
||||
description "This identity is used to a VPD memory on the device.";
|
||||
}
|
||||
identity wifi {
|
||||
base iahw:hardware-class;
|
||||
description "This identity is used to describe a WiFi radio/PHY";
|
||||
}
|
||||
|
||||
deviation "/iehw:hardware/iehw:component/iehw:state/iehw:admin-state" {
|
||||
deviate add {
|
||||
@@ -58,12 +62,6 @@ module infix-hardware {
|
||||
deviation "/iehw:hardware/iehw:component/iehw:state/iehw:standby-state" {
|
||||
deviate not-supported;
|
||||
}
|
||||
deviation "/iehw:hardware/iehw:component/iehw:parent" {
|
||||
deviate not-supported;
|
||||
}
|
||||
deviation "/iehw:hardware/iehw:component/iehw:parent-rel-pos" {
|
||||
deviate not-supported;
|
||||
}
|
||||
deviation "/iehw:hardware/iehw:component/iehw:alias" {
|
||||
deviate not-supported;
|
||||
}
|
||||
|
||||
+19
-11
@@ -203,24 +203,32 @@ def system(args: List[str]) -> None:
|
||||
# Augment with runtime data
|
||||
runtime = {}
|
||||
|
||||
# Extract thermal sensors from hardware components
|
||||
thermal_zones = []
|
||||
# Extract CPU temperature and fan speed from hardware components
|
||||
cpu_temp = None
|
||||
fan_rpm = None
|
||||
if hardware_data and "ietf-hardware:hardware" in hardware_data:
|
||||
components = hardware_data.get("ietf-hardware:hardware", {}).get("component", [])
|
||||
for component in components:
|
||||
sensor_data = component.get("sensor-data", {})
|
||||
if sensor_data and sensor_data.get("value-type") == "celsius":
|
||||
# Convert from millidegrees to degrees
|
||||
if not sensor_data:
|
||||
continue
|
||||
|
||||
name = component.get("name", "")
|
||||
value_type = sensor_data.get("value-type")
|
||||
|
||||
# Only capture CPU temperature (ignore phy, sfp, etc.)
|
||||
if value_type == "celsius" and name == "cpu" and cpu_temp is None:
|
||||
temp_millidegrees = sensor_data.get("value", 0)
|
||||
temp_celsius = temp_millidegrees / 1000.0
|
||||
cpu_temp = temp_millidegrees / 1000.0
|
||||
|
||||
thermal_zones.append({
|
||||
"type": component.get("name", "unknown"),
|
||||
"temp": temp_celsius
|
||||
})
|
||||
# Capture fan speed if available
|
||||
elif value_type == "rpm" and fan_rpm is None:
|
||||
fan_rpm = sensor_data.get("value", 0)
|
||||
|
||||
if thermal_zones:
|
||||
runtime["thermal"] = thermal_zones
|
||||
if cpu_temp is not None:
|
||||
runtime["cpu_temp"] = cpu_temp
|
||||
if fan_rpm is not None:
|
||||
runtime["fan_rpm"] = fan_rpm
|
||||
|
||||
# Extract resource usage from system-state
|
||||
system_state = data.get("ietf-system:system-state", {})
|
||||
|
||||
@@ -171,15 +171,14 @@ class PadUsbPort:
|
||||
|
||||
|
||||
class PadSensor:
|
||||
name = 20
|
||||
type = 15
|
||||
value = 15
|
||||
name = 30
|
||||
value = 20
|
||||
status = 10
|
||||
|
||||
@classmethod
|
||||
def table_width(cls):
|
||||
"""Total width of sensor table"""
|
||||
return cls.name + cls.type + cls.value + cls.status
|
||||
"""Total width of sensor table (matches show system width)"""
|
||||
return cls.name + cls.value + cls.status
|
||||
|
||||
|
||||
class PadNtpSource:
|
||||
@@ -598,6 +597,8 @@ class Sensor:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', 'unknown')
|
||||
self.description = data.get('description') # Human-readable description
|
||||
self.parent = data.get('parent') # Parent component name
|
||||
sensor_data = data.get('sensor-data', {})
|
||||
self.value_type = sensor_data.get('value-type', 'unknown')
|
||||
self.value = sensor_data.get('value', 0)
|
||||
@@ -606,26 +607,77 @@ class Sensor:
|
||||
|
||||
def get_formatted_value(self):
|
||||
"""Convert sensor value based on scale and type"""
|
||||
# Handle temperature sensors
|
||||
if self.value_type == 'celsius':
|
||||
if self.value_scale == 'milli':
|
||||
temp_celsius = self.value / 1000.0
|
||||
# Color code like in show system
|
||||
# Color code based on temperature thresholds
|
||||
if temp_celsius < 60:
|
||||
return Decore.green(f"{temp_celsius:.1f}°C")
|
||||
return Decore.green(f"{temp_celsius:.1f} °C")
|
||||
elif temp_celsius < 75:
|
||||
return Decore.yellow(f"{temp_celsius:.1f}°C")
|
||||
return Decore.yellow(f"{temp_celsius:.1f} °C")
|
||||
else:
|
||||
return Decore.red(f"{temp_celsius:.1f}°C")
|
||||
return Decore.red(f"{temp_celsius:.1f} °C")
|
||||
else:
|
||||
return f"{self.value}°C"
|
||||
return f"{self.value} °C"
|
||||
|
||||
# Handle fan speed sensors
|
||||
elif self.value_type == 'rpm':
|
||||
return f"{self.value} RPM"
|
||||
|
||||
# Handle voltage sensors
|
||||
elif self.value_type == 'volts-DC':
|
||||
if self.value_scale == 'milli':
|
||||
volts = self.value / 1000.0
|
||||
return f"{volts:.2f} VDC"
|
||||
else:
|
||||
return f"{self.value} VDC"
|
||||
|
||||
# Handle current sensors
|
||||
elif self.value_type == 'amperes':
|
||||
if self.value_scale == 'milli':
|
||||
amps = self.value / 1000.0
|
||||
return f"{amps:.3f} A"
|
||||
else:
|
||||
return f"{self.value} A"
|
||||
|
||||
# Handle power sensors
|
||||
elif self.value_type == 'watts':
|
||||
if self.value_scale == 'micro':
|
||||
watts = self.value / 1000000.0
|
||||
return f"{watts:.3f} W"
|
||||
elif self.value_scale == 'milli':
|
||||
watts = self.value / 1000.0
|
||||
return f"{watts:.2f} W"
|
||||
else:
|
||||
return f"{self.value} W"
|
||||
|
||||
# For unknown sensor types, show raw value
|
||||
else:
|
||||
# For other sensor types, just show raw value
|
||||
return f"{self.value} {self.value_type}"
|
||||
|
||||
def print(self):
|
||||
def print(self, indent=0):
|
||||
import re
|
||||
row = f"{self.name:<{PadSensor.name}}"
|
||||
row += f"{self.value_type:<{PadSensor.type}}"
|
||||
# Add indentation for child sensors
|
||||
indent_str = " " * indent
|
||||
|
||||
# Determine display name: prefer description, fallback to name
|
||||
if self.description:
|
||||
# Use description if available (e.g., "WiFi Radio wlan0 (2.4 GHz)")
|
||||
display_name = self.description
|
||||
elif indent > 0 and self.parent:
|
||||
# Child sensor: strip parent prefix from name
|
||||
# "sfp1-RX_power" -> "RX_power" -> "Rx Power"
|
||||
display_name = self.name
|
||||
if display_name.startswith(self.parent + "-"):
|
||||
display_name = display_name[len(self.parent) + 1:]
|
||||
# Format: "RX_power" -> "Rx Power"
|
||||
display_name = display_name.replace('_', ' ').replace('-', ' ').title()
|
||||
else:
|
||||
# Standalone sensor without description: use name as-is
|
||||
display_name = self.name
|
||||
|
||||
row = f"{indent_str}{display_name:<{PadSensor.name - len(indent_str)}}"
|
||||
# For colored value, pad manually to account for ANSI codes
|
||||
value_str = self.get_formatted_value()
|
||||
# Count visible characters (strip ANSI codes for length calculation)
|
||||
@@ -1669,15 +1721,46 @@ def show_hardware(json):
|
||||
|
||||
if sensors:
|
||||
Decore.title("Sensors", width)
|
||||
|
||||
# Print header
|
||||
hdr = (f"{'NAME':<{PadSensor.name}}"
|
||||
f"{'TYPE':<{PadSensor.type}}"
|
||||
f"{'VALUE':<{PadSensor.value}}"
|
||||
f"{'STATUS':<{PadSensor.status}}")
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
# Build parent-child map
|
||||
children = {} # parent_name -> [child_components]
|
||||
standalone = [] # components without parents
|
||||
|
||||
for component in sensors:
|
||||
sensor = Sensor(component)
|
||||
sensor.print()
|
||||
parent = component.get("parent")
|
||||
if parent:
|
||||
if parent not in children:
|
||||
children[parent] = []
|
||||
children[parent].append(component)
|
||||
else:
|
||||
standalone.append(component)
|
||||
|
||||
# Get all parent modules (non-sensor components)
|
||||
modules = [c for c in components if c.get("class") == "iana-hardware:module"]
|
||||
|
||||
# Display modules with their child sensors (indented)
|
||||
for module in sorted(modules, key=lambda m: m.get("name", "")):
|
||||
module_name = module.get("name", "unknown")
|
||||
print(f"\n{module_name}:")
|
||||
|
||||
if module_name in children:
|
||||
for child in sorted(children[module_name], key=lambda c: c.get("name", "")):
|
||||
sensor = Sensor(child)
|
||||
sensor.print(indent=1)
|
||||
|
||||
# Display standalone sensors (no parent)
|
||||
if standalone:
|
||||
if modules:
|
||||
print() # Add blank line between modules and standalone
|
||||
for component in sorted(standalone, key=lambda c: c.get("name", "")):
|
||||
sensor = Sensor(component)
|
||||
sensor.print()
|
||||
|
||||
|
||||
def show_ntp(json):
|
||||
@@ -1777,20 +1860,33 @@ def show_system(json):
|
||||
percent = int((used / total * 100)) if total > 0 else 0
|
||||
print(f"{'Memory':<20}: {used} / {total} MB ({percent}% used)")
|
||||
|
||||
thermal = runtime.get("thermal", [])
|
||||
if thermal:
|
||||
for zone in thermal:
|
||||
zone_type = zone.get("type", "Unknown").replace("-thermal", "")
|
||||
temp = zone.get("temp", 0)
|
||||
# Color code temperature: green < 60, yellow 60-75, red > 75
|
||||
temp_str = f"{temp:.1f}°C"
|
||||
if temp < 60:
|
||||
# Show CPU temperature and fan speed on one line
|
||||
cpu_temp = runtime.get("cpu_temp")
|
||||
fan_rpm = runtime.get("fan_rpm")
|
||||
|
||||
if cpu_temp is not None or fan_rpm is not None:
|
||||
status_line = ""
|
||||
|
||||
# Add CPU temperature with color coding
|
||||
if cpu_temp is not None:
|
||||
temp_str = f"{cpu_temp:.1f} °C"
|
||||
if cpu_temp < 60:
|
||||
temp_colored = Decore.green(temp_str)
|
||||
elif temp < 75:
|
||||
elif cpu_temp < 75:
|
||||
temp_colored = Decore.yellow(temp_str)
|
||||
else:
|
||||
temp_colored = Decore.red(temp_str)
|
||||
print(f"{'Temperature':<20}: {temp_colored} ({zone_type})")
|
||||
status_line = f"CPU: {temp_colored}"
|
||||
|
||||
# Add fan speed if available
|
||||
if fan_rpm is not None:
|
||||
if status_line:
|
||||
status_line += f", Fan: {fan_rpm} RPM"
|
||||
else:
|
||||
status_line = f"Fan: {fan_rpm} RPM"
|
||||
|
||||
if status_line:
|
||||
print(f"{'Hardware':<20}: {status_line}")
|
||||
|
||||
disk = runtime.get("disk", [])
|
||||
# Filter out root partition (/) - it's read-only and shows confusing 100% usage
|
||||
|
||||
@@ -109,6 +109,302 @@ def motherboard_component(systemjson):
|
||||
return [component]
|
||||
|
||||
|
||||
def normalize_sensor_name(name):
|
||||
"""
|
||||
Normalize sensor names for cleaner display.
|
||||
|
||||
Examples:
|
||||
sfp_2 -> sfp2
|
||||
mt7915_phy0 -> phy0
|
||||
marvell_alaska_tomte_phy7 -> phy7
|
||||
cpu_thermal -> cpu
|
||||
pwmfan -> pwmfan
|
||||
|
||||
Strategy:
|
||||
1. Strip common suffixes like -thermal/_thermal
|
||||
2. Extract well-known sensor type names (phy, sfp, fan, etc.) from
|
||||
the end of the name, stripping any vendor/chipset prefix
|
||||
3. Remove underscores before trailing numbers (sfp_2 -> sfp2)
|
||||
"""
|
||||
import re
|
||||
|
||||
# Strip common suffixes
|
||||
name = name.replace("-thermal", "").replace("_thermal", "")
|
||||
|
||||
# Extract well-known sensor types from end of name, stripping any prefix
|
||||
# This handles: mt7915_phy0 -> phy0, marvell_alaska_phy7 -> phy7, etc.
|
||||
sensor_types = r'(phy|sfp|fan|temp|sensor|psu|cpu|gpu|memory|disk)'
|
||||
match = re.search(rf'.*_({sensor_types}\d*)$', name)
|
||||
if match:
|
||||
name = match.group(1)
|
||||
|
||||
# Remove underscores before trailing numbers (sfp_2 -> sfp2)
|
||||
name = re.sub(r'_(\d+)$', r'\1', name)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def get_wifi_phy_info():
|
||||
"""
|
||||
Discover WiFi PHYs and map them to bands and interface names.
|
||||
Returns dict: {phy_name: {band: str, iface: str, description: str}}
|
||||
|
||||
Example: {"phy0": {"band": "2.4 GHz", "iface": "wlan0", "description": "WiFi Radio (2.4 GHz)"}}
|
||||
"""
|
||||
phy_info = {}
|
||||
|
||||
try:
|
||||
# Enumerate PHYs from /sys/class/ieee80211/
|
||||
ieee80211_path = "/sys/class/ieee80211"
|
||||
if not os.path.exists(ieee80211_path):
|
||||
return phy_info
|
||||
|
||||
for phy in os.listdir(ieee80211_path):
|
||||
if not phy.startswith("phy"):
|
||||
continue
|
||||
|
||||
phy_path = os.path.join(ieee80211_path, phy)
|
||||
info = {"band": "Unknown", "iface": None, "description": None}
|
||||
|
||||
# Try to determine band from device path or hwmon name
|
||||
# The hwmon device usually tells us: mt7915_phy0, mt7915_phy1, etc.
|
||||
# We'll check supported frequencies to determine band
|
||||
try:
|
||||
# Read supported bands - check if device supports 5 GHz
|
||||
# Most dual-band chips expose phy0 as 2.4 GHz and phy1 as 5 GHz
|
||||
device_path = os.path.join(phy_path, "device")
|
||||
if os.path.exists(device_path):
|
||||
# Simple heuristic: phy0 is usually 2.4 GHz, phy1 is 5 GHz
|
||||
# This works for most MediaTek chips (mt7915, mt7921, etc.)
|
||||
if phy == "phy0":
|
||||
info["band"] = "2.4 GHz"
|
||||
elif phy == "phy1":
|
||||
info["band"] = "5 GHz"
|
||||
elif phy == "phy2":
|
||||
info["band"] = "6 GHz" # WiFi 6E
|
||||
except:
|
||||
pass
|
||||
|
||||
# Find associated interface by checking which interface has a phy80211 link to this PHY
|
||||
try:
|
||||
net_path = "/sys/class/net"
|
||||
if os.path.exists(net_path):
|
||||
for iface in os.listdir(net_path):
|
||||
phy_link = os.path.join(net_path, iface, "phy80211")
|
||||
if os.path.islink(phy_link):
|
||||
# Read the link target and extract PHY name
|
||||
try:
|
||||
link_target = os.readlink(phy_link)
|
||||
linked_phy = os.path.basename(link_target)
|
||||
if linked_phy == phy:
|
||||
info["iface"] = iface
|
||||
break
|
||||
except:
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
|
||||
# Build description
|
||||
if info["iface"] and info["band"] != "Unknown":
|
||||
info["description"] = f"WiFi Radio {info['iface']} ({info['band']})"
|
||||
elif info["band"] != "Unknown":
|
||||
info["description"] = f"WiFi Radio ({info['band']})"
|
||||
elif info["iface"]:
|
||||
info["description"] = f"WiFi Radio {info['iface']}"
|
||||
else:
|
||||
info["description"] = "WiFi Radio"
|
||||
|
||||
phy_info[phy] = info
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return phy_info
|
||||
|
||||
|
||||
def hwmon_sensor_components():
|
||||
"""
|
||||
Discover hwmon sensors and create sensor components with parent/child relationships.
|
||||
Returns a list of hardware components with sensor-data for temperature,
|
||||
fan, voltage, current, and power sensors.
|
||||
|
||||
For devices with multiple sensors (like SFP modules), creates:
|
||||
- A parent component representing the device (class: module/container)
|
||||
- Child sensor components that reference the parent via "parent" field
|
||||
|
||||
For simple devices with only one sensor, creates standalone sensor components.
|
||||
"""
|
||||
components = []
|
||||
device_sensors = {} # Track {device_base_name: [list of sensor components]}
|
||||
|
||||
def add_sensor(base_name, sensor_component):
|
||||
"""Helper to track sensors per device"""
|
||||
if base_name not in device_sensors:
|
||||
device_sensors[base_name] = []
|
||||
device_sensors[base_name].append(sensor_component)
|
||||
|
||||
try:
|
||||
hwmon_devices = glob.glob("/sys/class/hwmon/hwmon*")
|
||||
|
||||
for hwmon_path in hwmon_devices:
|
||||
try:
|
||||
name_path = os.path.join(hwmon_path, "name")
|
||||
if not HOST.exists(name_path):
|
||||
continue
|
||||
|
||||
device_name = HOST.read(name_path).strip()
|
||||
base_name = normalize_sensor_name(device_name)
|
||||
|
||||
# Helper to create sensor component with human-readable description
|
||||
def create_sensor(sensor_name, value, value_type, value_scale, label=None):
|
||||
component = {
|
||||
"name": sensor_name,
|
||||
"class": "iana-hardware:sensor",
|
||||
"sensor-data": {
|
||||
"value": value,
|
||||
"value-type": value_type,
|
||||
"value-scale": value_scale,
|
||||
"value-precision": 0,
|
||||
"value-timestamp": str(YangDate()),
|
||||
"oper-status": "ok"
|
||||
}
|
||||
}
|
||||
# Add human-readable description if we have a label
|
||||
if label:
|
||||
# Format label nicely: "RX_power" -> "RX Power", "VCC" -> "VCC"
|
||||
desc = label.replace('_', ' ').title()
|
||||
component["description"] = desc
|
||||
return component
|
||||
|
||||
# Temperature sensors
|
||||
for temp_file in glob.glob(os.path.join(hwmon_path, "temp*_input")):
|
||||
try:
|
||||
sensor_num = os.path.basename(temp_file).split('_')[0].replace('temp', '')
|
||||
value = int(HOST.read(temp_file).strip())
|
||||
label_file = os.path.join(hwmon_path, f"temp{sensor_num}_label")
|
||||
raw_label = None
|
||||
if HOST.exists(label_file):
|
||||
raw_label = HOST.read(label_file).strip()
|
||||
label = normalize_sensor_name(raw_label)
|
||||
sensor_name = f"{base_name}-{label}"
|
||||
else:
|
||||
sensor_name = base_name if sensor_num == '1' else f"{base_name}{sensor_num}"
|
||||
add_sensor(base_name, create_sensor(sensor_name, value, "celsius", "milli", raw_label))
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
# Fan sensors
|
||||
for fan_file in glob.glob(os.path.join(hwmon_path, "fan*_input")):
|
||||
try:
|
||||
sensor_num = os.path.basename(fan_file).split('_')[0].replace('fan', '')
|
||||
value = int(HOST.read(fan_file).strip())
|
||||
label_file = os.path.join(hwmon_path, f"fan{sensor_num}_label")
|
||||
raw_label = None
|
||||
if HOST.exists(label_file):
|
||||
raw_label = HOST.read(label_file).strip()
|
||||
label = normalize_sensor_name(raw_label)
|
||||
sensor_name = f"{base_name}-{label}"
|
||||
else:
|
||||
sensor_name = base_name if sensor_num == '1' else f"{base_name}{sensor_num}"
|
||||
add_sensor(base_name, create_sensor(sensor_name, value, "rpm", "units", raw_label))
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
# Voltage sensors
|
||||
for voltage_file in glob.glob(os.path.join(hwmon_path, "in*_input")):
|
||||
try:
|
||||
sensor_num = os.path.basename(voltage_file).split('_')[0].replace('in', '')
|
||||
value = int(HOST.read(voltage_file).strip())
|
||||
label_file = os.path.join(hwmon_path, f"in{sensor_num}_label")
|
||||
raw_label = None
|
||||
if HOST.exists(label_file):
|
||||
raw_label = HOST.read(label_file).strip()
|
||||
label = normalize_sensor_name(raw_label)
|
||||
sensor_name = f"{base_name}-{label}"
|
||||
else:
|
||||
raw_label = "voltage"
|
||||
sensor_name = f"{base_name}-voltage" if sensor_num == '0' else f"{base_name}-voltage{sensor_num}"
|
||||
add_sensor(base_name, create_sensor(sensor_name, value, "volts-DC", "milli", raw_label))
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
# Current sensors
|
||||
for current_file in glob.glob(os.path.join(hwmon_path, "curr*_input")):
|
||||
try:
|
||||
sensor_num = os.path.basename(current_file).split('_')[0].replace('curr', '')
|
||||
value = int(HOST.read(current_file).strip())
|
||||
label_file = os.path.join(hwmon_path, f"curr{sensor_num}_label")
|
||||
raw_label = None
|
||||
if HOST.exists(label_file):
|
||||
raw_label = HOST.read(label_file).strip()
|
||||
label = normalize_sensor_name(raw_label)
|
||||
sensor_name = f"{base_name}-{label}"
|
||||
else:
|
||||
raw_label = "current"
|
||||
sensor_name = f"{base_name}-current" if sensor_num == '1' else f"{base_name}-current{sensor_num}"
|
||||
add_sensor(base_name, create_sensor(sensor_name, value, "amperes", "milli", raw_label))
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
# Power sensors
|
||||
for power_file in glob.glob(os.path.join(hwmon_path, "power*_input")):
|
||||
try:
|
||||
sensor_num = os.path.basename(power_file).split('_')[0].replace('power', '')
|
||||
value = int(HOST.read(power_file).strip())
|
||||
label_file = os.path.join(hwmon_path, f"power{sensor_num}_label")
|
||||
raw_label = None
|
||||
if HOST.exists(label_file):
|
||||
raw_label = HOST.read(label_file).strip()
|
||||
label = normalize_sensor_name(raw_label)
|
||||
sensor_name = f"{base_name}-{label}"
|
||||
else:
|
||||
raw_label = "power"
|
||||
sensor_name = f"{base_name}-power" if sensor_num == '1' else f"{base_name}-power{sensor_num}"
|
||||
add_sensor(base_name, create_sensor(sensor_name, value, "watts", "micro", raw_label))
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
continue
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Now create parent/child relationships
|
||||
for base_name, sensors in device_sensors.items():
|
||||
if len(sensors) > 1:
|
||||
# Multiple sensors: create parent component
|
||||
parent = {
|
||||
"name": base_name,
|
||||
"class": "iana-hardware:module", # Use "module" for multi-sensor devices like SFP
|
||||
}
|
||||
components.append(parent)
|
||||
|
||||
# Add parent reference to all child sensors
|
||||
for sensor in sensors:
|
||||
sensor["parent"] = base_name
|
||||
components.append(sensor)
|
||||
else:
|
||||
# Single sensor: add without parent
|
||||
components.extend(sensors)
|
||||
|
||||
# Enrich WiFi PHY sensors with descriptive information
|
||||
wifi_info = get_wifi_phy_info()
|
||||
for component in components:
|
||||
name = component.get("name", "")
|
||||
# Match phy0, phy1, etc. sensors
|
||||
if name.startswith("phy") and name in wifi_info:
|
||||
phy = wifi_info[name]
|
||||
# Add WiFi-specific description
|
||||
component["description"] = phy["description"]
|
||||
# Optionally change class to wifi for WiFi PHY sensors
|
||||
if component.get("class") == "iana-hardware:sensor":
|
||||
# Keep as sensor but we could create a parent WiFi component later if needed
|
||||
pass
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def thermal_sensor_components():
|
||||
"""
|
||||
Discover thermal zones and create sensor components.
|
||||
@@ -138,7 +434,7 @@ def thermal_sensor_components():
|
||||
|
||||
# Create component with sensor-data
|
||||
# Component name: strip "-thermal" suffix for cleaner display
|
||||
component_name = zone_type.replace("-thermal", "")
|
||||
component_name = normalize_sensor_name(zone_type)
|
||||
|
||||
component = {
|
||||
"name": component_name,
|
||||
@@ -175,6 +471,7 @@ def operational():
|
||||
motherboard_component(systemjson) +
|
||||
vpd_components(systemjson) +
|
||||
usb_port_components(systemjson) +
|
||||
hwmon_sensor_components() +
|
||||
thermal_sensor_components() +
|
||||
[],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user