diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardModel.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardModel.java index f0890eef4..4475dfbfd 100644 --- a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardModel.java +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardModel.java @@ -444,14 +444,99 @@ public enum BoardModel { this.usesRP1 = usesRP1; } + /** + * Returns the {@link BoardModel} corresponding to the given {@link RevisionCode} by matching on the board type and, + * where necessary, the PCB revision or memory size. + * + * @param revisionCode the parsed revision code to identify + * @return the matching {@code BoardModel}, or {@code UNKNOWN} if the type is not recognized + */ + public static BoardModel getByRevisionCode(RevisionCode revisionCode) { + switch (revisionCode.type()) { + case RPI_A: + return BoardModel.MODEL_1_A; + case RPI_B: + case RPI_ALPHA: + return BoardModel.MODEL_1_B; + case RPI_A_PLUS: + return BoardModel.MODEL_1_A_PLUS; + case RPI_B_PLUS: + return BoardModel.MODEL_1_B_PLUS; + case RPI_2B: + if (revisionCode.revision() < 2) { + return BoardModel.MODEL_2_B; + } else { + return BoardModel.MODEL_2_B_V1_2; + } + case RPI_CM1: + return BoardModel.COMPUTE_1; + case RPI_3B: + return BoardModel.MODEL_3_B; + case RPI_ZERO: + if (revisionCode.revision() < 3) { + return BoardModel.ZERO_PCB_1_2; + } else { + return BoardModel.ZERO_PCB_1_3; + } + case RPI_CM3: + return BoardModel.COMPUTE_3; + case RPI_ZERO_W: + return BoardModel.ZERO_W; + case RPI_3B_PLUS: + return BoardModel.MODEL_3_B_PLUS; + case RPI_3A_PLUS: + return BoardModel.MODEL_3_A_PLUS; + case RPI_CM3_PLUS: + return BoardModel.COMPUTE_3_PLUS; + case RPI_4B: + return BoardModel.MODEL_4_B; + case RPI_ZERO_2_W: + case RPI_CM0: + return BoardModel.ZERO_V2; + case RPI_400: + return BoardModel.MODEL_400; + case RPI_CM4: + return BoardModel.COMPUTE_4; + case RPI_CM4S: + return BoardModel.COMPUTE_4_SODIMM; + case RPI_5: + return BoardModel.MODEL_5_B; + case RPI_CM5: + case RPI_CM5_LITE: + return BoardModel.COMPUTE_5; + case RPI_500_500_PLUS: + if (RevisionCode.MemorySize.GB_16.equals(revisionCode.memorySize())) { + return BoardModel.MODEL_500_PLUS; + } else { + return BoardModel.MODEL_500; + } + default: + return UNKNOWN; + } + } + /** * Retrieves the board model corresponding to the given board code. * + *

Where possible, identification is performed by decoding the structured fields of the revision code per the + * Raspberry Pi revision code specification. If the revision code cannot be parsed, falls back to direct string + * matching against known board codes.

+ * * @param boardCode the board code to look up * @return the matching {@code BoardModel} or {@code UNKNOWN} if no match is found * @throws Exception if multiple matches are found */ public static BoardModel getByBoardCode(String boardCode) throws Exception { + try { + RevisionCode revisionCode = RevisionCode.of(boardCode); + BoardModel result = getByRevisionCode(revisionCode); + if (result != UNKNOWN) { + return result; + } + } catch (IllegalArgumentException e) { + // Not a valid revision code, fall through to legacy lookup. + } + var matches = Arrays.stream(BoardModel.values()) .filter(bm -> bm.boardCodes.contains(boardCode)) .collect(Collectors.toList()); diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/RevisionCode.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/RevisionCode.java new file mode 100644 index 000000000..94e93de2b --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/RevisionCode.java @@ -0,0 +1,483 @@ +package com.pi4j.boardinfo.definition; + +/*- + * #%L + * ********************************************************************** + * ORGANIZATION : Pi4J + * PROJECT : Pi4J :: LIBRARY :: Java Library (CORE) + * FILENAME : RevisionCode.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.Arrays; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * Represents a parsed Raspberry Pi 32-bit revision code in the format: + *
NOQuuuWuFMMMCCCCPPPPTTTTTTTTRRRR
+ * where: + * + * + *

Both old-style and new-style revision codes are supported. New-style codes are identified + * by bit 23 being set. Old-style codes are matched against known revision code tables.

+ * + *

Instances are obtained via the factory methods {@link #of(int)} and {@link #of(String)}:

+ *
+ *     RevisionCode code = RevisionCode.of("a03111");
+ *     RevisionCode code = RevisionCode.of(0xa03111);
+ * 
+ * + * @see + * Raspberry Pi New-Style Revision Codes + */ +public final class RevisionCode { + + private final MemorySize memorySize; + private final Processor processor; + private final Type type; + private final int revision; + + /** + * Private constructor. Use {@link #of(int)} or {@link #of(String)} to obtain instances. + * + * @param memorySize the memory size decoded from the revision code + * @param processor the processor decoded from the revision code + * @param type the board type decoded from the revision code + * @param revision the PCB revision number decoded from the revision code + */ + private RevisionCode(MemorySize memorySize, Processor processor, Type type, int revision) { + this.memorySize = memorySize; + this.processor = processor; + this.type = type; + this.revision = revision; + } + + /** + * Returns the memory size of the board. + * + * @return the {@link MemorySize} decoded from the revision code + */ + public MemorySize memorySize() { + return memorySize; + } + + /** + * Returns the processor of the board. + * + * @return the {@link Processor} decoded from the revision code + */ + public Processor processor() { + return processor; + } + + /** + * Returns the board type. + * + * @return the {@link Type} decoded from the revision code + */ + public Type type() { + return type; + } + + /** + * Returns the PCB revision number. + * For old-style revision codes, this always returns {@code 1} since old-style boards + * do not have revision-specific {@code BoardModel} instances. + * + * @return the PCB revision number + */ + public int revision() { + return revision; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RevisionCode that)) return false; + return memorySize == that.memorySize + && processor == that.processor + && type == that.type + && revision == that.revision; + } + + @Override + public int hashCode() { + return Objects.hash( + memorySize, + processor, + type, + revision); + } + + @Override + public String toString() { + return "RevisionCode{" + + "memorySize=" + memorySize + + ", processor=" + processor + + ", type=" + type + + ", revision=" + revision + + '}'; + } + + /** + * Creates a {@code RevisionCode} by parsing a 32-bit integer revision code. + * + * @param revisionCode the raw 32-bit revision code + * @return a {@code RevisionCode} instance representing the parsed fields + */ + public static RevisionCode of(int revisionCode) { + return new RevisionCode( + MemorySize.fromRevisionCode(revisionCode), + Processor.fromRevisionCode(revisionCode), + Type.fromRevisionCode(revisionCode), + extractRevision(revisionCode) + ); + } + + /** + * Creates a {@code RevisionCode} by parsing a hexadecimal string revision code. + * The string may optionally include a {@code 0x} or {@code 0X} prefix and surrounding + * whitespace, both of which are stripped before parsing. + * + * @param revisionCode the revision code as a hex string (e.g. {@code "a03111"} or {@code "0xa03111"}) + * @return a {@code RevisionCode} instance representing the parsed fields + * @throws IllegalArgumentException if the string is {@code null}, blank, or not valid hexadecimal + */ + public static RevisionCode of(String revisionCode) { + if (revisionCode == null || revisionCode.isBlank()) { + throw new IllegalArgumentException(("Revision code cannot be null or empty.")); + } + String clean = revisionCode.trim(); + if (clean.startsWith("0X") || clean.startsWith("0x")) { + clean = clean.substring(2); + } + + if (clean.isEmpty() || clean.length() > 8 || !clean.matches("[0-9a-fA-F]+")) { + throw new IllegalArgumentException("Revision code is not valid 32-bit hex value: " + revisionCode); + } + return of((int) Long.parseLong(clean, 16)); + } + + /** + * Returns {@code true} if the given revision code uses the new-style format, + * identified by bit 23 being set. + * + * @param revisionCode the raw 32-bit revision code + * @return {@code true} if new-style, {@code false} if old-style + */ + private static boolean isNewStyleRevisionCode(int revisionCode) { + return (revisionCode & 0x800000) != 0; + } + + /** + * Extracts the PCB revision number (bits 3:0) from a new-style revision code. + * For old-style revision codes, returns {@code 1} since old-style boards do not + * have revision-specific {@code BoardModel} instances. + * + * @param revisionCode the raw 32-bit revision code + * @return the PCB revision number, or {@code 1} for old-style codes + */ + private static int extractRevision(int revisionCode) { + return isNewStyleRevisionCode(revisionCode) ? revisionCode & 0xF : 1; + } + + /** + * A common interface for the internal enumerations of {@code RevisionCode}. + * + *

Provides access to the numeric field value used in new-style revision codes + * and the list of old-style revision codes associated with each constant, allowing + * shared lookup logic to operate generically across {@link RevisionCode.MemorySize}, + * {@link RevisionCode.Processor}, and {@link RevisionCode.Type}.

+ */ + private interface RevisionCodeEnum { + /** + * Returns the numeric value of this constant as encoded in the + * new-style revision code bit field. + * + * @return the bit field value + */ + int getValue(); + + /** + * Returns the old-style revision codes associated with this constant, + * or an empty array if this constant has no old-style codes. + * + * @return the old-style revision codes + */ + int[] getOldStyleRevisionCodes(); + } + + /** + * Represents the memory size field (bits 22:20) of a new-style Raspberry Pi revision code. + * + *

Old-style revision codes are matched against known values. New-style codes are + * identified by the 3-bit MMM field. If the value is unrecognized, {@link #UNKNOWN} is returned.

+ * + * @see + * Raspberry Pi New-Style Revision Codes + */ + public enum MemorySize implements RevisionCodeEnum { + MB_256(0, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x0012), + MB_512(1, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0013, 0x0014), + GB_1(2), + GB_2(3), + GB_4(4), + GB_8(5), + GB_16(6), + OTHER(7, 0x0015), + UNKNOWN(8); + + /** A list of old-style revision codes for the enum instance. */ + private final int[] oldStyleRevisionCodes; + + /** The value of the enum instance. */ + private final int value; + + MemorySize(int value) { + this(value, new int[0]); + } + + MemorySize(int value, int ... oldStyleRevisionCodes) { + this.oldStyleRevisionCodes = oldStyleRevisionCodes; + this.value = value; + } + + /** + * Returns the {@code MemorySize} corresponding to the given revision code. + * Returns {@link #UNKNOWN} if no match is found. + * + * @param revisionCode the raw 32-bit revision code + * @return the matching {@code MemorySize}, or {@link #UNKNOWN} + */ + static MemorySize fromRevisionCode(int revisionCode) { + return isNewStyleRevisionCode(revisionCode) ? + fromNewStyleCode(revisionCode, 20, 0x7, MemorySize::values, MemorySize.UNKNOWN) : + fromOldStyleCode(revisionCode, MemorySize::values, MemorySize.UNKNOWN); + } + + @Override + public int getValue() { + return value; + } + + @Override + public int[] getOldStyleRevisionCodes() { + return Arrays.copyOf(oldStyleRevisionCodes, oldStyleRevisionCodes.length); + } + } + + /** + * Represents the processor field (bits 15:12) of a new-style Raspberry Pi revision code. + * + *

All boards using old-style revision codes are assumed to use the {@link #BCM2835}. + * If the processor value in a new-style code is unrecognized, {@link #UNKNOWN} is returned.

+ * + * @see + * Raspberry Pi New-Style Revision Codes + */ + public enum Processor implements RevisionCodeEnum { + BCM2835(0, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015), + BCM2836(1), + BCM2837(2), + BCM2711(3), + BCM2712(4), + UNKNOWN(16); + + /** A list of old-style revision codes for the enum instance. */ + private final int[] oldStyleRevisionCodes; + + /** The value of the enum instance. */ + private final int value; + + Processor(int value) { + this(value, new int[0]); + } + + Processor(int value, int ... oldStyleRevisionCodes) { + this.oldStyleRevisionCodes = oldStyleRevisionCodes; + this.value = value; + } + + /** + * Returns the {@code Processor} corresponding to the given revision code. + * Old-style revision codes always return {@link #BCM2835}. + * Returns {@link #UNKNOWN} if no match is found in a new-style code. + * + * @param revisionCode the raw 32-bit revision code + * @return the matching {@code Processor}, or {@link #UNKNOWN} + */ + static Processor fromRevisionCode(int revisionCode) { + return isNewStyleRevisionCode(revisionCode) ? + fromNewStyleCode(revisionCode, 12, 0xF, Processor::values, Processor.UNKNOWN) : + fromOldStyleCode(revisionCode, Processor::values, Processor.UNKNOWN); + } + + @Override + public int getValue() { + return value; + } + + @Override + public int[] getOldStyleRevisionCodes() { + return Arrays.copyOf(oldStyleRevisionCodes, oldStyleRevisionCodes.length); + } + } + + /** + * Represents the board type field (bits 11:4) of a new-style Raspberry Pi revision code. + * + *

Old-style revision codes are matched against known values per type. New-style codes + * are identified by the 8-bit TTTTTTTT field. If the value is unrecognized, + * {@link #UNKNOWN} is returned.

+ * + * @see + * Raspberry Pi New-Style Revision Codes + */ + public enum Type implements RevisionCodeEnum { + RPI_A(0, 0x0007, 0x0008, 0x0009), + RPI_B(1, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x000d, 0x000e, 0x000f), + RPI_A_PLUS(2, 0x0012, 0x0015), + RPI_B_PLUS(3, 0x0010, 0x0013), + RPI_2B(4), + RPI_ALPHA(5), + RPI_CM1(6, 0x0011, 0x0014), + RPI_3B(8), + RPI_ZERO(9), + RPI_CM3(10), + RPI_ZERO_W(12), + RPI_3B_PLUS(13), + RPI_3A_PLUS(14), + RPI_CM3_PLUS(16), + RPI_4B(17), + RPI_ZERO_2_W(18), + RPI_400(19), + RPI_CM4(20), + RPI_CM4S(21), + RPI_5(23), + RPI_CM5(24), + RPI_500_500_PLUS(25), + RPI_CM5_LITE(26), + RPI_CM0(27), + UNKNOWN(256); + + /** A list of old-style revision codes for the enum instance. */ + private final int[] oldStyleRevisionCodes; + + /** The value of the enum instance. */ + private final int value; + + Type(int value) { + this(value, new int[0]); + } + + Type(int value, int ... oldStyleRevisionCodes) { + this.oldStyleRevisionCodes = oldStyleRevisionCodes; + this.value = value; + } + + /** + * Returns the {@code Type} corresponding to the given revision code. + * Returns {@link #UNKNOWN} if no match is found. + * + * @param revisionCode the raw 32-bit revision code + * @return the matching {@code Type}, or {@link #UNKNOWN} + */ + static Type fromRevisionCode(int revisionCode) { + return isNewStyleRevisionCode(revisionCode) ? + fromNewStyleCode(revisionCode, 4, 0xFF, Type::values, Type.UNKNOWN) : + fromOldStyleCode(revisionCode, Type::values, Type.UNKNOWN); + } + + @Override + public int getValue() { + return value; + } + + @Override + public int[] getOldStyleRevisionCodes() { + return Arrays.copyOf(oldStyleRevisionCodes, oldStyleRevisionCodes.length); + } + } + + /** + * Returns the enum constant of type {@code T} whose bit field value matches the + * extracted field from the given new-style revision code. + * + *

The field value is extracted by shifting the revision code right by {@code shift} + * bits and masking with {@code mask}. The result is compared against + * {@link RevisionCodeEnum#getValue()} for each constant.

+ * + * @param the enum type, which must implement {@link RevisionCodeEnum} + * @param revisionCode the raw 32-bit new-style revision code + * @param shift the number of bits to right-shift before applying the mask + * @param mask the bitmask to apply after shifting + * @param values a supplier of the enum constants to search (e.g. {@code MemorySize::values}) + * @param unknown the sentinel value to return if no match is found + * @return the matching enum constant, or {@code unknown} if no match is found + */ + private static T fromNewStyleCode( + int revisionCode, int shift, int mask, + Supplier values, T unknown) { + final int value = (revisionCode >> shift) & mask; + for (var s : values.get()) { + if (s.getValue() == value) { + return s; + } + } + return unknown; + } + + /** + * Returns the enum constant of type {@code T} whose old-style revision code list + * contains the given revision code. + * + *

Each constant is checked in declaration order. The first constant whose + * {@link RevisionCodeEnum#getOldStyleRevisionCodes()} array contains the given + * revision code is returned.

+ * + * @param the enum type, which must implement {@link RevisionCodeEnum} + * @param revisionCode the raw 32-bit old-style revision code + * @param values a supplier of the enum constants to search (e.g. {@code Type::values}) + * @param unknown the sentinel value to return if no match is found + * @return the matching enum constant, or {@code unknown} if no match is found + */ + private static T fromOldStyleCode( + int revisionCode, Supplier values, T unknown) { + for (var s : values.get()) { + for (var r : s.getOldStyleRevisionCodes()) { + if (r == revisionCode) { + return s; + } + } + } + return unknown; + } +} diff --git a/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java b/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java index 898a2a505..c34644769 100644 --- a/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java +++ b/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java @@ -9,6 +9,94 @@ class BoardModelTest { + @Test + void getBoardModelByRevisionCode() { + assertAll( + () -> assertEquals(BoardModel.MODEL_1_A_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("900021"))), + () -> assertEquals(BoardModel.MODEL_1_B_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("900032"))), + () -> assertEquals(BoardModel.MODEL_2_B, BoardModel.getByRevisionCode(RevisionCode.of("a01040"))), + () -> assertEquals(BoardModel.MODEL_2_B, BoardModel.getByRevisionCode(RevisionCode.of("a01041"))), + () -> assertEquals(BoardModel.MODEL_2_B, BoardModel.getByRevisionCode(RevisionCode.of("a21041"))), + () -> assertEquals(BoardModel.MODEL_2_B_V1_2, BoardModel.getByRevisionCode(RevisionCode.of("a02042"))), + () -> assertEquals(BoardModel.MODEL_2_B_V1_2, BoardModel.getByRevisionCode(RevisionCode.of("a22042"))), + () -> assertEquals(BoardModel.COMPUTE_1, BoardModel.getByRevisionCode(RevisionCode.of("900061"))), + () -> assertEquals(BoardModel.MODEL_3_B, BoardModel.getByRevisionCode(RevisionCode.of("a02082"))), + () -> assertEquals(BoardModel.MODEL_3_B, BoardModel.getByRevisionCode(RevisionCode.of("a22082"))), + () -> assertEquals(BoardModel.MODEL_3_B, BoardModel.getByRevisionCode(RevisionCode.of("a32082"))), + () -> assertEquals(BoardModel.MODEL_3_B, BoardModel.getByRevisionCode(RevisionCode.of("a52082"))), + () -> assertEquals(BoardModel.MODEL_3_B, BoardModel.getByRevisionCode(RevisionCode.of("a22083"))), + () -> assertEquals(BoardModel.ZERO_PCB_1_2, BoardModel.getByRevisionCode(RevisionCode.of("900092"))), + () -> assertEquals(BoardModel.ZERO_PCB_1_2, BoardModel.getByRevisionCode(RevisionCode.of("920092"))), + () -> assertEquals(BoardModel.ZERO_PCB_1_3, BoardModel.getByRevisionCode(RevisionCode.of("900093"))), + () -> assertEquals(BoardModel.ZERO_PCB_1_3, BoardModel.getByRevisionCode(RevisionCode.of("920093"))), + () -> assertEquals(BoardModel.COMPUTE_3, BoardModel.getByRevisionCode(RevisionCode.of("a020a0"))), + () -> assertEquals(BoardModel.COMPUTE_3, BoardModel.getByRevisionCode(RevisionCode.of("a220a0"))), + () -> assertEquals(BoardModel.ZERO_W, BoardModel.getByRevisionCode(RevisionCode.of("9000c1"))), + () -> assertEquals(BoardModel.MODEL_3_B_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("a020d3"))), + () -> assertEquals(BoardModel.MODEL_3_B_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("a020d4"))), + () -> assertEquals(BoardModel.MODEL_3_A_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("9020e0"))), + () -> assertEquals(BoardModel.MODEL_3_A_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("9020e1"))), + () -> assertEquals(BoardModel.COMPUTE_3_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("a02100"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("a03111"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("b03111"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("c03111"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("b03112"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("c03112"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("b03114"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("c03114"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("d03114"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("b03115"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("c03115"))), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByRevisionCode(RevisionCode.of("d03115"))), + () -> assertEquals(BoardModel.ZERO_V2, BoardModel.getByRevisionCode(RevisionCode.of("902120"))), + () -> assertEquals(BoardModel.MODEL_400, BoardModel.getByRevisionCode(RevisionCode.of("c03130"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("a03140"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("b03140"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("c03140"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("d03140"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("a03141"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("b03141"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("c03141"))), + () -> assertEquals(BoardModel.COMPUTE_4, BoardModel.getByRevisionCode(RevisionCode.of("d03141"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("b04170"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("c04170"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("d04170"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("a04171"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("b04171"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("c04171"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("d04171"))), + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByRevisionCode(RevisionCode.of("e04171"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("b04180"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("c04180"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("d04180"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("e04180"))), + () -> assertEquals(BoardModel.MODEL_500, BoardModel.getByRevisionCode(RevisionCode.of("d04190"))), + () -> assertEquals(BoardModel.MODEL_500_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("e04190"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("b041a0"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("c041a0"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("d041a0"))), + () -> assertEquals(BoardModel.COMPUTE_5, BoardModel.getByRevisionCode(RevisionCode.of("e041a0"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("0002"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("0003"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("0004"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("0005"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("0006"))), + () -> assertEquals(BoardModel.MODEL_1_A, BoardModel.getByRevisionCode(RevisionCode.of("0007"))), + () -> assertEquals(BoardModel.MODEL_1_A, BoardModel.getByRevisionCode(RevisionCode.of("0008"))), + () -> assertEquals(BoardModel.MODEL_1_A, BoardModel.getByRevisionCode(RevisionCode.of("0009"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("000d"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("000e"))), + () -> assertEquals(BoardModel.MODEL_1_B, BoardModel.getByRevisionCode(RevisionCode.of("000f"))), + () -> assertEquals(BoardModel.MODEL_1_B_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("0010"))), + () -> assertEquals(BoardModel.COMPUTE_1, BoardModel.getByRevisionCode(RevisionCode.of("0011"))), + () -> assertEquals(BoardModel.MODEL_1_A_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("0012"))), + () -> assertEquals(BoardModel.MODEL_1_B_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("0013"))), + () -> assertEquals(BoardModel.COMPUTE_1, BoardModel.getByRevisionCode(RevisionCode.of("0014"))), + () -> assertEquals(BoardModel.MODEL_1_A_PLUS, BoardModel.getByRevisionCode(RevisionCode.of("0015"))), + () -> assertEquals(BoardModel.UNKNOWN, BoardModel.getByRevisionCode(RevisionCode.of("ffffffff"))) + ); + } + @Test void getBoardModelByBoardCode() { assertAll( diff --git a/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/RevisionCodeTest.java b/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/RevisionCodeTest.java new file mode 100644 index 000000000..6733ad5e4 --- /dev/null +++ b/pi4j-core/src/test/java/com/pi4j/boardinfo/definition/RevisionCodeTest.java @@ -0,0 +1,172 @@ +package com.pi4j.boardinfo.definition; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RevisionCodeTest { + + @Test + void getInvalidRevisionCode() { + assertAll( + () -> assertThrows(IllegalArgumentException.class, () -> RevisionCode.of(null)), + () -> assertThrows(IllegalArgumentException.class, () -> RevisionCode.of("")), + () -> assertThrows(IllegalArgumentException.class, () -> RevisionCode.of("0X")), + () -> assertThrows(IllegalArgumentException.class, () -> RevisionCode.of("0xBadHex")), + () -> assertThrows(IllegalArgumentException.class, () -> RevisionCode.of("FFFFFFFFF")) + ); + } + + @Test + void testHashCodeEqualsToString() { + final Object obj = new Object(); + final String rc1Str = "e04180"; + final String rc2Str = "0XE04180"; + final int rcInt = 0xe04180; + final RevisionCode rc1 = RevisionCode.of(rc1Str); + final RevisionCode rc2 = RevisionCode.of(rc2Str); + assertAll( + () -> assertNotEquals(rc1Str, rc2Str), + () -> assertNotEquals(rc1, obj), + () -> assertEquals(rc1, rc2), + () -> assertEquals(rc1.hashCode(), rc2.hashCode()), + () -> assertEquals(rc1.toString(), rc2.toString()), + () -> assertNotEquals(rc1, RevisionCode.of(rcInt ^ (0x1 << 21))), // Flip a memory size bit. + () -> assertNotEquals(rc1, RevisionCode.of(rcInt ^ (0x1 << 5))), // Flip a type bit. + () -> assertNotEquals(rc1, RevisionCode.of(rcInt ^ (0x1 << 14))), // Flip a processor bit. + () -> assertNotEquals(rc1, RevisionCode.of(rcInt ^ 0x1)) // Flip a revision bit. + ); + } + + @Test + void getNewStyleRevisionCode() { + int revisionCode = 0x800000; + revisionCode |= (0x4 << 20); // Set MemorySize GB_4 + revisionCode |= (0x4 << 12); // Set Processor: BCM2712 + revisionCode |= (0x17 << 4); // Set Type: RPI_5 + revisionCode |= 1; // Set Revision: 1 + RevisionCode actual = RevisionCode.of(revisionCode); + assertAll( + () -> assertEquals(RevisionCode.MemorySize.GB_4, actual.memorySize()), + () -> assertEquals(RevisionCode.Processor.BCM2712, actual.processor()), + () -> assertEquals(RevisionCode.Type.RPI_5, actual.type()), + () -> assertEquals(1, actual.revision()) + ); + } + + @Test + void getOldStyleRevisionCode() { + String revisionCode = "0x0002"; + RevisionCode actual = RevisionCode.of(revisionCode); + assertAll( + () -> assertEquals(RevisionCode.MemorySize.MB_256, actual.memorySize()), + () -> assertEquals(RevisionCode.Processor.BCM2835, actual.processor()), + () -> assertEquals(RevisionCode.Type.RPI_B, actual.type()), + () -> assertEquals(1, actual.revision()) + ); + } + + @Test + void getUnknownRevisionCode() { + String revisionCode = "0x0001"; + RevisionCode actual = RevisionCode.of(revisionCode); + assertAll( + () -> assertEquals(RevisionCode.MemorySize.UNKNOWN, actual.memorySize()), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, actual.processor()), + () -> assertEquals(RevisionCode.Type.UNKNOWN, actual.type()), + () -> assertEquals(1, actual.revision()) + ); + } + + @Test + void getMemorySizeFromRevisionCode() { + assertAll( + // New-style + () -> assertEquals(RevisionCode.MemorySize.MB_256, RevisionCode.MemorySize.fromRevisionCode(0x800000)), + () -> assertEquals(RevisionCode.MemorySize.MB_512, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x1 << 20))), + () -> assertEquals(RevisionCode.MemorySize.GB_1, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x2 << 20))), + () -> assertEquals(RevisionCode.MemorySize.GB_2, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x3 << 20))), + () -> assertEquals(RevisionCode.MemorySize.GB_4, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x4 << 20))), + () -> assertEquals(RevisionCode.MemorySize.GB_8, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x5 << 20))), + () -> assertEquals(RevisionCode.MemorySize.GB_16, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x6 << 20))), + () -> assertEquals(RevisionCode.MemorySize.OTHER, RevisionCode.MemorySize.fromRevisionCode(0x800000 | (0x7 << 20))), + // Old-style + () -> assertEquals(RevisionCode.MemorySize.MB_256, RevisionCode.MemorySize.fromRevisionCode(2)), + () -> assertEquals(RevisionCode.MemorySize.MB_512, RevisionCode.MemorySize.fromRevisionCode(16)), + () -> assertEquals(RevisionCode.MemorySize.OTHER, RevisionCode.MemorySize.fromRevisionCode(21)), + () -> assertEquals(RevisionCode.MemorySize.UNKNOWN, RevisionCode.MemorySize.fromRevisionCode(0)) + ); + } + + @Test + void getTypeFromRevisionCode() { + assertAll( + // New-style + () -> assertEquals(RevisionCode.Type.RPI_A, RevisionCode.Type.fromRevisionCode(0x800000)), + () -> assertEquals(RevisionCode.Type.RPI_B, RevisionCode.Type.fromRevisionCode(0x800000 | (0x01 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_A_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x02 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_B_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x03 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_2B, RevisionCode.Type.fromRevisionCode(0x800000 | (0x04 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_ALPHA, RevisionCode.Type.fromRevisionCode(0x800000 | (0x05 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM1, RevisionCode.Type.fromRevisionCode(0x800000 | (0x06 << 4))), + () -> assertEquals(RevisionCode.Type.UNKNOWN, RevisionCode.Type.fromRevisionCode(0x800000 | (0x07 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_3B, RevisionCode.Type.fromRevisionCode(0x800000 | (0x08 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_ZERO, RevisionCode.Type.fromRevisionCode(0x800000 | (0x09 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM3, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0a << 4))), + () -> assertEquals(RevisionCode.Type.UNKNOWN, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0b << 4))), + () -> assertEquals(RevisionCode.Type.RPI_ZERO_W, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0c << 4))), + () -> assertEquals(RevisionCode.Type.RPI_3B_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0d << 4))), + () -> assertEquals(RevisionCode.Type.RPI_3A_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0e << 4))), + () -> assertEquals(RevisionCode.Type.UNKNOWN, RevisionCode.Type.fromRevisionCode(0x800000 | (0x0f << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM3_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x10 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_4B, RevisionCode.Type.fromRevisionCode(0x800000 | (0x11 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_ZERO_2_W, RevisionCode.Type.fromRevisionCode(0x800000 | (0x12 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_400, RevisionCode.Type.fromRevisionCode(0x800000 | (0x13 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM4, RevisionCode.Type.fromRevisionCode(0x800000 | (0x14 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM4S, RevisionCode.Type.fromRevisionCode(0x800000 | (0x15 << 4))), + () -> assertEquals(RevisionCode.Type.UNKNOWN, RevisionCode.Type.fromRevisionCode(0x800000 | (0x16 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_5, RevisionCode.Type.fromRevisionCode(0x800000 | (0x17 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM5, RevisionCode.Type.fromRevisionCode(0x800000 | (0x18 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_500_500_PLUS, RevisionCode.Type.fromRevisionCode(0x800000 | (0x19 << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM5_LITE, RevisionCode.Type.fromRevisionCode(0x800000 | (0x1a << 4))), + () -> assertEquals(RevisionCode.Type.RPI_CM0, RevisionCode.Type.fromRevisionCode(0x800000 | (0x1b << 4))), + // Old-style + () -> assertEquals(RevisionCode.Type.RPI_A, RevisionCode.Type.fromRevisionCode(0x7)), + () -> assertEquals(RevisionCode.Type.RPI_B, RevisionCode.Type.fromRevisionCode(0x3)), + () -> assertEquals(RevisionCode.Type.RPI_A_PLUS, RevisionCode.Type.fromRevisionCode(0x12)), + () -> assertEquals(RevisionCode.Type.RPI_B_PLUS, RevisionCode.Type.fromRevisionCode(0x13)), + () -> assertEquals(RevisionCode.Type.RPI_CM1, RevisionCode.Type.fromRevisionCode(0x11)), + () -> assertEquals(RevisionCode.Type.UNKNOWN, RevisionCode.Type.fromRevisionCode(0x1a)) + ); + } + + @Test + void getProcessorFromRevisionCode() { + assertAll( + // New-style + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x800000)), + () -> assertEquals(RevisionCode.Processor.BCM2836, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x1 << 12))), + () -> assertEquals(RevisionCode.Processor.BCM2837, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x2 << 12))), + () -> assertEquals(RevisionCode.Processor.BCM2711, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x3 << 12))), + () -> assertEquals(RevisionCode.Processor.BCM2712, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x4 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x5 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x6 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x7 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x8 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0x9 << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xa << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xb << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xc << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xd << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xe << 12))), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x800000 | (0xf << 12))), + // Old-style + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x2)), + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x3)), + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x4)), + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x5)), + () -> assertEquals(RevisionCode.Processor.BCM2835, RevisionCode.Processor.fromRevisionCode(0x6)), + () -> assertEquals(RevisionCode.Processor.UNKNOWN, RevisionCode.Processor.fromRevisionCode(0x1a)) + ); + } +}