From 0b3405fa094474ee98849003398ed615fc52eeb0 Mon Sep 17 00:00:00 2001 From: j-heitz Date: Wed, 8 Oct 2025 14:18:30 -0500 Subject: [PATCH 1/5] Add ShiftRegister74HC595Helper class with SPI communication methods and logging --- .../ShiftRegister74HC595Helper.java | 66 +++++++++++++++++++ .../ShiftRegister74HC595HelperTest.java | 5 ++ 2 files changed, 71 insertions(+) create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java create mode 100644 pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java new file mode 100644 index 00000000..9a9308b6 --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java @@ -0,0 +1,66 @@ +package com.opensourcewithslu.outputdevices; + +import java.io.IOException; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.pi4j.io.spi.Spi; + +public class ShiftRegister74HC595Helper { + + private static final Logger log = LoggerFactory.getLogger(ShiftRegister74HC595Helper.class); + private final Spi spi; + private byte state = 0x00; + + + public ShiftRegister74HC595Helper(Spi spi) { + this.spi = Objects.requireNonNull(spi, "spi must not be null"); + log.info("74HC595 helper initialized; state=0x00"); + } + + + public void shiftOut(byte value) throws IOException { + try { + spi.write(value); + this.state = value; + log.info("Shifted out 0x{}", toHex(this.state)); + } catch (Exception e) { + String msg = String.format("SPI write failed for 0x%s: %s", toHex(value), e.getMessage()); + log.error(msg, e); + throw new IOException(msg, e); + } + } + + + public void clear() throws IOException { + shiftOut((byte) 0x00); + log.info("Shift register cleared"); + } + + // Method to set a specific bit + public void setBit(int bitIndex, boolean value) throws IOException { + if (bitIndex < 0 || bitIndex > 7) { + throw new IllegalArgumentException("bitIndex must be 0..7; got " + bitIndex); + } + if (value) { + state |= (1 << bitIndex); + } else { + state &= ~(1 << bitIndex); + } + shiftOut(state); + log.info("Bit {} set to {}", bitIndex, value); + } + + + public void clearBit(int bitIndex) throws IOException { + setBit(bitIndex, false); + log.info("Bit {} cleared", bitIndex); + } + + + private static String toHex(byte b) { + return String.format("%02X", b & 0xFF); + } +} \ No newline at end of file diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java new file mode 100644 index 00000000..50b57bc1 --- /dev/null +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java @@ -0,0 +1,5 @@ +package com.opensourcewithslu.outputdevices; + +public class ShiftRegister74HC595HelperTest { + +} From 1359401994bc58e494464361614207fe3bbc4723 Mon Sep 17 00:00:00 2001 From: j-heitz Date: Wed, 8 Oct 2025 15:27:22 -0500 Subject: [PATCH 2/5] added getState method in ShiftRegister74HC595Helper --- .../outputdevices/ShiftRegister74HC595Helper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java index 9a9308b6..4b596955 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java @@ -39,7 +39,6 @@ public void clear() throws IOException { log.info("Shift register cleared"); } - // Method to set a specific bit public void setBit(int bitIndex, boolean value) throws IOException { if (bitIndex < 0 || bitIndex > 7) { throw new IllegalArgumentException("bitIndex must be 0..7; got " + bitIndex); @@ -59,6 +58,10 @@ public void clearBit(int bitIndex) throws IOException { log.info("Bit {} cleared", bitIndex); } + public byte getState() { + return state; + } + private static String toHex(byte b) { return String.format("%02X", b & 0xFF); From 1c87241c1a9e8e89b235ae290880e4c05d5b3b19 Mon Sep 17 00:00:00 2001 From: j-heitz Date: Wed, 8 Oct 2025 20:48:00 -0500 Subject: [PATCH 3/5] Add unit tests for ShiftRegister74HC595Helper methods and improve test coverage --- .../ShiftRegister74HC595Helper.java | 2 + .../ShiftRegister74HC595HelperTest.java | 75 ++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java index 4b596955..24ef8bf1 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java @@ -39,6 +39,7 @@ public void clear() throws IOException { log.info("Shift register cleared"); } + public void setBit(int bitIndex, boolean value) throws IOException { if (bitIndex < 0 || bitIndex > 7) { throw new IllegalArgumentException("bitIndex must be 0..7; got " + bitIndex); @@ -57,6 +58,7 @@ public void clearBit(int bitIndex) throws IOException { setBit(bitIndex, false); log.info("Bit {} cleared", bitIndex); } + public byte getState() { return state; diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java index 50b57bc1..8326edfe 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595HelperTest.java @@ -1,5 +1,78 @@ package com.opensourcewithslu.outputdevices; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.pi4j.io.spi.Spi; + public class ShiftRegister74HC595HelperTest { - + + @Mock + private Spi spi; + + private ShiftRegister74HC595Helper helper; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + helper = new ShiftRegister74HC595Helper(spi); + } + + @Test + public void testShiftOut_ValidByte() throws IOException { + byte value = 0x5A; + helper.shiftOut(value); + verify(spi, times(1)).write(value); + } + + @Test + public void testShiftOut_SpiException() throws IOException { + byte value = 0x5A; + doThrow(new RuntimeException("SPI error")).when(spi).write(value); + IOException exception = assertThrows(IOException.class, () -> helper.shiftOut(value)); + assertTrue(exception.getMessage().contains("SPI write failed")); + } + + @Test + public void testClear() throws IOException { + helper.clear(); + verify(spi, times(1)).write((byte) 0x00); + } + + @Test + public void testSetBit_ValidIndex() throws IOException { + helper.setBit(3, true); + verify(spi, times(1)).write((byte) 0x08); + helper.setBit(3, false); + verify(spi, times(1)).write((byte) 0x00); + } + + @Test + public void testSetBit_InvalidIndex() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> helper.setBit(8, true)); + assertEquals("bitIndex must be 0..7; got 8", exception.getMessage()); + } + + @Test + public void testClearBit() throws IOException { + helper.setBit(2, true); // Set bit 2 first + verify(spi, times(1)).write((byte) 0x04); + helper.clearBit(2); + verify(spi, times(1)).write((byte) 0x00); + } + + @Test + public void testGetState() throws IOException { + assertEquals(0x00, helper.getState()); + helper.setBit(1, true); + assertEquals(0x02, helper.getState()); + } + } From 475182026b4b65b323fa83f9c0b40f0d76af3a74 Mon Sep 17 00:00:00 2001 From: j-heitz Date: Mon, 13 Oct 2025 10:31:41 -0500 Subject: [PATCH 4/5] Added Java doc comments to ShiftRegister74HC595Helper --- .../ShiftRegister74HC595Helper.java | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java index 24ef8bf1..0273d03a 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java @@ -11,16 +11,32 @@ public class ShiftRegister74HC595Helper { private static final Logger log = LoggerFactory.getLogger(ShiftRegister74HC595Helper.class); - private final Spi spi; - private byte state = 0x00; + private final Spi spi; + private byte state = 0x00; - + /** + * Constructs a new ShiftRegister74HC595Helper with the specified SPI interface. + * + * Initializes the helper with a clean state (0x00) and sets up logging. + * + * + * @param spi the SPI interface to use for communication with the shift register + * @throws NullPointerException if spi is null + */ public ShiftRegister74HC595Helper(Spi spi) { this.spi = Objects.requireNonNull(spi, "spi must not be null"); log.info("74HC595 helper initialized; state=0x00"); } - + /** + * Shifts out a byte value to the 74HC595 shift register via SPI. + * + * This method writes the specified byte value to the shift register and + * updates the internal state tracking. The operation is logged for debugging. + * + * @param value the byte value to shift out to the register + * @throws IOException if the SPI write operation fails + */ public void shiftOut(byte value) throws IOException { try { spi.write(value); @@ -33,13 +49,29 @@ public void shiftOut(byte value) throws IOException { } } - + /** + * Clears the shift register by setting all outputs to LOW (0x00). + * + * This is equivalent to calling {@code shiftOut((byte) 0x00)}. + * + * @throws IOException if the SPI write operation fails + */ public void clear() throws IOException { shiftOut((byte) 0x00); log.info("Shift register cleared"); } - + /** + * Sets or clears a specific bit in the shift register. + * + * This method modifies the internal state by setting or clearing the specified + * bit index (0-7), then shifts out the updated state to the register. + * + * @param bitIndex the index of the bit to modify (0-7, where 0 is LSB) + * @param value {@code true} to set the bit to HIGH, {@code false} to set to LOW + * @throws IllegalArgumentException if bitIndex is not in the range 0-7 + * @throws IOException if the SPI write operation fails + */ public void setBit(int bitIndex, boolean value) throws IOException { if (bitIndex < 0 || bitIndex > 7) { throw new IllegalArgumentException("bitIndex must be 0..7; got " + bitIndex); @@ -53,18 +85,42 @@ public void setBit(int bitIndex, boolean value) throws IOException { log.info("Bit {} set to {}", bitIndex, value); } - + /** + * Clears (sets to LOW) a specific bit in the shift register. + * + * This is a convenience method equivalent to calling {@code setBit(bitIndex, false)}. + * + * @param bitIndex the index of the bit to clear (0-7, where 0 is LSB) + * @throws IllegalArgumentException if bitIndex is not in the range 0-7 + * @throws IOException if the SPI write operation fails + */ public void clearBit(int bitIndex) throws IOException { setBit(bitIndex, false); log.info("Bit {} cleared", bitIndex); } - + /** + * Returns the current state of the shift register. + * + * This method returns the internally tracked state without performing + * any SPI operations. The state reflects the last value written to + * the shift register. + * + * @return the current state as a byte value + */ public byte getState() { return state; } - + /** + * Converts a byte value to its hexadecimal string representation. + * + * This is a utility method used for logging purposes to display + * byte values in a readable hexadecimal format. + * + * @param b the byte value to convert + * @return a two-character uppercase hexadecimal string representation + */ private static String toHex(byte b) { return String.format("%02X", b & 0xFF); } From ab1cd337b6b8c4ff1fd600503f0aada0d970dc40 Mon Sep 17 00:00:00 2001 From: j-heitz Date: Mon, 13 Oct 2025 21:31:18 -0500 Subject: [PATCH 5/5] Refactor documentation for ShiftRegister74HC595Helper class --- .../outputdevices/ShiftRegister74HC595Helper.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java index 0273d03a..caaf0fae 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/ShiftRegister74HC595Helper.java @@ -8,6 +8,18 @@ import com.pi4j.io.spi.Spi; +/** + * Helper for controlling a 74HC595 shift register over SPI (Pi4J). + * + * Provides simple operations to write a full byte to the shift register, + * set or clear individual output bits, clear the register, + * and read the last written state. All SPI communication is performed using + * the provided {@link com.pi4j.io.spi.Spi} instance. + * + * The helper maintains an internal state byte reflecting the last value + * written to the device. + * + */ public class ShiftRegister74HC595Helper { private static final Logger log = LoggerFactory.getLogger(ShiftRegister74HC595Helper.class);