Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
55 changes: 55 additions & 0 deletions docs/modules/thermalSensorControllerModules/virtual_commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Thermal Sensor YAML Command Definitions

**Directory:** `framework/core/thermalSensorControllerModules`

## Overview

This document consolidates Thermal Sensor YAML command definitions used by the virtual controller (`virtualThermalSensorController`). The payloads are posted to the vcomponent control-plane endpoint (`/api/postKVP`) with top-level node `IThermalSensor`.

---

## 1. temperature_update

### Purpose

This command injects a raw temperature reading. The vcomponent evaluates per-sensor thresholds and automatically emits thermal state transitions. This is the supported control-plane command for thermal event simulation.

### YAML Content

```yaml
IThermalSensor:
command: temperature_update
sensorName: SoC Die
temperatureCelsius: 100.0
timestampMonotonicMs: 1710000000000
```

### Parameters

| Parameter | Type | Description |
|---|---|---|
| `command` | string | Must be `temperature_update`. |
| `sensorName` | string | Sensor name providing the reading. |
| `temperatureCelsius` | float | Temperature value in Celsius. |
| `timestampMonotonicMs` | int | Reading timestamp in milliseconds. |

### Auto-transition Behavior

Based on current state and configured thresholds, `temperature_update` drives transitions such as:

- `NORMAL` → `CRITICAL_TEMPERATURE_EXCEEDED`
- `CRITICAL_TEMPERATURE_EXCEEDED` → `CRITICAL_TEMPERATURE_RECOVERED`
- `CRITICAL_TEMPERATURE_RECOVERED` → `NORMAL`
- `CRITICAL_TEMPERATURE_EXCEEDED` or `CRITICAL_TEMPERATURE_RECOVERED` → `CRITICAL_SHUTDOWN_IMMINENT`

> **Note:** Once in `CRITICAL_SHUTDOWN_IMMINENT`, recovery is intentionally suppressed by policy.

---

## Summary

Thermal Sensor controller uses one YAML command payload:

- **Temperature Update (`temperature_update`)**: Threshold logic computes transitions automatically.

These definitions are used for thermal event simulation and validation in vDevice test flows.
Comment on lines +51 to +55
69 changes: 69 additions & 0 deletions docs/modules/thermal_sensor_controller_modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Thermal Sensor Controller Modules

**Directory:** `framework/core/thermalSensorControllerModules`

**Purpose:**

The thermal sensor controller modules provide a pluggable set of implementations for simulating or interacting with thermal sensor hardware during L3 testing. Each implementation conforms to a common interface, allowing the factory (`ThermalSensorController`) to select the appropriate backend based on rack configuration without any changes to test logic.

---

**Key Classes:**

* **ThermalSensorControllerInterface** (`ThermalSensorControllerInterface.py`):
* Abstract base class that all thermal sensor controller implementations must inherit.
* Defines the two mandatory methods:
* `triggerThermalStateChange(state, sensorName, temperatureCelsius, timestampMonotonicMs)` — directly commands a state transition.
* `injectTemperatureUpdate(sensorName, temperatureCelsius, timestampMonotonicMs)` — injects a raw temperature reading and lets the vcomponent derive the state.

* **virtualThermalSensorController** (`virtualThermalSensorController.py`):
* Used in vDevice (software-only) test flows.
* Posts YAML command payloads to the vcomponent control-plane endpoint (`/api/postKVP`).
* See [`thermalSensorControllerModules/virtual_commands.md`](thermalSensorControllerModules/virtual_commands.md) for full YAML payload definitions.

* **actualThermalSensorController** (`actualThermalSensorController.py`):
* Used for real hardware flows where thermal events are raised by platform firmware.
* Prompts the test operator via `utUserResponse` to manually trigger the required thermal condition and confirm the result.

* **programmableThermalChamber** (`programmableThermalChamber.py`):
* Drives a programmable thermal chamber through configured shell command templates.
* Supports a dedicated SSH control session to the chamber if an `address` is provided.

* **smartSwitchThermalController** (`smartSwitchThermalController.py`):
* Controls a heater or blower via a smart power switch (Kasa, Tapo, APC, etc.) using the RAFT `powerControlClass` abstraction.
* Timed heating/cooling pulses are derived from per-sensor configuration keys such as `heating_duration_seconds` and `seconds_per_degree_celsius`.

---

**How to Use:**

1. **Rack Configuration:** Add a `thermalSensorController` block to the device entry in the rack YAML:

```yaml
thermalSensorController:
type: virtual # vdevice | actual | programmable-thermal-chamber | smartswitch_thermalcontroller
control_port: 8081
```

2. **Initialization:** The helper class instantiates `ThermalSensorController` (factory in `thermalSensorController.py`), which resolves the correct implementation automatically.

3. **Triggering Events in Tests:**

```python
# Inject a temperature reading (vDevice flow)
self.thermalSensorDevice.injectTemperatureUpdate(
sensorName="SoC Die",
temperatureCelsius=100.0)

# Force a direct state transition
self.thermalSensorDevice.triggerThermalStateChange(
state="CRITICAL_TEMPERATURE_EXCEEDED",
sensorName="SoC Die",
temperatureCelsius=97.0)
```

---

**Related Documents:**

* [`thermalSensorControllerModules/virtual_commands.md`](thermalSensorControllerModules/virtual_commands.md) — YAML payload reference for the virtual controller.
81 changes: 81 additions & 0 deletions framework/core/thermalSensorController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
#** *****************************************************************************
# *
# * Copyright 2026 RDK Management
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.apache.org/licenses/LICENSE-2.0
# *
#* ******************************************************************************

from framework.core.thermalSensorControllerModules.actualThermalSensorController import actualThermalSensorController
from framework.core.thermalSensorControllerModules.programmableThermalChamber import programmableThermalChamber
from framework.core.thermalSensorControllerModules.smartSwitchThermalController import smartSwitchThermalController
from framework.core.thermalSensorControllerModules.virtualThermalSensorController import virtualThermalSensorController


class ThermalSensorController:
"""
Factory class that selects the appropriate thermal sensor implementation.

The first argument may be either the legacy platform string or a controller
configuration dictionary from the rack config. This keeps the existing
vDevice flow intact while allowing explicit controller types such as a
programmable thermal chamber.
"""

VIRTUAL_CONTROLLER_TYPES = {
"vdevice",
"virtual",
"virtual-thermalsensor-client",
}

PROGRAMMABLE_CHAMBER_TYPES = {
"programmable-thermal-chamber",
"thermal-chamber",
"chamber",
}

SMART_SWITCH_CONTROLLER_TYPES = {
"smartswitch_thermalcontroller",
"smartswitch-thermalcontroller",
"smart-switch-thermalcontroller",
}

ACTUAL_CONTROLLER_TYPES = {
"actual",
"actual-thermalsensor-client",
"hardware",
"manual",
}

def __new__(cls, controllerConfig, session, prompt: str = "~#", port: int = 8080):
controllerType, controllerSettings, prompt, port = cls._normaliseControllerConfig(
controllerConfig, prompt, port)
if controllerType in cls.VIRTUAL_CONTROLLER_TYPES:
return virtualThermalSensorController(session, prompt, port)
Comment on lines +55 to +59
if controllerType in cls.PROGRAMMABLE_CHAMBER_TYPES:
return programmableThermalChamber(session, prompt, port, controllerSettings)
if controllerType in cls.SMART_SWITCH_CONTROLLER_TYPES:
return smartSwitchThermalController(session, prompt, port, controllerSettings)
return actualThermalSensorController(session, prompt, port)

@classmethod
def _normaliseControllerConfig(cls, controllerConfig, prompt: str, port: int):
if isinstance(controllerConfig, dict):
controllerSettings = dict(controllerConfig)
controllerType = str(
controllerSettings.get("type") or controllerSettings.get("platform") or ""
).strip().lower()
prompt = controllerSettings.get("prompt", prompt)
port = controllerSettings.get(
"control_port",
controllerSettings.get("port", port),
)
return controllerType, controllerSettings, prompt, port
Comment on lines +73 to +78

controllerType = str(controllerConfig or "").strip().lower()
return controllerType, {}, prompt, port
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
#** *****************************************************************************
# *
# * Copyright 2026 RDK Management
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.apache.org/licenses/LICENSE-2.0
# *
#* ******************************************************************************

from abc import ABCMeta, abstractmethod


class ThermalSensorControllerInterface(metaclass=ABCMeta):
"""
Abstract base class for ThermalSensor controller implementations.
Both virtual (vDevice) and actual hardware implementations must inherit this.
"""

def __init__(self, session, prompt: str = "~#", port: int = 8080):
self.session = session
self.prompt = prompt
self.port = port

@abstractmethod
def triggerThermalStateChange(self, state: str, sensorName: str = "",
temperatureCelsius: float = 0.0,
timestampMonotonicMs: int = 0):
pass

@abstractmethod
def injectTemperatureUpdate(self, sensorName: str, temperatureCelsius: float,
timestampMonotonicMs: int = 0):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
#** *****************************************************************************
# *
# * Copyright 2026 RDK Management
# *
# * Licensed under the Apache License, Version 2.0 (the "License");
# * you may not use this file except in compliance with the License.
# * You may obtain a copy of the License at
# *
# * http://www.apache.org/licenses/LICENSE-2.0
# *
#* ******************************************************************************

from raft.framework.plugins.ut_raft.utUserResponse import utUserResponse

from framework.core.thermalSensorControllerModules.ThermalSensorControllerInterface import ThermalSensorControllerInterface

Comment on lines +14 to +17

class actualThermalSensorController(ThermalSensorControllerInterface):
"""
Actual (hardware) Thermal Sensor controller.
On real hardware, thermal events are raised by platform firmware so they
cannot be injected programmatically. Each method prompts the test operator
to manually trigger the required thermal condition and confirm the result.
"""

def __init__(self, session, prompt: str = "~#", port: int = 8080):
super().__init__(session, prompt, port)
self.testUserResponse = utUserResponse(session, prompt)

def triggerThermalStateChange(self, state: str, sensorName: str = "",
temperatureCelsius: float = 0.0,
timestampMonotonicMs: int = 0):
msg = f"Manually trigger thermal state '{state}'"
if sensorName:
msg += f" on sensor '{sensorName}' at {temperatureCelsius} degC"
msg += ". Has the device reached the expected state? (Y/N):"
return self.testUserResponse.getUserYN(msg)

def injectTemperatureUpdate(self, sensorName: str, temperatureCelsius: float,
timestampMonotonicMs: int = 0):
return self.testUserResponse.getUserYN(
f"Set sensor '{sensorName}' to {temperatureCelsius} Celsius on the hardware. "
f"Has the temperature been reached? (Y/N):"
)
Loading