diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java new file mode 100644 index 00000000..390c6606 --- /dev/null +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -0,0 +1,40 @@ +package com.opensourcewithslu.components.controllers; + +import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; +import com.pi4j.context.Context; +import com.pi4j.io.i2c.I2CConfig; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import jakarta.inject.Named; + +//tag::ex[] +@Controller("/four-digit-seven-segment") +public class FourDigitSevenSegmentDisplayController { + private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; + + public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") I2CConfig fourdigsevenseg, Context pi4jContext + ) { + this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, pi4jContext); + } + + @Get("/enable") + public void enable() { + fourDigitSevenSegmentDisplayHelper.setEnabled(true); + } + + @Get("/displayValue/{value}") + public void displayValue(String value) { + fourDigitSevenSegmentDisplayHelper.displayValue(value); + } + + @Get("/disable") + public void disable() { + fourDigitSevenSegmentDisplayHelper.setEnabled(false); + } + + @Get("/clear") + public void clear() { + fourDigitSevenSegmentDisplayHelper.clear(); + } +} +//end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 9e27c6d2..466b89df 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -62,6 +62,10 @@ pi4j: name: lcd # <2> bus: 1 # <3> device: 0x27 # <4> + four-digit-seven-segment: + name: 4 Digit 7 Segment Display + bus: 1 + device: 0x27 # end::i2c[] # tag::digitalInput[] diff --git a/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc b/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc new file mode 100644 index 00000000..e4f69753 --- /dev/null +++ b/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc @@ -0,0 +1,118 @@ +:imagesdir: img/ + +ifndef::rootpath[] +:rootpath: ../../ +endif::rootpath[] + +ifdef::rootpath[] +:imagesdir: {rootpath}{imagesdir} +endif::rootpath[] + +==== 4-Digit 7-Segment Display + +[.text-right] +https://github.com/oss-slu/Pi4Micronaut/edit/develop/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc[Improve this doc] + +===== Overview + +This section provides details of the 4-digit 7-segment display, including its components and assembly instructions. + +===== Components + +* 1 x Raspberry Pi +* 1 x Breadboard +* 1 x T-Extension Board +* 25 x Jumper Wire +* 4 x Resistor (220Ω) +* 1 x 4-Digit 7-Segment Display +* 1 x 74HC595 +* Power source (appropriate voltage, typically 3.3V) + +===== Assembly Instructions + +* Connect the ground (GND) pins of the Raspberry Pi to the ground rails on the breadboard. +* Connect the two ground rails of the breadboard with a jumper wire. +* Place the 74HC595 on the breadboard. +* Place the four resistors on the breadboard. +* Place the 4-digit 7-segment display on the breadboard. +* Connect a jumper wire from each "digit" pin of the 4-digit 7-segment to one of the resistors. +* Connect the other end of the resistors to the Raspberry Pi's pins: + +- Digit 1 to GPIO17 (BCM pin 18) +- Digit 2 to GPIO27 (BCM pin 27) +- Digit 3 to GPIO22 (BCM pin 22) +- Digit 4 to SPIMOSI (BCM pin 10) + +// TODO: Describe connections to 74HC595 + +===== Circuit Diagram + +image::four_digit_circuit.webp[] + +===== Schematic Diagram + +image::four_digit_schematic.webp[] + +===== Functionality + +The display can be enabled/disabled, display a custom value, or cleared. + +Each digit of the display can display a digit 0 to 9, an uppercase letter A to F, a hypen (-), or a blank space. +Each of the four decimal points can also be turned on or off. + +Example possible values include: + +* "1" (displayed with the 1 in the first digit and the others blank) +* "8.8.8.8." (displayed with all segments enabled as `8.8.8.8.`) +* "A.-.42" (displayed as ```A.-.42```) + +===== Testing + +Use the below commands to test the component. +This will cause the display to turn on and display the value `1234`. + +[source,bash] +---- +$ curl http://localhost:8080/four-digit-seven-segment/enable +$ curl http://localhost:8080/four-digit-seven-segment/displayValue/1234 +---- + +* `/enable` - Enables the display. +* `/disable` - Disables the display. +* `/displayValue/{value}` - Displays a custom value on the display. +* `/clear` - Clears the display. + +===== Troubleshooting + +* Display does not turn on +- Ensure all connections are secure and correct. +- Check the 74HC595 for proper orientation and placement. +- Ensure the software configuration matches the hardware setup. +- Look for any error messages in the console or logs. + +* Display turns on but does not respond to commands +- Check the software configuration for any discrepancies. +- Ensure the 74HC595 is functioning properly. + +===== YAML Configuration + +[source,yaml] +---- +i2c: +four-digit-seven-segment: + name: 4 Digit 7 Segment Display + bus: 1 + device: 0x27 +---- + +===== Constructor and Methods + +To see the constructor and methods of our FourDigitSevenSegmentHelper class see our javadoc link:https://oss-slu.github.io/Pi4Micronaut/javadoc/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentHelper.html[here] +for more details. + +===== An Example Controller + +[source,java] +---- +include::../../../../../../components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java[tag=ex] +---- diff --git a/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_circuit.webp b/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_circuit.webp new file mode 100644 index 00000000..8761985a Binary files /dev/null and b/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_circuit.webp differ diff --git a/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_schematic.webp b/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_schematic.webp new file mode 100644 index 00000000..2b981db2 Binary files /dev/null and b/pi4micronaut-utils/src/docs/asciidoc/img/four_digit_schematic.webp differ diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java new file mode 100644 index 00000000..52bdf20e --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -0,0 +1,122 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.context.Context; +import com.pi4j.io.i2c.I2CConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.pi4j.crowpi.components.SevenSegmentComponent; + +public class FourDigitSevenSegmentDisplayHelper { + private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + private final SevenSegmentComponent display; + private String displayValue; + + /** + * Constructs a new FourDigitSevenSegmentDisplayHelper. + * + * @param i2cConfig the I2C configuration + * @param pi4jContext the Pi4J context + */ + //tag::const[] + public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) + //end::const[] + { + this.display = new SevenSegmentComponent(pi4jContext, i2cConfig.getBus(), i2cConfig.getDevice()); + } + + /** + * Enables or disables the display. + * + * @param enabled True to enable, false to disable + */ + //tag::method[] + public void setEnabled(boolean enabled) + //end::method[] + { + display.setEnabled(enabled); + this.displayValue = ""; + } + + /** + * Displays a value on the four-digit seven-segment display. + * + * @param value The value to display. It can include digits 0-9, letters A-F (case-insensitive), + * hyphens, spaces, and decimal points. The value must not have more than 4 non-decimal + * point characters, no consecutive decimal points, and if there are 4 non-decimal + * point characters, decimal points must not appear on the ends. + */ + //tag::method[] + public void displayValue(String value) + //end::method[] + { + // Parse out the decimal points + String noDecimals = value.replaceAll("\\.", ""); + + // Check: No more than 4 non-decimal point characters long + if (noDecimals.length() > 4) { + log.error("Display value must not have more than 4 non-decimal point characters"); + return; + } + + // Check: No consecutive decimal points + if (value.contains("..")) { + log.error("Display value cannot have consecutive decimal points"); + return; + } + + // Check: If there are 4 non-decimal point characters, then decimal points must not appear on the ends + if (noDecimals.length() == 4 && (value.startsWith(".") || value.endsWith("."))) { + log.error("Display value must have decimal points appearing strictly between the digits"); + return; + } + + // Check: Non-decimal point characters must be digits 0 to 1, letters A to F (case-insensitive), -, or space + String valid = "1234567890ABCDEFabcdef- "; + for (char character : noDecimals.toCharArray()) { + if (valid.indexOf(character) == -1) { + log.error("Each display value digit must be numeric, a letter A to F (case insensitive), a hyphen, or a space"); + return; + } + } + + display.print(value); + display.refresh(); + value = value.toUpperCase(); + log.info("Displaying value: {}", value); + this.displayValue = value; + } + + /** + * Clears the display. + */ + //tag::method[] + public void clear() + //end::method[] + { + for (int i = 0; i < 4; i++) { + display.clear(); + display.setDecimalPoint(i, false); + } + display.setColon(false); + display.refresh(); + this.displayValue = ""; + } + + /** + * Sets the logger object. + * + * @param log Logger object to set the logger to. + */ + public void setLog(Logger log) { + FourDigitSevenSegmentDisplayHelper.log = log; + } + + /** + * Gets the display value. + * + * @return The display value + */ + public String getDisplayValue() { + return displayValue; + } +} diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java new file mode 100644 index 00000000..48163b5c --- /dev/null +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -0,0 +1,272 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.context.ContextProperties; +import com.pi4j.io.i2c.I2CConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.pi4j.context.Context; +import com.pi4j.crowpi.components.SevenSegmentComponent; + +import java.lang.reflect.Field; + +public class FourDigitSevenSegmentDisplayHelperTest { + I2CConfig i2cConfig = mock(I2CConfig.class); + Context pi4jContext = mock(Context.class); + ContextProperties contextProperties = mock(ContextProperties.class); + FourDigitSevenSegmentDisplayHelper displayHelper; + SevenSegmentComponent displayComponent = mock(SevenSegmentComponent.class); + Logger log = mock(Logger.class); + + @BeforeEach + void setUp() throws Exception { + // Mock the Context to return a non-null ContextProperties + when(pi4jContext.properties()).thenReturn(contextProperties); + + displayHelper = new FourDigitSevenSegmentDisplayHelper(i2cConfig, pi4jContext); + + // Use reflection to set the mock SevenSegmentComponent + Field displayField = FourDigitSevenSegmentDisplayHelper.class.getDeclaredField("display"); + displayField.setAccessible(true); + displayField.set(displayHelper, displayComponent); + } + + @BeforeEach + public void openMocks() { + displayHelper.setLog(log); + } + + @Test + void longNumberFails() { + String value = "12345"; + displayHelper.displayValue(value); + verify(log).error("Display value must not have more than 4 non-decimal point characters"); + verify(log, never()).info("Displaying value: {}", "12345"); + verify(displayComponent, never()).print("12345"); + } + + @Test + void consecutiveDecimalPointsFails() { + String value = "1..23"; + displayHelper.displayValue(value); + verify(log).error("Display value cannot have consecutive decimal points"); + verify(log, never()).info("Displaying value: {}", "1..23"); + verify(displayComponent, never()).print("1..23"); + } + + @Test + void decimalPointOnLeftEndFails() { + String value = ".1234"; + displayHelper.displayValue(value); + verify(log).error("Display value must have decimal points appearing strictly between the digits"); + verify(log, never()).info("Displaying value: {}", ".1234"); + verify(displayComponent, never()).print(".1234"); + } + + @Test + void decimalPointOnRightEndFails() { + String value = "1234."; + displayHelper.displayValue(value); + verify(log).error("Display value must have decimal points appearing strictly between the digits"); + verify(log, never()).info("Displaying value: {}", "1234."); + verify(displayComponent, never()).print("1234."); + } + + @Test + void decimalPointsOnBothEndsFails() { + String value = ".1234."; + displayHelper.displayValue(value); + verify(log).error("Display value must have decimal points appearing strictly between the digits"); + verify(log, never()).info("Displaying value: {}", ".1234."); + verify(displayComponent, never()).print(".1234."); + } + + @Test + void invalidCharacterFails() { + String value = "G"; + displayHelper.displayValue(value); + verify(log).error("Each display value digit must be numeric, a letter A to F (case insensitive), a hyphen, or a space"); + verify(log, never()).info("Displaying value: {}", "G"); + verify(displayComponent, never()).print("G"); + } + + @Test + void displaysLetters() { + String value = "ABCD"; + displayHelper.displayValue(value); + verify(log).info("Displaying value: {}", "ABCD"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("ABCD", displayed); + } + + @Test + void displaysLettersWithLowercase() { + String value = "abcd"; + displayHelper.displayValue(value); + verify(log).info("Displaying value: {}", "ABCD"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("ABCD", displayed); + } + + @Test + void displaysLettersWithMixedCase() { + String value = "aBcD"; + displayHelper.displayValue(value); + verify(log).info("Displaying value: {}", "ABCD"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("ABCD", displayed); + } + + @Test + void displaysHyphen() { + String value = "-"; + displayHelper.displayValue(value); + verify(log).info("Displaying value: {}", "-"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("-", displayed); + } + + @Test + void displaysSpaces() { + String value = "2 2"; + displayHelper.displayValue(value); + verify(log).info("Displaying value: {}", "2 2"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("2 2", displayed); + } + + @Test + void displaysNegativeNumber() { + String number = "-123"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "-123"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("-123", displayed); + } + + @Test + void displaysFourDigitNumber() { + String number = "1234"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "1234"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("1234", displayed); + } + + @Test + void displaysThreeDigitNumber() { + String number = "123"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "123"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("123", displayed); + } + + @Test + void displaysTwoDigitNumber() { + String number = "34"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "34"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("34", displayed); + } + + @Test + void displaysOneDigitNumber() { + String number = "4"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "4"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("4", displayed); + } + + @Test + void displaysBlankValue() { + String number = ""; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", ""); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("", displayed); + } + + @Test + void displaysDecimalNumber() { + String number = "1.23"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "1.23"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("1.23", displayed); + } + + @Test + void displaysDecimalNumberWithLeadingDecimal() { + String number = ".23"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", ".23"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals(".23", displayed); + } + + @Test + void displaysDecimalNumberWithTrailingDecimal() { + String number = "1."; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "1."); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("1.", displayed); + } + + @Test + void displaysMultipleDecimals() { + String number = "1.2.3.4"; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "1.2.3.4"); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("1.2.3.4", displayed); + } + + @Test + void displaysSpacesAndDecimals() { + String number = " . . . "; + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", " . . . "); + + String displayed = displayHelper.getDisplayValue(); + assertEquals(" . . . ", displayed); + } + + @Test + void clearDisplay() { + String number = "1234"; + displayHelper.displayValue(number); + + displayHelper.clear(); + verify(displayComponent, times(4)).clear(); + verify(displayComponent, times(4)).setDecimalPoint(anyInt(), eq(false)); + verify(displayComponent).setColon(false); + verify(displayComponent, times(2)).refresh(); + + String displayed = displayHelper.getDisplayValue(); + assertEquals("", displayed); + } +}