From 836cd739dd78576f925f14e5bedaf58925c8d9da Mon Sep 17 00:00:00 2001 From: Dariusz Zbyrad Date: Tue, 24 Dec 2024 08:16:40 +0100 Subject: [PATCH 1/2] Add Support for Throttled State Parsing and Retrieval --- .../pi4j/boardinfo/model/BoardReading.java | 77 +++++++++++-- .../pi4j/boardinfo/model/ThrottledState.java | 90 +++++++++++++++ .../pi4j/boardinfo/util/BoardInfoHelper.java | 17 ++- .../java/com/pi4j/boardinfo/util/Command.java | 15 +++ .../com/pi4j/boardinfo/model/ModelTest.java | 25 ++++- .../boardinfo/model/ThrottledStateTest.java | 103 ++++++++++++++++++ 6 files changed, 311 insertions(+), 16 deletions(-) create mode 100644 pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java create mode 100644 pi4j-core/src/test/java/com/pi4j/boardinfo/model/ThrottledStateTest.java 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". + * + * @return the throttled state as an integer, or 0 if the conversion fails. + */ + public int getThrottledStateAsInt() { + try { + if (throttledState.startsWith("throttled=0x")) { + return Integer.parseInt(throttledState.substring(12), 16); + } else { + logger.warn("Unexpected throttled state format: {}", throttledState); + } + } catch (Exception e) { + logger.error("Can't convert throttled state value: {}. {}", throttledState, e.getMessage()); + } + return 0; + } + + /** + * Gets the list of active throttled states as decoded from the raw throttled state integer. + * This method calls {@link ThrottledState#decode(int)} to convert the raw throttled state value + * into a list of active {@link ThrottledState} enum values. + * + * @return a list of {@link ThrottledState} enums representing the active throttled states. + */ + public List getThrottledStates() { + return ThrottledState.decode(getThrottledStateAsInt()); + } + + /** + * Gets a human-readable description of the active throttled states. + * This method calls {@link ThrottledState#getActiveStatesDescription(int)} to convert the raw throttled + * state value into a string describing the active throttled states. + * + * @return a string containing the description of the active throttled states. + */ + public String getThrottledStatesDescription() { + return ThrottledState.getActiveStatesDescription(getThrottledStateAsInt()); + } + /** * Converts the temperature reading to Fahrenheit. * This method uses the Celsius temperature and applies the conversion formula: diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java new file mode 100644 index 00000000..c278bd81 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java @@ -0,0 +1,90 @@ +package com.pi4j.boardinfo.model; + +/*- + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: LIBRARY :: Java Library (CORE) + * FILENAME : ThrottledState.java + * + * This file is part of the Pi4J project. More information about + * this project can be found here: https://pi4j.com/ + * ********************************************************************** + * + * 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 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.ArrayList; +import java.util.List; + +public enum ThrottledState { + UNDERVOLTAGE_DETECTED(0x1, "Undervoltage detected"), + ARM_FREQUENCY_CAPPED(0x2, "ARM frequency capped"), + CURRENTLY_THROTTLED(0x4, "Currently throttled"), + SOFT_TEMPERATURE_LIMIT_ACTIVE(0x8, "Soft temperature limit active"), + UNDERVOLTAGE_HAS_OCCURED(0x10000, "Undervoltage has occurred"), + ARM_FREQUENCY_CAPPING_HAS_OCCURED(0x20000, "ARM frequency capping has occurred"), + THROTTLING_HAS_OCCURED(0x40000, "Throttling has occurred"), + SOFT_TEMPERATURE_LIMIT_HAS_OCCURED(0x80000, "Soft temperature limit has occurred"); + + private final int value; + private final String description; + + ThrottledState(int value, String description) { + this.value = value; + this.description = description; + } + + public int getValue() { + return value; + } + + public String getDescription() { + return description; + } + + /** + * Decodes a raw throttled state (as an integer) and returns a list of active {@link ThrottledState} enums. + * + * @param rawState the raw throttled state (as an integer value, e.g., 0x50005). + * @return a list of active throttled states. + */ + public static List decode(int rawState) { + List activeStates = new ArrayList<>(); + for (ThrottledState state : ThrottledState.values()) { + if ((rawState & state.getValue()) != 0) { + activeStates.add(state); + } + } + return activeStates; + } + + /** + * Returns a human-readable description of the active throttled states. + * + * @param rawState the raw throttled state (as an integer value, e.g., 0x50005). + * @return a description of the active throttled states. + */ + public static String getActiveStatesDescription(int rawState) { + List activeStates = decode(rawState); + StringBuilder description = new StringBuilder(); + for (ThrottledState state : activeStates) { + if (description.length() > 0) { + description.append(", "); + } + description.append(state.getDescription()); + } + return description.length() > 0 ? description.toString() : "No active throttled states"; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardInfoHelper.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardInfoHelper.java index 188e6d2c..30debf57 100644 --- a/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardInfoHelper.java +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardInfoHelper.java @@ -40,6 +40,7 @@ import static com.pi4j.boardinfo.util.Command.CORE_VOLTAGE_COMMAND; import static com.pi4j.boardinfo.util.Command.TEMPERATURE_COMMAND; +import static com.pi4j.boardinfo.util.Command.THROTTLED_STATE_COMMAND; import static com.pi4j.boardinfo.util.Command.UPTIME_COMMAND; import static com.pi4j.boardinfo.util.SystemProperties.ARCHITECTURE_DATA_MODEL; import static com.pi4j.boardinfo.util.SystemProperties.JAVA_RUNTIME_VERSION; @@ -242,7 +243,8 @@ public static BoardReading getBoardReading() { getTemperature(), getUptime(), getCoreVoltage(), - getMemTotal() + getMemTotal(), + getThrottledState() ); } @@ -281,5 +283,18 @@ private static String getUptime() { private static String getTemperature() { return execute(TEMPERATURE_COMMAND).getOutputMessage(); } + + /** + * Retrieves the throttled state of the system. + *

+ * This command uses the `vcgencmd get_throttled` utility to check for under-voltage, + * throttling, or frequency capping conditions on the board. + *

+ * + * @return a string representing the throttled state of the system + */ + public static String getThrottledState() { + return execute(THROTTLED_STATE_COMMAND).getOutputMessage(); + } } diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/util/Command.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/Command.java index ab28e1eb..321697c0 100644 --- a/pi4j-core/src/main/java/com/pi4j/boardinfo/util/Command.java +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/Command.java @@ -61,4 +61,19 @@ public interface Command { *

*/ String TEMPERATURE_COMMAND = "vcgencmd measure_temp"; + + /** + * Command to retrieve throttled state information. + *

+ * 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 From 910569d2dd9a7fdc9eb7c46c9cd25eac4d6853fc Mon Sep 17 00:00:00 2001 From: Dariusz Zbyrad Date: Tue, 24 Dec 2024 08:23:26 +0100 Subject: [PATCH 2/2] Added Javadoc comments --- .../pi4j/boardinfo/model/ThrottledState.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java index c278bd81..e90a2742 100644 --- a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/ThrottledState.java @@ -28,6 +28,11 @@ import java.util.ArrayList; import java.util.List; +/** + * Enum representing the different throttling and under-voltage states of the Raspberry Pi board. + * Each enum value represents a specific condition that might be detected on the board, such as + * undervoltage, frequency capping, throttling, or temperature limits. + */ public enum ThrottledState { UNDERVOLTAGE_DETECTED(0x1, "Undervoltage detected"), ARM_FREQUENCY_CAPPED(0x2, "ARM frequency capped"), @@ -41,15 +46,31 @@ public enum ThrottledState { private final int value; private final String description; + /** + * Constructor for the ThrottledState enum. + * + * @param value The integer value representing the state. + * @param description A human-readable description of the state. + */ ThrottledState(int value, String description) { this.value = value; this.description = description; } + /** + * Returns the integer value representing this throttled state. + * + * @return the integer value associated with the state. + */ public int getValue() { return value; } + /** + * Returns a human-readable description of this throttled state. + * + * @return the description of the throttled state. + */ public String getDescription() { return description; } @@ -57,7 +78,7 @@ public String getDescription() { /** * Decodes a raw throttled state (as an integer) and returns a list of active {@link ThrottledState} enums. * - * @param rawState the raw throttled state (as an integer value, e.g., 0x50005). + * @param rawState the raw throttled state as an integer (e.g., 0x50005). * @return a list of active throttled states. */ public static List decode(int rawState) { @@ -71,9 +92,9 @@ public static List decode(int rawState) { } /** - * Returns a human-readable description of the active throttled states. + * Returns a human-readable description of the active throttled states based on the raw throttled state value. * - * @param rawState the raw throttled state (as an integer value, e.g., 0x50005). + * @param rawState the raw throttled state as an integer (e.g., 0x50005). * @return a description of the active throttled states. */ public static String getActiveStatesDescription(int rawState) {