diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardReading.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardReading.java index 9c91abd6..615c4624 100644 --- a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardReading.java +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardReading.java @@ -28,10 +28,24 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + /** - * Represents the readings from a Raspberry Pi board including information - * about its code, version, temperature, uptime, voltage, and memory usage. - * Provides utility methods to parse and convert these readings. + * Represents the readings and status information from a Raspberry Pi board. + * This includes the board's unique code, version, temperature, uptime, voltage, memory usage, and throttled state. + * Provides utility methods to parse and convert these readings into more useful formats (e.g., Celsius, Fahrenheit, integer values for throttling state). + *
+ * The throttled state reflects whether the board is under certain limitations like low voltage or throttling due to high temperature or CPU usage. + * This class is intended to capture the board's operational data to help monitor its health and performance. + *
+ * Fields:
+ * - {@link #boardCode}: The unique code identifying the board model.
+ * - {@link #boardVersionCode}: The version code for the specific model of the board.
+ * - {@link #temperature}: The current temperature of the board (as a string).
+ * - {@link #uptimeInfo}: Information about how long the board has been running.
+ * - {@link #volt}: The current voltage reading (as a string).
+ * - {@link #memory}: Information about the memory usage of the board.
+ * - {@link #throttledState}: The throttling state of the board, indicating if the board is under-voltage, throttled, or experiencing frequency capping (as a string).
*/
public class BoardReading {
@@ -43,25 +57,29 @@ public class BoardReading {
private final String uptimeInfo;
private final String volt;
private final String memory;
+ private final String throttledState;
/**
* Constructor to initialize a {@link BoardReading} object.
*
- * @param boardCode the unique code for the board.
+ * @param boardCode the unique code for the board.
* @param boardVersionCode the version code of the board.
- * @param temperature the temperature reading of the board (in string format).
- * @param uptimeInfo the uptime information for the board.
- * @param volt the voltage reading of the board (in string format).
- * @param memory the memory usage information for the board.
+ * @param temperature the temperature reading of the board (in string format).
+ * @param uptimeInfo the uptime information for the board.
+ * @param volt the voltage reading of the board (in string format).
+ * @param memory the memory usage information for the board.
+ * @param throttledState the throttled state of the board, indicating under-voltage, throttling,
+ * or frequency capping conditions (in string format).
*/
public BoardReading(String boardCode, String boardVersionCode, String temperature, String uptimeInfo,
- String volt, String memory) {
+ String volt, String memory, String throttledState) {
this.boardCode = boardCode;
this.boardVersionCode = boardVersionCode;
this.temperature = temperature;
this.uptimeInfo = uptimeInfo;
this.volt = volt;
this.memory = memory;
+ this.throttledState = throttledState;
}
/**
@@ -138,6 +156,47 @@ public double getTemperatureInCelsius() {
return 0;
}
+ /**
+ * Converts the throttled state to an integer value.
+ * The expected input format is "throttled=0x
+ * This command uses the `vcgencmd get_throttled` utility to check for under-voltage,
+ * throttling, or frequency capping conditions on the board.
+ *
+ * This command uses the `vcgencmd` tool to query the throttled state of the system. The output provides + * details about under-voltage, throttling, and frequency capping conditions. It is useful for diagnosing + * power and thermal management issues. + *
+ *+ * For more details on the bit interpretation of the output, see the + * + * Raspberry Pi documentation. + *
+ */ + String THROTTLED_STATE_COMMAND = "vcgencmd get_throttled"; } diff --git a/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ModelTest.java b/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ModelTest.java index 1174b038..9c388694 100644 --- a/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ModelTest.java +++ b/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ModelTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,20 +25,31 @@ void testStringOutputFromJavaInfo() { void testBoardReadingParsing() { var boardReading = new BoardReading( "Raspberry Pi 4 Model B Rev 1.1", - "c03111", + "c03111", "temp=42.8'C", "08:06:15 up 85 days, 9:43, 0 users, load average: 0.00, 0.00, 0.00", - "volt=0.8563V", - "MemTotal: 3885396 kB" - ); + "volt=0.8563V", + "MemTotal: 3885396 kB", + "throttled=0x3" + ); assertAll( () -> assertEquals(42.8, boardReading.getTemperatureInCelsius(), "Temperature in Celsius"), - () -> assertEquals(109.03999999999999, boardReading.getTemperatureInFahrenheit(), "Temperature in Fahrenheit"), - () -> assertEquals(0.8563, boardReading.getVoltValue(), "Volt") + () -> assertEquals(109.04, boardReading.getTemperatureInFahrenheit(), 0.01, "Temperature in Fahrenheit"), + () -> assertEquals(0.8563, boardReading.getVoltValue(), "Volt"), + () -> assertEquals(3, boardReading.getThrottledStateAsInt(), "Throttled state as integer"), // Hex 0x3 to int + () -> assertEquals(List.of(ThrottledState.UNDERVOLTAGE_DETECTED, ThrottledState.ARM_FREQUENCY_CAPPED), + boardReading.getThrottledStates(), + "Throttled states as list of active ThrottledState enums"), + () -> assertEquals( + "Undervoltage detected, ARM frequency capped", + boardReading.getThrottledStatesDescription(), + "Throttled states description for active states" + ) ); } + @Test void testMemoryParsing() { var memory = new JvmMemory(Runtime.getRuntime()); diff --git a/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ThrottledStateTest.java b/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ThrottledStateTest.java new file mode 100644 index 00000000..b4166574 --- /dev/null +++ b/pi4j-core/src/test/java/com/pi4j/boardinfo/model/ThrottledStateTest.java @@ -0,0 +1,103 @@ +package com.pi4j.boardinfo.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ThrottledStateTest { + + @Test + void testDecodeThrottledStateUndervoltageDetected() { + int throttledStateInt = 0x1; // 0000000000000001 in binary + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(1, activeStates.size(), "Should only have 1 active state"), + () -> assertEquals(ThrottledState.UNDERVOLTAGE_DETECTED, activeStates.get(0), "Should be Undervoltage detected") + ); + } + + @Test + void testDecodeThrottledStateArmFrequencyCapped() { + int throttledStateInt = 0x2; // 0000000000000010 in binary + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(1, activeStates.size(), "Should only have 1 active state"), + () -> assertEquals(ThrottledState.ARM_FREQUENCY_CAPPED, activeStates.get(0), "Should be Arm frequency capped") + ); + } + + @Test + void testDecodeThrottledStateCurrentlyThrottled() { + int throttledStateInt = 0x4; // 0000000000000100 in binary + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(1, activeStates.size(), "Should only have 1 active state"), + () -> assertEquals(ThrottledState.CURRENTLY_THROTTLED, activeStates.get(0), "Should be Currently throttled") + ); + } + + @Test + void testDecodeCombinedThrottledStates() { + int throttledStateInt = 0x5; // 0000000000000101 in binary (Undervoltage detected + Currently throttled) + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(2, activeStates.size(), "Should have 2 active states"), + () -> assertEquals(ThrottledState.UNDERVOLTAGE_DETECTED, activeStates.get(0), "Should be Undervoltage detected"), + () -> assertEquals(ThrottledState.CURRENTLY_THROTTLED, activeStates.get(1), "Should be Currently throttled") + ); + } + + @Test + void testDecodeThrottledStateSoftTemperatureLimitActive() { + int throttledStateInt = 0x8; // 0000000000001000 in binary + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(1, activeStates.size(), "Should only have 1 active state"), + () -> assertEquals(ThrottledState.SOFT_TEMPERATURE_LIMIT_ACTIVE, activeStates.get(0), "Should be Soft temperature limit active") + ); + } + + @Test + void testThrottledStateDescription() { + int rawState = 0x50005; // rawState in hexadecimal: 0x50005 or 327685 + + // Breakdown of the bits that are set: + // 0x1 (bit 0): Undervoltage detected + // 0x4 (bit 2): Currently throttled + // 0x10000 (bit 16): Undervoltage has occurred + // 0x40000 (bit 18): Throttling has occurred + + String description = ThrottledState.getActiveStatesDescription(rawState); + + // Assert that the description contains the correct states based on the bits set + assertEquals( + "Undervoltage detected, Currently throttled, Undervoltage has occurred, Throttling has occurred", + description, + "Description should include the correct active states" + ); + } + + @Test + void testDecodeNoThrottledState() { + int throttledStateInt = 0x0; // 0000000000000000 (no bits set) + var activeStates = ThrottledState.decode(throttledStateInt); + + assertAll( + () -> assertEquals(0, activeStates.size(), "Should have 0 active states") + ); + } + + @Test + void testDecodeAllThrottledStates() { + int throttledStateInt = 0xFFFFF; // All possible bits set + var activeStates = ThrottledState.decode(throttledStateInt); + + assertEquals(8, activeStates.size(), "Should have 8 active states"); + } +} \ No newline at end of file