# -*- coding: utf-8 -*-
#
# Copyright (c) 2025 CSIRO Space and Astronomy.
#
# Distributed under the terms of the CSIRO Open Source Software Licence Agreement.
# See LICENSE for more info.
from pathlib import Path
from ska_low_cbf_fpga.hardware_info import FpgaHardwareInfo
from ska_low_cbf_fpga.icl_field import IclField
[docs]
def _int_from_mac_str(mac) -> int:
"""
Convert MAC address str to int.
:param mac: MAC address string that may contain seperator characters
e.g. "00:01:02:03:04:05"
"""
# we see ':' used as seperator, but let's handle other possibilities
return int(mac.translate(str.maketrans("", "", ":.- ")), 16)
[docs]
class AmiInfo(FpgaHardwareInfo):
"""
Hardware monitoring for AMI devices via sysfs.
"""
[docs]
def __init__(self, hwmon_dir: Path | str, sys_dir_0: Path | str):
if isinstance(hwmon_dir, str):
hwmon_dir = Path(hwmon_dir)
if isinstance(sys_dir_0, str):
sys_dir_0 = Path(sys_dir_0)
self._hwmon_dir = hwmon_dir
self._sys_dir = sys_dir_0
[docs]
def _read_hw(self, filename: Path | str):
"""Read text from a file in our hwmon directory."""
return self._hwmon_dir.joinpath(filename).read_text(encoding="ascii").strip()
[docs]
def _read_sys(self, filename: Path | str):
"""Read text from a file in our sysfs directory."""
return self._sys_dir.joinpath(filename).read_text(encoding="ascii").strip()
@property
def fpga_power(self) -> IclField[float]:
"""
Get FPGA power consumption in Watts.
"""
power = int(self._read_hw("power1_input")) / 1e6
return IclField(
description="FPGA power consumption in W",
format="%f",
type_=float,
value=power,
user_write=False,
)
@property
def fpga_temperature(self) -> IclField[int]:
"""
Get FPGA temperature in degrees Celsius.
"""
temperature = int(self._read_hw("temp2_input")) / 1e3
return IclField(
description="FPGA temperature in °C",
format="%d",
type_=int,
value=temperature,
user_write=False,
)
@property
def hbm_temperature(self) -> IclField[int]:
"""
Get HBM temperature in degrees Celsius.
"""
# this is called "1V2_VCC_HBM",
# not sure if it's a voltage regulator or the memory itself,
# but it's the closest we seem to have
temperature = int(self._read_hw("temp9_input")) / 1e3
return IclField(
description="HBM temperature in °C",
format="%d",
type_=int,
value=temperature,
user_write=False,
)
@property
def mac_addresses(self) -> IclField[str]:
"""
Get Ethernet MAC addresses.
:returns: Array of str, formatted like "00:01:02:03:04:05".
"""
first_mac = _int_from_mac_str(self._read_sys("mac_addr"))
n_macs = int(self._read_sys("mac_addr_count"))
last_mac = _int_from_mac_str(self._read_sys("mac_addr_n"))
macs = []
for mac_int in range(first_mac, first_mac + n_macs):
mac_hex = f"{mac_int:012x}"
macs.append(":".join(mac_hex[i : i + 2] for i in range(0, 12, 2)))
assert len(macs) == n_macs
assert last_mac == _int_from_mac_str(macs[-1])
return IclField(
description="Ethernet MAC addresses",
format="%s",
type_=str,
value=macs,
length=len(macs),
user_write=False,
)
@property
def pcie_12v_current(self) -> IclField[float]:
"""
Get PCIe 12V power rail's current.
"""
current = int(self._read_hw("curr7_input")) / 1e3
return IclField(
description="PCIe 12V current in Amperes",
format="%f",
type_=float,
value=current,
user_write=False,
)
@property
def pcie_12v_voltage(self) -> IclField[float]:
"""
Get PCIe 12V power rail voltage reading.
"""
voltage = int(self._read_hw("in6_input")) / 1e3
return IclField(
description="PCIe 12V power rail in volts",
format="%f",
type_=float,
value=voltage,
user_write=False,
)
@property
def power_supply_12v_current(self) -> IclField[float]:
"""
Get 12V AUX total current (sum of two).
"""
current_1 = int(self._read_hw("curr3_input")) / 1e3
current_2 = int(self._read_hw("curr4_input")) / 1e3
return IclField(
description="Total AUX 12V current in Amperes",
format="%f",
type_=float,
value=current_1 + current_2,
user_write=False,
)
@property
def power_supply_12v_voltage(self) -> IclField[float]:
"""
Get 12V AUX voltage (one of the two that deviates furthest from 12V).
"""
voltage_1 = int(self._read_hw("in2_input")) / 1e3
voltage_2 = int(self._read_hw("in3_input")) / 1e3
v1_error = abs(12 - voltage_1)
v2_error = abs(12 - voltage_2)
voltage = voltage_1 if v1_error > v2_error else voltage_2
return IclField(
description="Most deviant AUX 12 voltage in volts",
format="%f",
type_=float,
value=voltage,
user_write=False,
)