mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-02 13:53:01 +02:00
confd: add support for temperature sensors in ietf-hardware.yang
- Remove class deviation to allow iana-hardware:sensor - Populate sensor operational data from /sys/class/thermal - Extend 'show hardware' Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -162,6 +162,24 @@ class PadUsbPort:
|
||||
title = 30
|
||||
name = 20
|
||||
state = 10
|
||||
oper = 10
|
||||
|
||||
@classmethod
|
||||
def table_width(cls):
|
||||
"""Total width of USB port table"""
|
||||
return cls.name + cls.state + cls.oper
|
||||
|
||||
|
||||
class PadSensor:
|
||||
name = 20
|
||||
type = 15
|
||||
value = 15
|
||||
status = 10
|
||||
|
||||
@classmethod
|
||||
def table_width(cls):
|
||||
"""Total width of sensor table"""
|
||||
return cls.name + cls.type + cls.value + cls.status
|
||||
|
||||
|
||||
class PadNtpSource:
|
||||
@@ -567,11 +585,54 @@ class USBport:
|
||||
self.data = data
|
||||
self.name = data.get('name', '')
|
||||
self.state = get_json_data('', self.data, 'state', 'admin-state')
|
||||
self.oper = get_json_data('', self.data, 'state', 'oper-state')
|
||||
|
||||
def print(self):
|
||||
#print(self.name)
|
||||
row = f"{self.name:<{PadUsbPort.name}}"
|
||||
row += f"{self.state:<{PadUsbPort.state}}"
|
||||
row += f"{self.oper:<{PadUsbPort.oper}}"
|
||||
print(row)
|
||||
|
||||
|
||||
class Sensor:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.name = data.get('name', 'unknown')
|
||||
sensor_data = data.get('sensor-data', {})
|
||||
self.value_type = sensor_data.get('value-type', 'unknown')
|
||||
self.value = sensor_data.get('value', 0)
|
||||
self.value_scale = sensor_data.get('value-scale', 'units')
|
||||
self.oper_status = sensor_data.get('oper-status', 'unknown')
|
||||
|
||||
def get_formatted_value(self):
|
||||
"""Convert sensor value based on scale and type"""
|
||||
if self.value_type == 'celsius':
|
||||
if self.value_scale == 'milli':
|
||||
temp_celsius = self.value / 1000.0
|
||||
# Color code like in show system
|
||||
if temp_celsius < 60:
|
||||
return Decore.green(f"{temp_celsius:.1f}°C")
|
||||
elif temp_celsius < 75:
|
||||
return Decore.yellow(f"{temp_celsius:.1f}°C")
|
||||
else:
|
||||
return Decore.red(f"{temp_celsius:.1f}°C")
|
||||
else:
|
||||
return f"{self.value}°C"
|
||||
else:
|
||||
# For other sensor types, just show raw value
|
||||
return f"{self.value} {self.value_type}"
|
||||
|
||||
def print(self):
|
||||
import re
|
||||
row = f"{self.name:<{PadSensor.name}}"
|
||||
row += f"{self.value_type:<{PadSensor.type}}"
|
||||
# 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)
|
||||
visible_len = len(re.sub(r'\x1b\[[0-9;]*m', '', value_str))
|
||||
padding = PadSensor.value - visible_len
|
||||
row += value_str + (' ' * padding)
|
||||
row += f"{self.oper_status:<{PadSensor.status}}"
|
||||
print(row)
|
||||
|
||||
|
||||
@@ -1568,17 +1629,46 @@ def show_hardware(json):
|
||||
print("Error, top level \"ietf-hardware:component\" missing")
|
||||
sys.exit(1)
|
||||
|
||||
hdr = (f"{'NAME':<{PadUsbPort.name}}"
|
||||
f"{'STATE':<{PadUsbPort.state}}")
|
||||
Decore.title("USB PORTS", PadUsbPort.title) # TODO: could be len(hdr)
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
components = get_json_data({}, json, "ietf-hardware:hardware", "component")
|
||||
|
||||
for component in components:
|
||||
if component.get("class") == "infix-hardware:usb":
|
||||
# Separate components by type
|
||||
usb_ports = [c for c in components if c.get("class") == "infix-hardware:usb"]
|
||||
sensors = [c for c in components if c.get("class") == "iana-hardware:sensor"]
|
||||
|
||||
# Determine overall width (use the wider of the two sections)
|
||||
width = max(PadUsbPort.table_width(), PadSensor.table_width())
|
||||
|
||||
# Display full-width inverted heading
|
||||
print(Decore.invert(f"{'HARDWARE COMPONENTS':<{width}}"))
|
||||
print()
|
||||
|
||||
# USB Ports section
|
||||
if usb_ports:
|
||||
Decore.title("USB Ports", width)
|
||||
hdr = (f"{'NAME':<{PadUsbPort.name}}"
|
||||
f"{'STATE':<{PadUsbPort.state}}"
|
||||
f"{'OPER':<{PadUsbPort.oper}}")
|
||||
# Pad header to full width
|
||||
hdr = f"{hdr:<{width}}"
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
for component in usb_ports:
|
||||
port = USBport(component)
|
||||
port.print()
|
||||
print()
|
||||
|
||||
# Sensors section
|
||||
if sensors:
|
||||
Decore.title("Sensors", width)
|
||||
hdr = (f"{'NAME':<{PadSensor.name}}"
|
||||
f"{'TYPE':<{PadSensor.type}}"
|
||||
f"{'VALUE':<{PadSensor.value}}"
|
||||
f"{'STATUS':<{PadSensor.status}}")
|
||||
print(Decore.invert(hdr))
|
||||
|
||||
for component in sensors:
|
||||
sensor = Sensor(component)
|
||||
sensor.print()
|
||||
|
||||
|
||||
def show_ntp(json):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import datetime
|
||||
import os
|
||||
import glob
|
||||
|
||||
from .common import insert
|
||||
from .common import insert, YangDate
|
||||
from .host import HOST
|
||||
|
||||
|
||||
@@ -75,6 +76,63 @@ def usb_port_components(systemjson):
|
||||
return ports
|
||||
|
||||
|
||||
def thermal_sensor_components():
|
||||
"""
|
||||
Discover thermal zones and create sensor components.
|
||||
Returns a list of hardware components with sensor-data.
|
||||
"""
|
||||
components = []
|
||||
|
||||
try:
|
||||
# Find all thermal zones
|
||||
thermal_zones = glob.glob("/sys/class/thermal/thermal_zone*")
|
||||
|
||||
for zone_path in thermal_zones:
|
||||
try:
|
||||
# Read zone type (e.g., "cpu-thermal", "gpu-thermal")
|
||||
type_path = os.path.join(zone_path, "type")
|
||||
if not HOST.exists(type_path):
|
||||
continue
|
||||
|
||||
zone_type = HOST.read(type_path).strip()
|
||||
|
||||
# Read temperature in millidegrees Celsius
|
||||
temp_path = os.path.join(zone_path, "temp")
|
||||
if not HOST.exists(temp_path):
|
||||
continue
|
||||
|
||||
temp_millidegrees = int(HOST.read(temp_path).strip())
|
||||
|
||||
# Create component with sensor-data
|
||||
# Component name: strip "-thermal" suffix for cleaner display
|
||||
component_name = zone_type.replace("-thermal", "")
|
||||
|
||||
component = {
|
||||
"name": component_name,
|
||||
"class": "iana-hardware:sensor",
|
||||
"sensor-data": {
|
||||
"value": temp_millidegrees,
|
||||
"value-type": "celsius",
|
||||
"value-scale": "milli",
|
||||
"value-precision": 0,
|
||||
"value-timestamp": str(YangDate()),
|
||||
"oper-status": "ok"
|
||||
}
|
||||
}
|
||||
|
||||
components.append(component)
|
||||
|
||||
except (FileNotFoundError, ValueError, IOError):
|
||||
# Skip this thermal zone if we can't read it
|
||||
continue
|
||||
|
||||
except Exception:
|
||||
# If we can't access /sys/class/thermal at all, just return empty list
|
||||
pass
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def operational():
|
||||
systemjson = HOST.read_json("/run/system.json")
|
||||
|
||||
@@ -83,6 +141,7 @@ def operational():
|
||||
"component":
|
||||
vpd_components(systemjson) +
|
||||
usb_port_components(systemjson) +
|
||||
thermal_sensor_components() +
|
||||
[],
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user