diff --git a/pi4j-core/pom.xml b/pi4j-core/pom.xml index e695f29c..810a53f5 100644 --- a/pi4j-core/pom.xml +++ b/pi4j-core/pom.xml @@ -7,6 +7,7 @@ Pi4J :: LIBRARY :: Java Library (CORE) Pi4J Java API & Runtime Library jar + com.pi4j pi4j-parent @@ -14,9 +15,6 @@ ../pom.xml - - - 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 new file mode 100644 index 00000000..f18d49f0 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardModel.java @@ -0,0 +1,359 @@ +package com.pi4j.boardinfo.definition; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static com.pi4j.boardinfo.definition.BoardType.*; + +/** + * Partially based on + * en.wikipedia.org/wiki/Raspberry_Pi + * oastic.com/posts/how-to-know-which-raspberry-do-you-have + * raspberrypi.com/documentation/computers/raspberry-pi.html#new-style-revision-codes-in-use + * raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/ + */ +public enum BoardModel { + MODEL_1_A("Raspberry Pi 1 Model A", SINGLE_BOARD_COMPUTER, + new ArrayList<>(), + PiModel.MODEL_A, + HeaderVersion.TYPE_1, + LocalDate.of(2013, 2, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(700), + Collections.singletonList(256 * 1024)), + MODEL_1_A_PLUS("Raspberry Pi 1 Model A+", SINGLE_BOARD_COMPUTER, + Collections.singletonList("900021"), + PiModel.MODEL_A, + HeaderVersion.TYPE_1, + LocalDate.of(2014, 11, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(700), + Arrays.asList(256 * 1024, 512 * 1024), + Collections.singletonList("Amount of memory changed to 512Mb on 20160810")), + MODEL_3_A_PLUS("Raspberry Pi 3 Model A+", SINGLE_BOARD_COMPUTER, + Collections.singletonList("9020e0"), + PiModel.MODEL_A, + HeaderVersion.TYPE_3, + LocalDate.of(2018, 11, 1), + Soc.BCM2837B0, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1400), + Collections.singletonList(512 * 1024)), + MODEL_1_B("Raspberry Pi 1 Model B", SINGLE_BOARD_COMPUTER, + new ArrayList<>(), + PiModel.MODEL_B, + HeaderVersion.TYPE_1, + LocalDate.of(2012, 4, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(700), + Arrays.asList(256 * 1024, 512 * 1024), + Collections.singletonList("Amount of memory changed to 512Mb on 20121015")), + MODEL_1_B_PLUS("Raspberry Pi 1 Model B+", SINGLE_BOARD_COMPUTER, + Collections.singletonList("900032"), + PiModel.MODEL_B, + HeaderVersion.TYPE_1, + LocalDate.of(2014, 7, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(700), + Collections.singletonList(512 * 1024)), + MODEL_2_B("Raspberry Pi 2 Model B", SINGLE_BOARD_COMPUTER, + Arrays.asList("a01040", "a01041", "a21041"), + PiModel.MODEL_B, + HeaderVersion.TYPE_2, + LocalDate.of(2015, 2, 1), + Soc.BCM2836, + Cpu.CORTEX_A7, 4, + Collections.singletonList(900), + Collections.singletonList(1024 * 1024)), + MODEL_2_B_V1_2("Raspberry Pi 2 Model B V1.2", SINGLE_BOARD_COMPUTER, + Arrays.asList("a02042", "a22042"), + PiModel.MODEL_B, + HeaderVersion.TYPE_2, + LocalDate.of(2016, 10, 1), + Soc.BCM2837, + Cpu.CORTEX_A53, 4, + Collections.singletonList(900), + Collections.singletonList(1024 * 1024)), + MODEL_3_B("Raspberry Pi 3 Model B", SINGLE_BOARD_COMPUTER, + Arrays.asList("a02082", "a22082", "a32082", "a52082", "a22083"), + PiModel.MODEL_B, + HeaderVersion.TYPE_3, + LocalDate.of(2016, 2, 1), + Soc.BCM2837, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1200), + Collections.singletonList(1024 * 1024)), + MODEL_3_B_PLUS("Raspberry Pi 3 Model B+", SINGLE_BOARD_COMPUTER, + Collections.singletonList("a020d3"), + PiModel.MODEL_B, + HeaderVersion.TYPE_3, + LocalDate.of(2018, 3, 14), + Soc.BCM2837B0, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1400), + Collections.singletonList(1024 * 1024)), + MODEL_4_B("Raspberry Pi 4 Model B", SINGLE_BOARD_COMPUTER, + Arrays.asList("a03111", "b03111", "b03112", "b03114", "b03115", "c03111", "c03112", "c03114", "c03115", "d03114", "d03115"), + PiModel.MODEL_B, + HeaderVersion.TYPE_3, + LocalDate.of(2019, 6, 24), + Soc.BCM2711, + Cpu.CORTEX_A72, 4, + Arrays.asList(1500, 1800), + Arrays.asList(1024 * 1024, 2048 * 1024, 4096 * 1024, 8192 * 1024)), + MODEL_400("Raspberry Pi 400", ALL_IN_ONE_COMPUTER, + Collections.singletonList("c03130"), + PiModel.MODEL_B, + HeaderVersion.TYPE_3, + LocalDate.of(2020, 11, 2), + Soc.BCM2711C0, + Cpu.CORTEX_A72, 4, + Collections.singletonList(1800), + Collections.singletonList(4096 * 1024)), + MODEL_5_B("Raspberry Pi 5 Model B", SINGLE_BOARD_COMPUTER, + Arrays.asList("c04170", "d04170"), + PiModel.MODEL_B, + HeaderVersion.TYPE_3, + LocalDate.of(2023, 9, 28), + Soc.BCM2712, + Cpu.CORTEX_A76, 4, + Collections.singletonList(2400), + Arrays.asList(4096 * 1024, 8192 * 1024)), + COMPUTE_1("Compute Module 1", STACK_ON_COMPUTER, + Collections.singletonList("900061"), + PiModel.COMPUTE, + HeaderVersion.COMPUTE, + LocalDate.of(2014, 4, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(700), + Collections.singletonList(512 * 1024)), + COMPUTE_3("Compute Module 3", STACK_ON_COMPUTER, + Arrays.asList("a020a0", "a220a0"), + PiModel.COMPUTE, + HeaderVersion.COMPUTE, + LocalDate.of(2017, 1, 1), + Soc.BCM2837, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1200), + Collections.singletonList(1024 * 1024)), + COMPUTE_3_PLUS("Compute Module 3+", STACK_ON_COMPUTER, + Collections.singletonList("a02100"), + PiModel.COMPUTE, + HeaderVersion.COMPUTE, + LocalDate.of(2019, 1, 1), + Soc.BCM2837B0, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1200), + Collections.singletonList(1024 * 1024)), + COMPUTE_4("Compute Module 4", STACK_ON_COMPUTER, + Arrays.asList("a03140", "b03140", "c03140", "d03140"), + PiModel.COMPUTE, + HeaderVersion.COMPUTE, + LocalDate.of(2020, 10, 1), + Soc.BCM2711, + Cpu.CORTEX_A72, 4, + Collections.singletonList(1500), + Arrays.asList(1024 * 1024, 2048 * 1024, 4096 * 1024, 8192 * 1024)), + ZERO_PCB_1_2("Raspberry Pi Zero PCB V1.2", SINGLE_BOARD_COMPUTER, + Arrays.asList("900092", "920092"), + PiModel.ZERO, + HeaderVersion.TYPE_2, + LocalDate.of(2015, 11, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(1000), + Collections.singletonList(512 * 1024)), + ZERO_PCB_1_3("Raspberry Pi Zero PCB V1.3", SINGLE_BOARD_COMPUTER, + Arrays.asList("900093", "920093"), + PiModel.ZERO, + HeaderVersion.TYPE_3, + LocalDate.of(2016, 5, 1), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(1000), + Collections.singletonList(512 * 1024)), + ZERO_W("Raspberry Pi Zero W", SINGLE_BOARD_COMPUTER, + Collections.singletonList("9000c1"), + PiModel.ZERO, + HeaderVersion.TYPE_3, + LocalDate.of(2017, 2, 28), + Soc.BCM2835, + Cpu.ARM1176JZF_S, 1, + Collections.singletonList(1000), + Collections.singletonList(512 * 1024)), + ZERO_V2("Raspberry Pi Zero V2", SINGLE_BOARD_COMPUTER, + Collections.singletonList("902120"), + PiModel.ZERO, + HeaderVersion.TYPE_3, + LocalDate.of(2021, 10, 28), + Soc.BCM2710A1, + Cpu.CORTEX_A53, 4, + Collections.singletonList(1000), + Collections.singletonList(512 * 1024)), + PICO("Raspberry Pi Pico", MICROCONTROLLER, + new ArrayList<>(), + PiModel.PICO, + HeaderVersion.PICO, + LocalDate.of(2021, 1, 1), + Soc.RP2040, + Cpu.CORTEX_MO_PLUS, 1, + Collections.singletonList(1000), + Collections.singletonList(264 + 2048)), + PICO_W("Raspberry Pi Pico W", MICROCONTROLLER, + new ArrayList<>(), + PiModel.PICO, + HeaderVersion.PICO, + LocalDate.of(2022, 6, 1), + Soc.RP2040, + Cpu.CORTEX_MO_PLUS, 1, + Collections.singletonList(1000), + Collections.singletonList(264 + 2048), + Collections.singletonList("Same form factor as PICO but with Wi-Fi")), + UNKNOWN("Unknown", BoardType.UNKNOWN, + new ArrayList<>(), + PiModel.UNKNOWN, + HeaderVersion.UNKNOWN, + null, + Soc.UNKNOWN, + Cpu.UNKNOWN, 0, + new ArrayList<>(), + new ArrayList<>()); + + private static final Logger logger = LoggerFactory.getLogger(BoardModel.class); + + private final String label; + private final BoardType boardType; + private final List boardCodes; + private final PiModel model; + private final HeaderVersion headerVersion; + private final LocalDate releaseDate; + private final Soc soc; + private final Cpu cpu; + private final Integer numberOfCpu; + private final List versionsProcessorSpeedInMhz; + private final List versionsMemoryInKb; + private final List remarks; + + BoardModel(String label, BoardType boardType, List boardCodes, + PiModel model, HeaderVersion headerVersion, LocalDate releaseDate, + Soc soc, Cpu cpu, Integer numberOfCpu, + List versionsProcessorSpeedInMhz, List versionsMemoryInKb) { + this(label, boardType, boardCodes, model, headerVersion, releaseDate, soc, cpu, numberOfCpu, versionsProcessorSpeedInMhz, + versionsMemoryInKb, new ArrayList<>()); + } + + BoardModel(String label, BoardType boardType, List boardCodes, + PiModel model, HeaderVersion headerVersion, LocalDate releaseDate, + Soc soc, Cpu cpu, Integer numberOfCpu, + List versionsProcessorSpeedInMhz, List versionsMemoryInKb, + List remarks) { + this.label = label; + this.boardType = boardType; + this.boardCodes = boardCodes; + this.model = model; + this.headerVersion = headerVersion; + this.releaseDate = releaseDate; + this.soc = soc; + this.cpu = cpu; + this.numberOfCpu = numberOfCpu; + this.versionsProcessorSpeedInMhz = versionsProcessorSpeedInMhz; + this.versionsMemoryInKb = versionsMemoryInKb; + this.remarks = remarks; + } + + public static BoardModel getByBoardCode(String boardCode) { + var matches = Arrays.stream(BoardModel.values()) + .filter(bm -> bm.boardCodes.contains(boardCode)) + .collect(Collectors.toList()); + if (matches.isEmpty()) { + return BoardModel.UNKNOWN; + } else if (matches.size() > 1) { + logger.error("Too many matching models found for code {}, probably an error in the definitions", boardCode); + } + return matches.get(0); + } + + public static BoardModel getByBoardName(String boardName) { + var matches = Arrays.stream(BoardModel.values()) + .filter(bm -> boardName.toLowerCase().startsWith(bm.label.toLowerCase())) + .collect(Collectors.toList()); + if (matches.isEmpty()) { + return BoardModel.UNKNOWN; + } else if (matches.size() > 1) { + logger.error("Too many matching models found for name {}, the given name is not exclusive enough", boardName); + } + return matches.get(0); + } + + public String getName() { + return name(); + } + + public String getLabel() { + return label; + } + + public BoardType getBoardType() { + return boardType; + } + + public List getBoardCodes() { + return boardCodes; + } + + public PiModel getModel() { + return model; + } + + public HeaderVersion getHeaderVersion() { + return headerVersion; + } + + public LocalDate getReleaseDate() { + return releaseDate; + } + + public Soc getSoc() { + return soc; + } + + public Cpu getCpu() { + return cpu; + } + + public Integer getNumberOfCpu() { + return numberOfCpu; + } + + public List getVersionsProcessorSpeedInMhz() { + return versionsProcessorSpeedInMhz; + } + + public List getVersionsMemoryInKb() { + return versionsMemoryInKb; + } + + public List getVersionsMemoryInMb() { + return versionsMemoryInKb.stream().map(m -> m / 1024F).collect(Collectors.toList()); + } + + public List getVersionsMemoryInGb() { + return versionsMemoryInKb.stream().map(m -> m / 1024F / 1024F).collect(Collectors.toList()); + } + + public List getRemarks() { + return remarks; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardType.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardType.java new file mode 100644 index 00000000..542fe1a7 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/BoardType.java @@ -0,0 +1,9 @@ +package com.pi4j.boardinfo.definition; + +public enum BoardType { + ALL_IN_ONE_COMPUTER, + MICROCONTROLLER, + SINGLE_BOARD_COMPUTER, + STACK_ON_COMPUTER, + UNKNOWN +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Cpu.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Cpu.java new file mode 100644 index 00000000..7c1da3a9 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Cpu.java @@ -0,0 +1,22 @@ +package com.pi4j.boardinfo.definition; + +public enum Cpu { + ARM1176JZF_S("ARM1176JZF-S"), + CORTEX_A53("Cortex-A53"), + CORTEX_A7("Cortex-A7"), + CORTEX_A72("Cortex-A72"), + CORTEX_A76("Cortex-A76"), + CORTEX_MO_PLUS("Cortex-M0+"), + UNKNOWN("Unknown"), + ; + + private final String label; + + Cpu(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderPins.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderPins.java new file mode 100644 index 00000000..571dbc7a --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderPins.java @@ -0,0 +1,123 @@ +package com.pi4j.boardinfo.definition; + +import com.pi4j.boardinfo.model.HeaderPin; + +import java.util.ArrayList; +import java.util.List; + +/** + * List of pins in a Raspberry Pi header. + */ +public enum HeaderPins { + HEADER_8("8pin header", get8PinsHeader()), + HEADER_26_TYPE_1("26pin header - type 1", get26PinsHeader(1)), + HEADER_26_TYPE_2("26pin header - type 2", get26PinsHeader(2)), + HEADER_40("40pin header", get40PinsHeader()), + COMPUTE_J5("Compute J5", getComputeJ5()), + COMPUTE_J6("Compute J6", getComputeJ6()); + + private final String label; + private List pins; + + HeaderPins(String label, List pins) { + this.label = label; + this.pins = pins; + } + + public String getLabel() { + return label; + } + + public List getPins() { + return pins; + } + + static List get8PinsHeader() { + List header = new ArrayList<>(); + + header.add(new HeaderPin(1, PinType.POWER, "5.0 VDC")); + header.add(new HeaderPin(2, PinType.POWER, "3.3 VDC")); + header.add(new HeaderPin(3, PinType.DIGITAL, null, 28, 17, "")); + header.add(new HeaderPin(4, PinType.DIGITAL, null, 29, 18, "")); + header.add(new HeaderPin(5, PinType.DIGITAL, null, 30, 19, "")); + header.add(new HeaderPin(6, PinType.DIGITAL, null, 31, 20, "")); + header.add(new HeaderPin(7, PinType.GROUND, "Ground")); + header.add(new HeaderPin(8, PinType.GROUND, "Ground")); + + return header; + } + + static List get26PinsHeader(int type) { + List header = new ArrayList<>(); + + header.add(new HeaderPin(1, PinType.POWER, "3.3 VDC")); + header.add(new HeaderPin(2, PinType.POWER, "5.0 VDC")); + header.add(new HeaderPin(3, PinType.DIGITAL_NO_PULL_DOWN, PinFunction.I2C, (type == 1 ? 0 : 2), 8, "SDA1 (I2C)", "SDA.1 pin has a physical pull-up resistor")); + header.add(new HeaderPin(4, PinType.POWER, "5.0 VDC")); + header.add(new HeaderPin(5, PinType.DIGITAL_NO_PULL_DOWN, PinFunction.I2C, (type == 1 ? 1 : 3), 9, "SCL1 (I2C)", "SCL.1 pin has a physical pull-up resistor")); + header.add(new HeaderPin(6, PinType.GROUND, "Ground")); + header.add(new HeaderPin(7, PinType.DIGITAL, PinFunction.GPCLK, 4, 7, "GPCLK0")); + header.add(new HeaderPin(8, PinType.DIGITAL, PinFunction.UART, 14, 15, "UART TxD")); + header.add(new HeaderPin(9, PinType.GROUND, "Ground")); + header.add(new HeaderPin(10, PinType.DIGITAL, PinFunction.UART, 15, 16, "UART RxD")); + header.add(new HeaderPin(11, PinType.DIGITAL, PinFunction.SPI, 17, 0, "")); + header.add(new HeaderPin(12, PinType.DIGITAL_AND_PWM, PinFunction.SPI, 18, 1, "PCM_CLK/PWM0", "Supports PWM0 [ALT5]")); + header.add(new HeaderPin(13, PinType.DIGITAL, null, (type == 1 ? 21 : 27), 2, "")); + header.add(new HeaderPin(14, PinType.GROUND, "Ground")); + header.add(new HeaderPin(15, PinType.DIGITAL, null, 22, 3, "")); + header.add(new HeaderPin(16, PinType.DIGITAL, null, 23, 4, "")); + header.add(new HeaderPin(17, PinType.POWER, "3.3 VDC")); + header.add(new HeaderPin(18, PinType.DIGITAL, null, 24, 5, "")); + header.add(new HeaderPin(19, PinType.DIGITAL, PinFunction.SPI, 10, 12, "MOSI (SPI)")); + header.add(new HeaderPin(20, PinType.GROUND, "Ground")); + header.add(new HeaderPin(21, PinType.DIGITAL, PinFunction.SPI, 9, 13, "MISO (SPI)")); + header.add(new HeaderPin(22, PinType.DIGITAL, null, 25, 6, "")); + header.add(new HeaderPin(23, PinType.DIGITAL, PinFunction.SPI, 11, 14, "SCLK (SPI)")); + header.add(new HeaderPin(24, PinType.DIGITAL, PinFunction.SPI, 8, 10, "CE0 (SPI)")); + header.add(new HeaderPin(25, PinType.GROUND, "Ground")); + header.add(new HeaderPin(26, PinType.DIGITAL, PinFunction.SPI, 7, 11, "CE1 (SPI)")); + + return header; + } + + static List get40PinsHeader() { + List header = new ArrayList<>(); + + header.addAll(get26PinsHeader(2)); + + header.add(new HeaderPin(27, PinType.DIGITAL_NO_PULL_DOWN, PinFunction.I2C, 0, 30, "SDA0 I2C ID EEPROM", "SDA.0 pin has a physical pull-up resistor")); + header.add(new HeaderPin(28, PinType.DIGITAL_NO_PULL_DOWN, PinFunction.I2C, 1, 31, "SCL0 I2C ID EEPROM", "SDC.0 pin has a physical pull-up resistor")); + header.add(new HeaderPin(29, PinType.DIGITAL, PinFunction.GPCLK, 5, 21, "GPCLK1")); + header.add(new HeaderPin(30, PinType.GROUND, "Ground")); + header.add(new HeaderPin(31, PinType.DIGITAL, PinFunction.GPCLK, 6, 22, "GPCL2")); + header.add(new HeaderPin(32, PinType.DIGITAL_AND_PWM, null, 12, 26, "PWM0", "Supports PWM0 [ALT0]")); + header.add(new HeaderPin(33, PinType.DIGITAL_AND_PWM, null, 13, 23, "PWM1", "Supports PWM1 [ALT0]")); + header.add(new HeaderPin(34, PinType.GROUND, "Ground")); + header.add(new HeaderPin(35, PinType.DIGITAL_AND_PWM, PinFunction.SPI, 19,24, "PCM_FS/PWM1", "Supports PWM1 [ALT5]")); + header.add(new HeaderPin(36, PinType.DIGITAL, PinFunction.SPI, 16, 27, "")); + header.add(new HeaderPin(37, PinType.DIGITAL, null, 26, 25, "")); + header.add(new HeaderPin(38, PinType.DIGITAL, PinFunction.SPI, 20, 28, "PCM_DIN")); + header.add(new HeaderPin(39, PinType.GROUND, "Ground")); + header.add(new HeaderPin(40, PinType.DIGITAL, PinFunction.SPI, 21, 29, "PCM_DOUT")); + + return header; + } + + static List getComputeJ5() { + List header = new ArrayList<>(); + + // TODO + // https://pi4j.com/1.2/pins/model-cm-rev1.html#J5_Pinout_60-pin_Header + + return header; + } + + static List getComputeJ6() { + List header = new ArrayList<>(); + + // TODO + // https://pi4j.com/1.2/pins/model-cm-rev1.html#J6_Pinout_60-pin_Header + + return header; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderVersion.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderVersion.java new file mode 100644 index 00000000..5d384ea7 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/HeaderVersion.java @@ -0,0 +1,37 @@ +package com.pi4j.boardinfo.definition; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public enum HeaderVersion { + PICO("Pico", "Used on the Pico microcontroller", new ArrayList<>()), + TYPE_1("Type 1", "Used on original Model B", Collections.singletonList(HeaderPins.HEADER_26_TYPE_1)), + TYPE_2("Type 2", "Used on Model A and Model B (revision 2)", Arrays.asList(HeaderPins.HEADER_26_TYPE_2, HeaderPins.HEADER_8)), + TYPE_3("Type 3", "Used on Model A+, B+, Pi Zero, Pi Zero W, Pi2B, Pi3B, Pi4B", Collections.singletonList(HeaderPins.HEADER_40)), + COMPUTE("Compute Module", "54 GPIO", Arrays.asList(HeaderPins.COMPUTE_J5, HeaderPins.COMPUTE_J6)), + UNKNOWN("Unknown", "", new ArrayList<>()); + + private final String label; + private final String description; + private final List headerPins; + + HeaderVersion(String label, String description, List headerPins) { + this.label = label; + this.description = description; + this.headerPins = headerPins; + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } + + public List getHeaderPins() { + return headerPins; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/InstructionSet.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/InstructionSet.java new file mode 100644 index 00000000..f4028338 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/InstructionSet.java @@ -0,0 +1,19 @@ +package com.pi4j.boardinfo.definition; + +public enum InstructionSet { + ARM_V6_M("ARMv6-M"), + ARM_V6("ARMv6"), + ARM_V7("ARMv7"), + ARM_V8("ARMv8"), + UNKNOWN("Unknown"); + + private final String label; + + InstructionSet(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PiModel.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PiModel.java new file mode 100644 index 00000000..62775beb --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PiModel.java @@ -0,0 +1,28 @@ +package com.pi4j.boardinfo.definition; + +public enum PiModel { + COMPUTE("Compute Module", "Pi on a 200-pin DDR2-memory-like module for integration in embedded devices"), + MODEL_A("Model A", "Without ethernet connector"), + MODEL_B("Model B", "With ethernet connector"), + PICO("Pico", "Microcontroller"), + ZERO("Zero", "Smaller size and reduced GPIO capabilities"), + UNKNOWN("Unknown", ""); + + private final String label; + private final String description; + + PiModel(String label, String description) { + this.label = label; + this.description = description; + + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } +} + diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinFunction.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinFunction.java new file mode 100644 index 00000000..0b9f64f6 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinFunction.java @@ -0,0 +1,27 @@ +package com.pi4j.boardinfo.definition; + +/** + * List of pin functions in a header. + */ +public enum PinFunction { + UART("Universal Asynchronous Receiver and Transmitter", "Asynchronous serial communication protocol"), + GPCLK("General Purpose Clock", "Output a fixed frequency"), + I2C("Inter Integrated Circuit", "Synchronous serial computer bus"), + SPI("Serial Peripheral Interface", "Four-wire serial bus"); + + private final String label; + private final String description; + + PinFunction(String label, String description) { + this.label = label; + this.description = description; + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinType.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinType.java new file mode 100644 index 00000000..1f7a8386 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/PinType.java @@ -0,0 +1,28 @@ +package com.pi4j.boardinfo.definition; + +/** + * List of pin types in a header. + */ +public enum PinType { + POWER("Power", 0x990000), + GROUND("Ground", 0x000000), + DIGITAL("Digital", 0x009900), + DIGITAL_AND_PWM("Digital and PWM", 0xff7ff00), + DIGITAL_NO_PULL_DOWN("Digital without pulldown", 0x800080); + + private final String label; + private final int color; + + PinType(String label, int color) { + this.label = label; + this.color = color; + } + + public String getLabel() { + return label; + } + + public int getColor() { + return color; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Soc.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Soc.java new file mode 100644 index 00000000..e0710bed --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/definition/Soc.java @@ -0,0 +1,24 @@ +package com.pi4j.boardinfo.definition; + +public enum Soc { + BCM2710A1(InstructionSet.ARM_V8), + BCM2711(InstructionSet.ARM_V8), + BCM2711C0(InstructionSet.ARM_V8), + BCM2712(InstructionSet.ARM_V8), + BCM2835(InstructionSet.ARM_V6), + BCM2836(InstructionSet.ARM_V7), + BCM2837(InstructionSet.ARM_V8), + BCM2837B0(InstructionSet.ARM_V8), + RP2040(InstructionSet.ARM_V6_M), + UNKNOWN(InstructionSet.UNKNOWN); + + private final InstructionSet instructionSet; + + Soc(InstructionSet instructionSet) { + this.instructionSet = instructionSet; + } + + public InstructionSet getInstructionSet() { + return instructionSet; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardInfo.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardInfo.java new file mode 100644 index 00000000..2ec8e2dc --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/BoardInfo.java @@ -0,0 +1,28 @@ +package com.pi4j.boardinfo.model; + +import com.pi4j.boardinfo.definition.BoardModel; + +public class BoardInfo { + + private final BoardModel boardModel; + private final OperatingSystem operatingSystem; + private final JavaInfo javaInfo; + + public BoardInfo(BoardModel boardModel, OperatingSystem operatingSystem, JavaInfo javaInfo) { + this.boardModel = boardModel; + this.operatingSystem = operatingSystem; + this.javaInfo = javaInfo; + } + + public BoardModel getBoardModel() { + return boardModel; + } + + public OperatingSystem getOperatingSystem() { + return operatingSystem; + } + + public JavaInfo getJavaInfo() { + return javaInfo; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/HeaderPin.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/HeaderPin.java new file mode 100644 index 00000000..34645a71 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/HeaderPin.java @@ -0,0 +1,59 @@ +package com.pi4j.boardinfo.model; + +import com.pi4j.boardinfo.definition.PinFunction; +import com.pi4j.boardinfo.definition.PinType; + +/** + * Describes a pin in the header. + */ +public class HeaderPin { + private final int pinNumber; + private final PinType pinType; + private final PinFunction pinFunction; + private final Integer bcmNumber; + private final Integer wiringPiNumber; + private final String name; + private final String remark; + + public HeaderPin(int pinNumber, PinType pinType, String name) { + this(pinNumber, pinType, null, null, null, name, ""); + } + + public HeaderPin(int pinNumber, PinType pinType, PinFunction pinFunction, Integer bcmNumber, Integer wiringPiNumber, String name) { + this(pinNumber, pinType, pinFunction, bcmNumber, wiringPiNumber, name, ""); + } + + public HeaderPin(int pinNumber, PinType pinType, PinFunction pinFunction, Integer bcmNumber, Integer wiringPiNumber, String name, String remark) { + this.pinNumber = pinNumber; + this.pinType = pinType; + this.pinFunction = pinFunction; + this.bcmNumber = bcmNumber; + this.wiringPiNumber = wiringPiNumber; + this.name = name; + this.remark = remark; + } + + public int getPinNumber() { + return pinNumber; + } + + public PinType getPinType() { + return pinType; + } + + public PinFunction getPinFunction() { return pinFunction; } + + public Integer getBcmNumber() { return bcmNumber; } + + public Integer getWiringPiNumber() { + return wiringPiNumber; + } + + public String getName() { + return name; + } + + public String getRemark() { + return remark; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/JavaInfo.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/JavaInfo.java new file mode 100644 index 00000000..b04a7e91 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/JavaInfo.java @@ -0,0 +1,40 @@ +package com.pi4j.boardinfo.model; + +public class JavaInfo { + + private final String version; + private final String runtime; + private final String vendor; + private final String vendorVersion; + + public JavaInfo(String version, String runtime, String vendor, String vendorVersion) { + this.version = version; + this.runtime = runtime; + this.vendor = vendor; + this.vendorVersion = vendorVersion; + } + + public String getVersion() { + return version; + } + + public String getRuntime() { + return runtime; + } + + public String getVendor() { + return vendor; + } + + public String getVendorVersion() { + return vendorVersion; + } + + @Override + public String toString() { + return "Version: " + version + + ", runtime: " + runtime + + ", vendor: " + vendor + + ", vendor version: " + vendorVersion; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/model/OperatingSystem.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/OperatingSystem.java new file mode 100644 index 00000000..1489168e --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/model/OperatingSystem.java @@ -0,0 +1,33 @@ +package com.pi4j.boardinfo.model; + +public class OperatingSystem { + + private final String name; + private final String version; + private final String architecture; + + public OperatingSystem(String name, String version, String architecture) { + this.name = name; + this.version = version; + this.architecture = architecture; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } + + public String getArchitecture() { + return architecture; + } + + @Override + public String toString() { + return "Name: " + name + + ", version: " + version + + ", architecture: " + architecture; + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardModelDetection.java b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardModelDetection.java new file mode 100644 index 00000000..f249d7c5 --- /dev/null +++ b/pi4j-core/src/main/java/com/pi4j/boardinfo/util/BoardModelDetection.java @@ -0,0 +1,148 @@ +package com.pi4j.boardinfo.util; + +import com.pi4j.boardinfo.definition.BoardModel; +import com.pi4j.boardinfo.model.BoardInfo; +import com.pi4j.boardinfo.model.JavaInfo; +import com.pi4j.boardinfo.model.OperatingSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.concurrent.TimeUnit; + +public class BoardModelDetection { + + private static final Logger logger = LoggerFactory.getLogger(BoardModelDetection.class); + + private BoardModelDetection() { + // Hide constructor + } + + public static BoardInfo current() { + var os = new OperatingSystem(System.getProperty("os.name"), + System.getProperty("os.version"), + System.getProperty("os.arch")); + logger.info("Detected OS: {}", os); + + var java = new JavaInfo(System.getProperty("java.version"), + System.getProperty("java.runtime.version"), + System.getProperty("java.vendor"), + System.getProperty("java.vendor.version")); + logger.info("Detected Java: {}", java); + + // Example output: c03111 + var boardVersionCode = getBoardVersionCode(); + var boardModelByBoardCode = BoardModel.getByBoardCode(boardVersionCode); + if (boardModelByBoardCode != BoardModel.UNKNOWN) { + logger.info("Detected board type {} by code: {}", boardModelByBoardCode.name(), boardVersionCode); + return new BoardInfo(boardModelByBoardCode, os, java); + } + + // Example output: Raspberry Pi 4 Model B Rev 1.1 + var boardName = getBoardName(); + boardModelByBoardCode = BoardModel.getByBoardName(boardName); + if (boardModelByBoardCode != BoardModel.UNKNOWN) { + logger.info("Detected board type {} by name: {}", boardModelByBoardCode.name(), boardName); + return new BoardInfo(boardModelByBoardCode, os, java); + } + + // Maybe there are other ways how a board can be detected? + // If so, this method can be further extended... + logger.warn("Sorry, could not detect the board type"); + return new BoardInfo(BoardModel.UNKNOWN, os, java); + } + + public static String getBoardVersionCode() { + var output = getCommandOutput("cat /proc/cpuinfo | grep 'Revision' | awk '{print $3}'"); + if (output.isSuccess()) { + return output.getOutputMessage(); + } + logger.error("Could not get the board version code: {}", output.getErrorMessage()); + return ""; + } + + public static String getBoardName() { + var output = getCommandOutput("cat /proc/device-tree/model"); + if (output.isSuccess()) { + return output.getOutputMessage(); + } + logger.error("Could not get the board name: {}", output.getErrorMessage()); + return ""; + } + + private static class CommandResult { + private final boolean success; + private final String outputMessage; + private final String errorMessage; + + public CommandResult(boolean success, String outputMessage, String errorMessage) { + this.success = success; + this.outputMessage = outputMessage; + this.errorMessage = errorMessage; + } + + public boolean isSuccess() { + return success; + } + + public String getOutputMessage() { + return outputMessage; + } + + public String getErrorMessage() { + return errorMessage; + } + } + + private static CommandResult getCommandOutput(String command) { + boolean finished = false; + String outputMessage = ""; + String errorMessage = ""; + + ProcessBuilder builder = new ProcessBuilder(); + builder.command("sh", "-c", command); + + try { + Process process = builder.start(); + + OutputStream outputStream = process.getOutputStream(); + InputStream inputStream = process.getInputStream(); + InputStream errorStream = process.getErrorStream(); + + outputMessage = readStream(inputStream); + errorMessage = readStream(errorStream); + + finished = process.waitFor(30, TimeUnit.SECONDS); + outputStream.flush(); + outputStream.close(); + + if (!finished) { + process.destroyForcibly(); + } + } catch (IOException ex) { + errorMessage = "IOException: " + ex.getMessage(); + } catch (InterruptedException ex) { + errorMessage = "InterruptedException: " + ex.getMessage(); + } + + if (!finished || !errorMessage.isEmpty()) { + logger.error("Could not execute '{}' to detect the board model: {}", command, errorMessage); + return new CommandResult(false, outputMessage, errorMessage); + } + + return new CommandResult(true, outputMessage, errorMessage); + } + + private static String readStream(InputStream inputStream) { + StringBuilder rt = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + rt.append(line); + } + } catch (Exception ex) { + rt.append("ERROR: ").append(ex.getMessage()); + } + return rt.toString(); + } +} diff --git a/pi4j-core/src/main/java/com/pi4j/context/Context.java b/pi4j-core/src/main/java/com/pi4j/context/Context.java index c5078779..7ee9b77a 100644 --- a/pi4j-core/src/main/java/com/pi4j/context/Context.java +++ b/pi4j-core/src/main/java/com/pi4j/context/Context.java @@ -25,6 +25,10 @@ * #L% */ +import com.pi4j.boardinfo.definition.BoardModel; +import com.pi4j.boardinfo.model.BoardInfo; +import com.pi4j.boardinfo.model.JavaInfo; +import com.pi4j.boardinfo.model.OperatingSystem; import com.pi4j.common.Describable; import com.pi4j.common.Descriptor; import com.pi4j.config.Config; @@ -305,6 +309,18 @@ default T provider(IOType ioType) throws ProviderNotFoundEx throw new ProviderNotFoundException(ioType); } + // ------------------------------------------------------------------------ + // BOARD INFO ACCESSOR METHODS + // ------------------------------------------------------------------------ + + /** + * Return the BoardInfo containing more info about the + * {@link BoardModel}, {@link OperatingSystem}, and {@link JavaInfo}. + * + * @return {@link BoardInfo} + */ + BoardInfo boardInfo(); + // ------------------------------------------------------------------------ // I/O INSTANCE ACCESSOR/CREATOR METHODS // ------------------------------------------------------------------------ diff --git a/pi4j-core/src/main/java/com/pi4j/context/impl/DefaultContext.java b/pi4j-core/src/main/java/com/pi4j/context/impl/DefaultContext.java index af74d71f..7f283338 100644 --- a/pi4j-core/src/main/java/com/pi4j/context/impl/DefaultContext.java +++ b/pi4j-core/src/main/java/com/pi4j/context/impl/DefaultContext.java @@ -25,6 +25,8 @@ * #L% */ +import com.pi4j.boardinfo.model.BoardInfo; +import com.pi4j.boardinfo.util.BoardModelDetection; import com.pi4j.context.Context; import com.pi4j.context.ContextConfig; import com.pi4j.context.ContextProperties; @@ -62,6 +64,7 @@ public class DefaultContext implements Context { private Providers providers = null; private Platforms platforms = null; private Registry registry = null; + private BoardInfo boardInfo = null; /** *

newInstance.

@@ -78,7 +81,7 @@ private DefaultContext(ContextConfig config) { logger.trace("new Pi4J runtime context initialized [config={}]", config); // validate config object exists - if(config == null){ + if(config == null) { throw new LifecycleException("Unable to create new Pi4J runtime context; missing (ContextConfig) config object."); } @@ -100,6 +103,12 @@ private DefaultContext(ContextConfig config) { // create API accessible platforms instance (READ-ONLY ACCESS OBJECT) this.platforms = DefaultPlatforms.newInstance(this.runtime.platforms()); + // detect the board model + this.boardInfo = BoardModelDetection.current(); + logger.info("Detected board model: {}", boardInfo.getBoardModel().getLabel()); + logger.info("Running on: {}", boardInfo.getOperatingSystem()); + logger.info("With Java version: {}", boardInfo.getJavaInfo()); + // initialize runtime now this.runtime.initialize(); @@ -128,6 +137,10 @@ public ContextProperties properties() { @Override public Platforms platforms() { return this.platforms; } + /** {@inheritDoc} */ + @Override + public BoardInfo boardInfo() { return this.boardInfo; } + /** {@inheritDoc} */ @Override public Future submitTask(Runnable task) { diff --git a/pi4j-core/src/main/java/module-info.java b/pi4j-core/src/main/java/module-info.java index 738e308c..00706ec1 100644 --- a/pi4j-core/src/main/java/module-info.java +++ b/pi4j-core/src/main/java/module-info.java @@ -27,9 +27,11 @@ // depends on SLF4J requires org.slf4j; - // exposed interfaces/classes exports com.pi4j; + exports com.pi4j.boardinfo.definition; + exports com.pi4j.boardinfo.model; + exports com.pi4j.boardinfo.util; exports com.pi4j.common; exports com.pi4j.config; exports com.pi4j.config.exception; diff --git a/pi4j-core/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java b/pi4j-core/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java new file mode 100644 index 00000000..9c99dcbf --- /dev/null +++ b/pi4j-core/test/java/com/pi4j/boardinfo/definition/BoardModelTest.java @@ -0,0 +1,29 @@ +package com.pi4j.boardinfo.definition; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BoardModelTest { + + @Test + void testGetBoardModelByBoardCode() { + assertAll( + () -> assertEquals(BoardModel.MODEL_5_B, BoardModel.getByBoardCode("d04170")), + () -> assertEquals(BoardModel.MODEL_400, BoardModel.getByBoardCode("c03130")), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByBoardCode("a03111")), + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByBoardCode("c03112")), + () -> assertEquals(BoardModel.ZERO_V2, BoardModel.getByBoardCode("902120")), + () -> assertEquals(BoardModel.MODEL_2_B_V1_2, BoardModel.getByBoardCode("a02042")), + () -> assertEquals(BoardModel.MODEL_2_B, BoardModel.getByBoardCode("a21041")) + ); + } + + @Test + void testGetBoardModelByBoardName() { + assertAll( + () -> assertEquals(BoardModel.MODEL_4_B, BoardModel.getByBoardName("Raspberry Pi 4 Model B Rev 1.1")) + ); + } +} diff --git a/pi4j-core/test/java/com/pi4j/boardinfo/model/ModelTest.java b/pi4j-core/test/java/com/pi4j/boardinfo/model/ModelTest.java new file mode 100644 index 00000000..1e6e53f9 --- /dev/null +++ b/pi4j-core/test/java/com/pi4j/boardinfo/model/ModelTest.java @@ -0,0 +1,20 @@ +package com.pi4j.boardinfo.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ModelTest { + + @Test + void testStringOutputFromOperatingSystem() { + var os = new OperatingSystem("aaa", "bbb", "ccc"); + assertEquals("Name: aaa, version: bbb, architecture: ccc", os.toString()); + } + + @Test + void testStringOutputFromJavaInfo() { + var java = new JavaInfo("aaa", "bbb", "ccc", "ddd"); + assertEquals("Version: aaa, runtime: bbb, vendor: ccc, vendor version: ddd", java.toString()); + } +} diff --git a/pi4j-core/test/java/com/pi4j/boardinfo/util/BoardModelDetectionTest.java b/pi4j-core/test/java/com/pi4j/boardinfo/util/BoardModelDetectionTest.java new file mode 100644 index 00000000..b6108f67 --- /dev/null +++ b/pi4j-core/test/java/com/pi4j/boardinfo/util/BoardModelDetectionTest.java @@ -0,0 +1,28 @@ +package com.pi4j.boardinfo.util; + +import com.pi4j.boardinfo.definition.PiModel; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BoardModelDetectionTest { + + @Test + void testGetDetectedBoard() { + var detectedBoard = BoardModelDetection.current(); + + assertAll( + () -> assertEquals(detectedBoard.getOperatingSystem().getName(), System.getProperty("os.name")), + () -> assertEquals(detectedBoard.getOperatingSystem().getVersion(), System.getProperty("os.version")), + () -> assertEquals(detectedBoard.getOperatingSystem().getArchitecture(), System.getProperty("os.arch")), + + () -> assertEquals(detectedBoard.getJavaInfo().getVersion(), System.getProperty("java.version")), + () -> assertEquals(detectedBoard.getJavaInfo().getRuntime(), System.getProperty("java.runtime.version")), + () -> assertEquals(detectedBoard.getJavaInfo().getVendor(), System.getProperty("java.vendor")), + () -> assertEquals(detectedBoard.getJavaInfo().getVendorVersion(), System.getProperty("java.vendor.version")), + + () -> assertEquals(detectedBoard.getBoardModel().getModel(), PiModel.UNKNOWN) // Only valid on PC, macOS or build server + ); + } +}