Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diagnostics Update #101

Merged
merged 22 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e7a3381
Pull diagnostics from the switches.
dboulware Nov 29, 2023
622d153
remove old undefined variable import.
dboulware Nov 29, 2023
f931575
Update allowed sensors and states in Diagnostic metadata.
dboulware Nov 29, 2023
6b7b2a0
Initial implementation of new spu diagnositcs.
dboulware Nov 30, 2023
d8eff11
typo fix.
dboulware Nov 30, 2023
79827a3
set optional fields to None.
dboulware Nov 30, 2023
9e9007f
Update Preselector lib to 3.1.0.
dboulware Nov 30, 2023
56d0dd4
Change scos_start and scos_uptime to software_start and software_uptime.
dboulware Dec 1, 2023
b127b32
Remove requirement to have SPU-x410 switch.
dboulware Dec 1, 2023
48298e5
fix typo in power_sensors metadata.
dboulware Dec 1, 2023
0b1a42d
change diagnostics to v2.0.0
dboulware Dec 1, 2023
2364a06
Move action_runtime into computer diagnostics.
dboulware Dec 1, 2023
c947a82
move action_runtime into computer diagnostics.
dboulware Dec 1, 2023
b0231d0
Not the value of the door_closed spu sensor because 0 indicates it is…
dboulware Dec 1, 2023
3fcefb3
use door_state in switch config instead of door_closed because it is …
dboulware Dec 1, 2023
690e147
change power sensor names.
dboulware Dec 1, 2023
7f000a3
increment version.
dboulware Dec 1, 2023
1047ff6
Remove unused variables.
dboulware Dec 1, 2023
98a6fd8
run pre-commit.
dboulware Dec 1, 2023
5c4f89c
Add log warnings when expected states or sensors are not found in swi…
dboulware Dec 1, 2023
34f00f6
Corrected type of noise_diode_path_enabled and changed power sensors …
dboulware Dec 1, 2023
666ec08
Merge branch 'master' of https://github.com/NTIA/scos-actions
dboulware Dec 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ classifiers = [
dependencies = [
"environs>=9.5.0",
"django>=3.2.18,<4.0",
"its_preselector @ git+https://github.com/NTIA/Preselector@3.0.2",
"its_preselector @ git+https://github.com/NTIA/Preselector@3.1.0",
"msgspec>=0.16.0,<1.0.0",
"numexpr>=2.8.3",
"numpy>=1.22.0",
Expand Down
2 changes: 1 addition & 1 deletion scos_actions/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "6.4.2"
__version__ = "7.0.0"
125 changes: 84 additions & 41 deletions scos_actions/actions/acquire_sea_data_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

Currently in development.
"""
import json
import logging
import lzma
import platform
Expand Down Expand Up @@ -122,6 +123,7 @@
FFT_DETECTOR = create_statistical_detector("FftMeanMaxDetector", ["mean", "max"])
PFP_M3_DETECTOR = create_statistical_detector("PfpM3Detector", ["min", "max", "mean"])


# Expected webswitch configuration:
PRESELECTOR_SENSORS = {
"temp": 1, # Internal temperature, deg C
Expand Down Expand Up @@ -704,47 +706,33 @@ def capture_diagnostics(self, action_start_tic: float, cpu_speeds: list) -> None
consecutive points as the action has been running.
"""
tic = perf_counter()
# Read SPU sensors
switch_diag = {}
all_switch_status = {}
# Add status for any switch
for switch in switches.values():
if switch.name == "SPU X410":
spu_diag = switch.get_status()
del spu_diag["name"]
del spu_diag["healthy"]
for sensor in SPU_SENSORS:
try:
value = switch.get_sensor_value(SPU_SENSORS[sensor])
spu_diag[sensor] = value
except:
logger.warning(f"Unable to read {sensor} from SPU x410")
try:
spu_diag["sigan_internal_temp"] = self.sigan.temperature
except:
logger.warning("Unable to read internal sigan temperature")
# Rename key for use with **
spu_diag["aux_28v_powered"] = spu_diag.pop("28v_aux_powered")

# Read preselector sensors
ps_diag = {}
for sensor in PRESELECTOR_SENSORS:
try:
value = preselector.get_sensor_value(PRESELECTOR_SENSORS[sensor])
ps_diag[sensor] = value
except:
logger.warning(f"Unable to read {sensor} from preselector")
for inpt in PRESELECTOR_DIGITAL_INPUTS:
try:
value = preselector.get_digital_input_value(
PRESELECTOR_DIGITAL_INPUTS[inpt]
)
ps_diag[inpt] = value
except:
logger.warning(f"Unable to read {inpt} from preselector")
switch_status = switch.get_status()
del switch_status["name"]
del switch_status["healthy"]
all_switch_status.update(switch_status)

self.set_ups_states(all_switch_status, switch_diag)
self.add_temperature_and_humidity_sensors(all_switch_status, switch_diag)
self.add_power_sensors(all_switch_status, switch_diag)
self.add_power_states(all_switch_status, switch_diag)
if "door_state" in all_switch_status:
switch_diag["door_closed"] = not bool(all_switch_status["door_state"])

#Read preselector sensors
ps_diag = preselector.get_status()
del ps_diag["name"]
del ps_diag["healthy"]

# Read computer performance metrics
cpu_diag = { # Start with CPU min/max/mean speeds
"cpu_max_clock": round(max(cpu_speeds), 1),
"cpu_min_clock": round(min(cpu_speeds), 1),
"cpu_mean_clock": round(np.mean(cpu_speeds), 1),
"action_runtime": round(perf_counter() - action_start_tic, 2),
}
try: # Computer uptime (days)
cpu_diag["cpu_uptime"] = round(get_cpu_uptime_seconds() / (60 * 60 * 24), 2)
Expand Down Expand Up @@ -775,13 +763,13 @@ def capture_diagnostics(self, action_start_tic: float, cpu_speeds: list) -> None
except:
logger.warning("Failed to get CPU overheating status")
try: # SCOS start time
cpu_diag["scos_start"] = convert_datetime_to_millisecond_iso_format(
cpu_diag["software_start"] = convert_datetime_to_millisecond_iso_format(
start_time
)
except:
logger.warning("Failed to get SCOS start time")
try: # SCOS uptime
cpu_diag["scos_uptime"] = get_days_up()
cpu_diag["software_uptime"] = get_days_up()
except:
logger.warning("Failed to get SCOS uptime")
try: # SSD SMART data
Expand All @@ -807,15 +795,73 @@ def capture_diagnostics(self, action_start_tic: float, cpu_speeds: list) -> None
diagnostics = {
"datetime": utils.get_datetime_str_now(),
"preselector": ntia_diagnostics.Preselector(**ps_diag),
"spu": ntia_diagnostics.SPU(**spu_diag),
"spu": ntia_diagnostics.SPU(**switch_diag),
"computer": ntia_diagnostics.Computer(**cpu_diag),
"software": ntia_diagnostics.Software(**software_diag),
"action_runtime": round(perf_counter() - action_start_tic, 2),
}

# Add diagnostics to SigMF global object
self.sigmf_builder.set_diagnostics(ntia_diagnostics.Diagnostics(**diagnostics))

def set_ups_states(self, all_switch_status: dict, switch_diag: dict):
if "ups_power_state" in all_switch_status:
switch_diag["battery_backup"] = not all_switch_status["ups_power_state"]
if "ups_battery_level" in all_switch_status:
switch_diag["low_battery"] = not all_switch_status["ups_battery_level"]
if "ups_state" in all_switch_status:
switch_diag["ups_healthy"] = not all_switch_status["ups_state"]
if "ups_battery_state" in all_switch_status:
switch_diag["replace_battery"] = not all_switch_status["ups_battery_state"]

def add_temperature_and_humidity_sensors(self, all_switch_status: dict, switch_diag: dict):
switch_diag["temperature_sensors"] = []
if "internal_temp" in all_switch_status:
switch_diag["temperature_sensors"].append(
{"name": "internal_temp", "value": all_switch_status["internal_temp"]})
try:
switch_diag["temperature_sensors"].append({"name": "sigan_internal_temp", "value": self.sigan.temperature})
except:
logger.warning("Unable to read internal sigan temperature")
if "tec_intake_temp" in all_switch_status:
switch_diag["temperature_sensors"].append({"name": "tec_intake_temp", "value": all_switch_status["tec_intake_temp"]})

if "tec_exhaust_temp" in all_switch_status:
switch_diag["temperature_sensors"].append(
{"name": "tec_exhaust_temp", "value": all_switch_status["tec_exhaust_temp"]})

if "internal_humidity" in all_switch_status:
switch_diag["humidity_sensors"] = [
{"name": "internal_humidity", "value": all_switch_status["internal_humidity"]}]

def add_power_sensors(self, all_switch_status: dict, switch_diag: dict ):
switch_diag["power_sensors"] = []
if "power_monitor5v" in all_switch_status:
switch_diag["power_sensors"].append(
{"name": "5v Monitor", "value": all_switch_status["power_monitor5v"], "expected_value": 5.0})
if "power_monitor15v" in all_switch_status:
switch_diag["power_sensors"].append(
{"name": "15v Monitor", "value": all_switch_status["power_monitor15v"], "expected_value": 15.0})
if "power_monitor24v" in all_switch_status:
switch_diag["power_sensors"].append(
{"name": "24v Monitor", "value": all_switch_status["power_monitor24v"], "expected_value": 24.0})
if "power_monitor28v" in all_switch_status:
switch_diag["power_sensors"].append(
{"name": "28v Monitor", "value": all_switch_status["power_monitor28v"], "expected_value": 28.0})

def add_heating_cooling(self, all_switch_status: dict, switch_diag: dict):
if "heating" in all_switch_status:
switch_diag["heating"] = all_switch_status["heating"]
if "cooling" in all_switch_status:
switch_diag["cooling"] = all_switch_status["cooling"]

def add_power_states(self, all_switch_status: dict, switch_diag: dict):
if "sigan_powered" in all_switch_status:
switch_diag["sigan_powered"] = all_switch_status["sigan_powered"]
if "temperature_control_powered" in all_switch_status:
switch_diag["temperature_control_powered"] = all_switch_status["temperature_control_powered"]
if "preselector_powered" in all_switch_status:
switch_diag["preselector_powered"] = all_switch_status["preselector_powered"]

def create_global_sensor_metadata(self):
# Add (minimal) ntia-sensor metadata to the sigmf_builder:
# sensor ID, serial numbers for preselector, sigan, and computer
Expand All @@ -836,9 +882,6 @@ def test_required_components(self):
msg = "Acquisition failed: signal analyzer is not available"
trigger_api_restart.send(sender=self.__class__)
raise RuntimeError(msg)
if "SPU X410" not in [s.name for s in switches.values()]:
msg = "Configuration error: no switch configured with name 'SPU X410'"
raise RuntimeError(msg)
if not self.sigan.healthy():
trigger_api_restart.send(sender=self.__class__)
return None
Expand Down
2 changes: 1 addition & 1 deletion scos_actions/metadata/sigmf_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
{
"name": "ntia-diagnostics",
"version": "v1.1.2",
"version": "v2.0.0",
"optional": True,
},
{
Expand Down
78 changes: 56 additions & 22 deletions scos_actions/metadata/structs/ntia_diagnostics.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, List

import msgspec

Expand All @@ -11,43 +11,76 @@ class Preselector(msgspec.Struct, **SIGMF_OBJECT_KWARGS):

:param temp: Temperature inside the preselector enclosure, in degrees Celsius.
:param noise_diode_temp: Temperature of the noise diode, in degrees Celsius.
:param noise_diode_powered: Boolean indicating if the noise diode is powered.
:param lna_powered: Boolean indicating if the lna is powered.
:param lna_temp: Temparature of the low noise amplifier, in degrees Celsius.
:param antenna_path_enabled: Boolean indicating if the antenna path is enabled.
:param noise_diode_path_enabled: Boolean indicating if the noise diode path is enabled.
:param humidity: Relative humidity inside the preselector enclosure, as a percentage.
:param door_closed: Indicates whether the door of the enclosure is closed.
"""

temp: Optional[float] = None
noise_diode_temp: Optional[float] = None
noise_diode_powered: Optional[bool] = None
lna_powered: Optional[bool] = None
lna_temp: Optional[float] = None
antenna_path_enabled: Optional[bool] = None
noise_diode_path_enabled: Optional = None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type annotation should be Optional[bool]

humidity: Optional[float] = None
door_closed: Optional[bool] = False

class DiagnosticSensor(msgspec.Struct, **SIGMF_OBJECT_KWARGS):
"""
Interface for generating `ntia-diagnostics` `DiagnosticSensor` objects.

:param name: The name of the sensor
:param value: The value provided by the sensor
:param maximum_allowed: The maximum value allowed from the sensor before action should be taken
:param mimimum_allowed: The minimum value allowed from the sensor before action should be taken
:param description: A description of the sensor
"""
name: str
value: float
maximum_allowed: Optional[float] = None
minimum_allowed: Optional[float] = None
expected_value: Optional[float] = None
description: Optional[str] = None

class SPU(
msgspec.Struct, rename={"aux_28v_powered": "28v_aux_powered"}, **SIGMF_OBJECT_KWARGS
):
msgspec.Struct, **SIGMF_OBJECT_KWARGS):
"""
Interface for generating `ntia-diagnostics` `SPU` objects.

:param rf_tray_powered: Indicates if the RF tray is powered.
:param cooling: Boolean indicating if the cooling is enabled.
:param heating: Boolean indicating if the heat is enabled.
:param preselector_powered: Indicates if the preselector is powered.
:param aux_28v_powered: Indicates if the 28V aux power is on.
:param pwr_box_temp: Ambient temperature in power distribution box,
in degrees Celsius.
:param pwr_box_humidity: Humidity in power distribution box, as a
percentage.
:param rf_box_temp: Ambient temperature in the RF box (around the signal
analyzer), in degrees Celsius.
:param sigan_internal_temp: Internal temperature reported by the signal analyzer.
:param sigan_powered: Boolean indicating if the signal analyzer is powered.
:param temperature_control_powered: Boolean indicating TEC AC power.
:param battery_backup: Boolean indicating if it is running on battery backup.
:param low_battery: Boolean indicating if the battery is low.
:param ups_healthy: Indicates trouble with UPS.
:param replace_battery: Boolean indicating if the ups battery needs replacing.
:param temperature_sensors: List of temperature sensor values
:param humidity_sensors: List of humidity sensor values
:param power_sensors: List of power sensor values
:param door_closed: Boolean indicating if the door is closed
"""

rf_tray_powered: Optional[bool] = None
cooling: Optional[bool] = None
heating: Optional[bool] = None
sigan_powered: Optional[bool] = None
temperature_control_powered: Optional[bool] = None
preselector_powered: Optional[bool] = None
aux_28v_powered: Optional[bool] = None
pwr_box_temp: Optional[float] = None
pwr_box_humidity: Optional[float] = None
rf_box_temp: Optional[float] = None
sigan_internal_temp: Optional[float] = None

battery_backup: Optional[bool] = None
low_battery: Optional[bool] = None
ups_healthy: Optional[bool] = None
replace_battery: Optional[bool] = None

temperature_sensors: Optional[List[DiagnosticSensor]] = None
humidity_sensors: Optional[List[DiagnosticSensor]] = None
power_sensors: Optional[List[DiagnosticSensor]] = None
door_closed: Optional[bool] = None


class SsdSmartData(msgspec.Struct, **SIGMF_OBJECT_KWARGS):
Expand Down Expand Up @@ -110,12 +143,13 @@ class Computer(msgspec.Struct, **SIGMF_OBJECT_KWARGS):
cpu_mean_clock: Optional[float] = None
cpu_uptime: Optional[float] = None
action_cpu_usage: Optional[float] = None
action_runtime: Optional[float] = None
system_load_5m: Optional[float] = None
memory_usage: Optional[float] = None
cpu_overheating: Optional[bool] = None
cpu_temp: Optional[float] = None
scos_start: Optional[str] = None
scos_uptime: Optional[float] = None
software_start: Optional[str] = None
software_uptime: Optional[float] = None
ssd_smart_data: Optional[SsdSmartData] = None


Expand Down Expand Up @@ -169,4 +203,4 @@ class Diagnostics(msgspec.Struct, **SIGMF_OBJECT_KWARGS):
spu: Optional[SPU] = None
computer: Optional[Computer] = None
software: Optional[Software] = None
action_runtime: Optional[float] = None