From a683e8467c98549a04f2abd01c04e76e7cdfe044 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 26 Oct 2024 20:50:41 -0500 Subject: [PATCH 01/33] Added 4-digit 7-segment display YAML config, basic controller, and helper with four displays initialized. --- ...ourDigitSevenSegmentDisplayController.java | 20 +++++++++++++ components/src/main/resources/application.yml | 12 +++++++- .../FourDigitSevenSegmentDisplayHelper.java | 28 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java 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..21fbef76 --- /dev/null +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -0,0 +1,20 @@ +package com.opensourcewithslu.components.controllers; + +import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; +import com.opensourcewithslu.utilities.MultiPinConfiguration; +import com.pi4j.context.Context; +import com.pi4j.io.gpio.digital.DigitalOutput; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import jakarta.inject.Named; + +//tag::ex[] +@Controller("/4digit7segment") +public class FourDigitSevenSegmentDisplayController { + private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; + + public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg) { + this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); + } +} +//end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index f235092c..d2956d2e 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -199,4 +199,14 @@ pi4j: provider: pigpio-pwm initials: 0, 0, 0 shutdowns: 0, 0, 0 - # end::multiPWM[] \ No newline at end of file + # end::multiPWM[] + + # tag::multiOutput[] + multi-digital-output: + 4digit7segment: + name: 4 Digit 7 Segment Display + addresses: 17, 18, 27, 22 + shutdowns: LOW, LOW, LOW, LOW + initials: HIGH, HIGH, HIGH, HIGH + provider: pigpio-digital-output + # end::multiOutput[] 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..5b9c2003 --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -0,0 +1,28 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.pwm.Pwm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.opensourcewithslu.utilities.MultiPinConfiguration; + +public class FourDigitSevenSegmentDisplayHelper { + private static Logger log = LoggerFactory.getLogger(LEDHelper.class); + + private final DigitalOutput display1; + private final DigitalOutput display2; + private final DigitalOutput display3; + private final DigitalOutput display4; + + //tag::const[] + public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevenseg) + //end::const[] + { + DigitalOutput[] displays = (DigitalOutput[]) fourdigitsevenseg.getComponents(); + this.display1 = displays[0]; + this.display2 = displays[1]; + this.display3 = displays[2]; + this.display4 = displays[3]; + } +} From b563b5d4e0f41e5d9b97fcd4c062c2affbc1a4c8 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 28 Oct 2024 09:54:19 -0500 Subject: [PATCH 02/33] Added endpoint to display a number, and functionality for it using tentative implementation of single-digit seven-segment display helper. --- ...ourDigitSevenSegmentDisplayController.java | 16 +++++- .../FourDigitSevenSegmentDisplayHelper.java | 50 ++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 21fbef76..a0e5361b 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -3,6 +3,7 @@ import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; import com.opensourcewithslu.utilities.MultiPinConfiguration; import com.pi4j.context.Context; +import com.pi4j.io.gpio.digital.Digital; import com.pi4j.io.gpio.digital.DigitalOutput; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; @@ -13,8 +14,19 @@ public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg) { - this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); + public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg, + @Named("display1") DigitalOutput display1, + @Named("display1") DigitalOutput display2, + @Named("display1") DigitalOutput display3, + @Named("display1") DigitalOutput display4 + ) { + DigitalOutput[] displayList = {display1, display2, display3, display4}; + this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, displayList); + } + + @Get("/displayNumber/{number}") + public void displayNumber(String number) { + fourDigitSevenSegmentDisplayHelper.displayNumber(number); } } //end::ex[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 5b9c2003..9783143f 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import com.pi4j.io.gpio.digital.DigitalOutput; import com.opensourcewithslu.utilities.MultiPinConfiguration; +import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; public class FourDigitSevenSegmentDisplayHelper { private static Logger log = LoggerFactory.getLogger(LEDHelper.class); @@ -15,8 +16,13 @@ public class FourDigitSevenSegmentDisplayHelper { private final DigitalOutput display3; private final DigitalOutput display4; + private final SevenSegmentDisplayHelper sevenSegmentDisplay1; + private final SevenSegmentDisplayHelper sevenSegmentDisplay2; + private final SevenSegmentDisplayHelper sevenSegmentDisplay3; + private final SevenSegmentDisplayHelper sevenSegmentDisplay4; + //tag::const[] - public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevenseg) + public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevenseg, DigitalOutput[] displayList) //end::const[] { DigitalOutput[] displays = (DigitalOutput[]) fourdigitsevenseg.getComponents(); @@ -24,5 +30,47 @@ public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevense this.display2 = displays[1]; this.display3 = displays[2]; this.display4 = displays[3]; + + sevenSegmentDisplay1 = new SevenSegmentDisplayHelper(displayList[0]); + sevenSegmentDisplay2 = new SevenSegmentDisplayHelper(displayList[1]); + sevenSegmentDisplay3 = new SevenSegmentDisplayHelper(displayList[2]); + sevenSegmentDisplay4 = new SevenSegmentDisplayHelper(displayList[3]); + } + + //tag::method[] + public void displayNumber(String number) + //end::method[] + { + char[] num = number.toCharArray(); + + if (num.length > 4) { + log.error("Number is too long"); + return; + } + if (num.length == 0) { + log.error("Number is too short"); + return; + } + + log.info("Displaying number: " + number); + + display2.low(); + display3.low(); + display4.low(); + + display1.high(); + sevenSegmentDisplay1.displayNumber(num[0]); + if (num.length > 1) { + display2.high(); + sevenSegmentDisplay2.disaplNumber(num[1]); + } + if (num.length > 2) { + display3.high(); + sevenSegmentDisplay2.disaplNumber(num[2]); + } + if (num.length > 3) { + display4.high(); + sevenSegmentDisplay2.disaplNumber(num[3]); + } } } From 1cfff19ba74dfe82ef361dd9443b705afc1038ba Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 28 Oct 2024 12:31:27 -0500 Subject: [PATCH 03/33] Modified according to Seyun's in-progress seven-segment display helper --- .../FourDigitSevenSegmentDisplayHelper.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 9783143f..8fb729de 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -31,10 +31,10 @@ public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevense this.display3 = displays[2]; this.display4 = displays[3]; - sevenSegmentDisplay1 = new SevenSegmentDisplayHelper(displayList[0]); - sevenSegmentDisplay2 = new SevenSegmentDisplayHelper(displayList[1]); - sevenSegmentDisplay3 = new SevenSegmentDisplayHelper(displayList[2]); - sevenSegmentDisplay4 = new SevenSegmentDisplayHelper(displayList[3]); + sevenSegmentDisplay1 = new SevenSegmentDisplayHelper(); + sevenSegmentDisplay2 = new SevenSegmentDisplayHelper(); + sevenSegmentDisplay3 = new SevenSegmentDisplayHelper(); + sevenSegmentDisplay4 = new SevenSegmentDisplayHelper(); } //tag::method[] @@ -54,23 +54,22 @@ public void displayNumber(String number) log.info("Displaying number: " + number); - display2.low(); - display3.low(); - display4.low(); + sevenSegmentDisplay2.resetDisplay(); + sevenSegmentDisplay3.resetDisplay(); + sevenSegmentDisplay4.resetDisplay(); - display1.high(); sevenSegmentDisplay1.displayNumber(num[0]); if (num.length > 1) { display2.high(); - sevenSegmentDisplay2.disaplNumber(num[1]); + sevenSegmentDisplay2.displayNumber(num[1]); } if (num.length > 2) { display3.high(); - sevenSegmentDisplay2.disaplNumber(num[2]); + sevenSegmentDisplay2.displayNumber(num[2]); } if (num.length > 3) { display4.high(); - sevenSegmentDisplay2.disaplNumber(num[3]); + sevenSegmentDisplay2.displayNumber(num[3]); } } } From 9c2f30287bccca42eb799190a7b930c1ead75799 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 28 Oct 2024 16:02:17 -0500 Subject: [PATCH 04/33] Updated pins. Also the 4-digit display now renders numbers to have leading zeroes --- components/src/main/resources/application.yml | 2 +- .../FourDigitSevenSegmentDisplayHelper.java | 50 +++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index d2956d2e..8578dac4 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -205,7 +205,7 @@ pi4j: multi-digital-output: 4digit7segment: name: 4 Digit 7 Segment Display - addresses: 17, 18, 27, 22 + addresses: 1, 2, 24, 25 shutdowns: LOW, LOW, LOW, LOW initials: HIGH, HIGH, HIGH, HIGH provider: pigpio-digital-output diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 8fb729de..c73a11bf 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -54,22 +54,44 @@ public void displayNumber(String number) log.info("Displaying number: " + number); + resetDisplay(); + + // Render numbers with leading zeroes, e.g. 1 as 0001 + switch (num.length) { + case 1: + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber(num[0]); + break; + case 2: + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber(num[0]); + sevenSegmentDisplay1.displayNumber(num[1]); + break; + case 3: + sevenSegmentDisplay1.displayNumber("0"); + sevenSegmentDisplay1.displayNumber(num[0]); + sevenSegmentDisplay1.displayNumber(num[1]); + sevenSegmentDisplay1.displayNumber(num[2]); + break; + case 4: + sevenSegmentDisplay1.displayNumber(num[0]); + sevenSegmentDisplay1.displayNumber(num[1]); + sevenSegmentDisplay1.displayNumber(num[2]); + sevenSegmentDisplay1.displayNumber(num[3]); + break; + } + } + + //tag::method[] + public void resetDisplay() + //end::method[] + { + sevenSegmentDisplay1.resetDisplay(); sevenSegmentDisplay2.resetDisplay(); sevenSegmentDisplay3.resetDisplay(); sevenSegmentDisplay4.resetDisplay(); - - sevenSegmentDisplay1.displayNumber(num[0]); - if (num.length > 1) { - display2.high(); - sevenSegmentDisplay2.displayNumber(num[1]); - } - if (num.length > 2) { - display3.high(); - sevenSegmentDisplay2.displayNumber(num[2]); - } - if (num.length > 3) { - display4.high(); - sevenSegmentDisplay2.displayNumber(num[3]); - } } } From ea01936ebadd8fd68823d2a4543cfadf07b5e527 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Thu, 31 Oct 2024 15:10:04 -0500 Subject: [PATCH 05/33] Added getValue(), edited 7-segment display helper function calls to correct type, added tests --- .../FourDigitSevenSegmentDisplayHelper.java | 40 +++++---- ...ourDigitSevenSegmentDisplayHelperTest.java | 84 +++++++++++++++++++ 2 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index c73a11bf..52af9edc 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -59,28 +59,28 @@ public void displayNumber(String number) // Render numbers with leading zeroes, e.g. 1 as 0001 switch (num.length) { case 1: - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber(num[0]); + sevenSegmentDisplay1.displayNumber(0); + sevenSegmentDisplay2.displayNumber(0); + sevenSegmentDisplay3.displayNumber(0); + sevenSegmentDisplay4.displayNumber((int) num[0]); break; case 2: - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber(num[0]); - sevenSegmentDisplay1.displayNumber(num[1]); + sevenSegmentDisplay1.displayNumber(0); + sevenSegmentDisplay2.displayNumber(0); + sevenSegmentDisplay3.displayNumber((int) num[0]); + sevenSegmentDisplay4.displayNumber((int) num[1]); break; case 3: - sevenSegmentDisplay1.displayNumber("0"); - sevenSegmentDisplay1.displayNumber(num[0]); - sevenSegmentDisplay1.displayNumber(num[1]); - sevenSegmentDisplay1.displayNumber(num[2]); + sevenSegmentDisplay1.displayNumber(0); + sevenSegmentDisplay2.displayNumber((int) num[0]); + sevenSegmentDisplay3.displayNumber((int) num[1]); + sevenSegmentDisplay4.displayNumber((int) num[2]); break; case 4: - sevenSegmentDisplay1.displayNumber(num[0]); - sevenSegmentDisplay1.displayNumber(num[1]); - sevenSegmentDisplay1.displayNumber(num[2]); - sevenSegmentDisplay1.displayNumber(num[3]); + sevenSegmentDisplay1.displayNumber((int) num[0]); + sevenSegmentDisplay2.displayNumber((int) num[1]); + sevenSegmentDisplay3.displayNumber((int) num[2]); + sevenSegmentDisplay4.displayNumber((int) num[3]); break; } } @@ -94,4 +94,12 @@ public void resetDisplay() sevenSegmentDisplay3.resetDisplay(); sevenSegmentDisplay4.resetDisplay(); } + + //tag::method[] + public int getValue() + //end::method[] + { + int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; + return Integer.parseInt(String.valueOf(values)); + } } 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..ec531609 --- /dev/null +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -0,0 +1,84 @@ +package com.opensourcewithslu.outputdevices; + +import org.junit.jupiter.api.Test; + +import org.slf4j.Logger; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; + +public class FourDigitSevenSegmentDisplayHelperTest { + FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); + SevenSegmentDisplayHelper sevenSegmentDisplayHelper = mock(SevenSegmentDisplayHelper.class); + Logger log = mock(Logger.class); + + @Test + void longNumberFails() { + String number = "12345"; + displayHelper.displayNumber(number); + verify(log).error("Number is too long"); + verify(log, never()).info("Displaying number: " + "12345"); + verify(sevenSegmentDisplayHelper, never()).displayNumber(12345); + } + + void shortNumberFails() { + String number = ""; + displayHelper.displayNumber(number); + verify(log).error("Number is too short"); + verify(log, never()).info("Displaying number: " + ""); + } + + @Test + void nonNumberFails() { + } + + @Test + void negativeNumberFails() { + } + + @Test + void displaysFourDigitNumber() { + String number = "1234"; + displayHelper.displayNumber(number); + verify(displayHelper).displayNumber("1234"); + verify(log).info("Displaying number: " + "1234"); + + int displayed = displayHelper.getValue(); + assertEquals(1234, displayed); + } + + @Test + void displaysThreeDigitNumber() { + String number = "123"; + displayHelper.displayNumber(number); + verify(displayHelper).displayNumber("123"); + verify(log).info("Displaying number: " + "123"); + + int displayed = displayHelper.getValue(); + assertEquals(123, displayed); + } + + @Test + void displaysTwoDigitNumber() { + String number = "34"; + displayHelper.displayNumber(number); + verify(displayHelper).displayNumber("34"); + verify(log).info("Displaying number: " + "34"); + + int displayed = displayHelper.getValue(); + assertEquals(34, displayed); + } + + @Test + void displaysOneDigitNumber() { + String number = "4"; + displayHelper.displayNumber(number); + verify(displayHelper).displayNumber("4"); + verify(log).info("Displaying number: " + "4"); + + int displayed = displayHelper.getValue(); + assertEquals(4, displayed); + } +} From 230c3ceafac41997384cdc0e05aab575bbb76146 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Thu, 31 Oct 2024 15:19:42 -0500 Subject: [PATCH 06/33] Added more tests, fixed log initialization typos --- .../FourDigitSevenSegmentDisplayHelper.java | 14 +++++++++-- ...ourDigitSevenSegmentDisplayHelperTest.java | 24 +++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 52af9edc..5163c683 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -9,7 +9,7 @@ import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; public class FourDigitSevenSegmentDisplayHelper { - private static Logger log = LoggerFactory.getLogger(LEDHelper.class); + private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); private final DigitalOutput display1; private final DigitalOutput display2; @@ -51,8 +51,18 @@ public void displayNumber(String number) log.error("Number is too short"); return; } + try { + int integerNumber = Integer.parseInt(number); + if (integerNumber < 0) { + log.error("Display value must be positive"); + return; + } + } catch (NumberFormatException e) { + log.error("Display value must be an integer"); + return; + } - log.info("Displaying number: " + number); + log.info("Displaying number: {}", number); resetDisplay(); diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java index ec531609..fba0d8ad 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -19,23 +19,33 @@ void longNumberFails() { String number = "12345"; displayHelper.displayNumber(number); verify(log).error("Number is too long"); - verify(log, never()).info("Displaying number: " + "12345"); - verify(sevenSegmentDisplayHelper, never()).displayNumber(12345); + verify(log, never()).info("Displaying number: {}", "12345"); + verify(sevenSegmentDisplayHelper, never()).displayNumber(1); + verify(sevenSegmentDisplayHelper, never()).displayNumber(2); + verify(sevenSegmentDisplayHelper, never()).displayNumber(3); + verify(sevenSegmentDisplayHelper, never()).displayNumber(4); + verify(sevenSegmentDisplayHelper, never()).displayNumber(5); } void shortNumberFails() { String number = ""; displayHelper.displayNumber(number); verify(log).error("Number is too short"); - verify(log, never()).info("Displaying number: " + ""); + verify(log, never()).info("Displaying number: {}", ""); } @Test void nonNumberFails() { + String number = "abc"; + displayHelper.displayNumber(number); + verify(log).error("Display value must be an integer"); } @Test void negativeNumberFails() { + String number = "-1234"; + displayHelper.displayNumber(number); + verify(log).error("Display value must be positive"); } @Test @@ -43,7 +53,7 @@ void displaysFourDigitNumber() { String number = "1234"; displayHelper.displayNumber(number); verify(displayHelper).displayNumber("1234"); - verify(log).info("Displaying number: " + "1234"); + verify(log).info("Displaying number: {}", "1234"); int displayed = displayHelper.getValue(); assertEquals(1234, displayed); @@ -54,7 +64,7 @@ void displaysThreeDigitNumber() { String number = "123"; displayHelper.displayNumber(number); verify(displayHelper).displayNumber("123"); - verify(log).info("Displaying number: " + "123"); + verify(log).info("Displaying number: {}", "123"); int displayed = displayHelper.getValue(); assertEquals(123, displayed); @@ -65,7 +75,7 @@ void displaysTwoDigitNumber() { String number = "34"; displayHelper.displayNumber(number); verify(displayHelper).displayNumber("34"); - verify(log).info("Displaying number: " + "34"); + verify(log).info("Displaying number: {}", "34"); int displayed = displayHelper.getValue(); assertEquals(34, displayed); @@ -76,7 +86,7 @@ void displaysOneDigitNumber() { String number = "4"; displayHelper.displayNumber(number); verify(displayHelper).displayNumber("4"); - verify(log).info("Displaying number: " + "4"); + verify(log).info("Displaying number: {}", "4"); int displayed = displayHelper.getValue(); assertEquals(4, displayed); From 673910ba24b75f95a1abdd1899f42601d9c93817 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 2 Nov 2024 21:44:18 -0500 Subject: [PATCH 07/33] 4-digit 7-segment display no longer reliant on code for 7-segment display. Commented out tests. Stubbed out method for displaying numbers, for testing --- ...ourDigitSevenSegmentDisplayController.java | 18 +-- components/src/main/resources/application.yml | 22 ++- .../FourDigitSevenSegmentDisplayHelper.java | 142 ++++++++++++------ ...ourDigitSevenSegmentDisplayHelperTest.java | 20 +-- 4 files changed, 123 insertions(+), 79 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index a0e5361b..38c79452 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -2,9 +2,6 @@ import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; import com.opensourcewithslu.utilities.MultiPinConfiguration; -import com.pi4j.context.Context; -import com.pi4j.io.gpio.digital.Digital; -import com.pi4j.io.gpio.digital.DigitalOutput; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import jakarta.inject.Named; @@ -14,19 +11,14 @@ public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg, - @Named("display1") DigitalOutput display1, - @Named("display1") DigitalOutput display2, - @Named("display1") DigitalOutput display3, - @Named("display1") DigitalOutput display4 + public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg ) { - DigitalOutput[] displayList = {display1, display2, display3, display4}; - this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, displayList); + this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); } - @Get("/displayNumber/{number}") - public void displayNumber(String number) { - fourDigitSevenSegmentDisplayHelper.displayNumber(number); + @Get("/displayNumber/{value}") + public void displayValue(String value) { + fourDigitSevenSegmentDisplayHelper.displayValue(value); } } //end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 6740b9bd..01b442a8 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -199,14 +199,22 @@ pi4j: provider: pigpio-pwm initials: 0, 0, 0 shutdowns: 0, 0, 0 + # multi-digital-output: + # 4digit7segment: + # name: 4 Digit 7 Segment Display + # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + # pwmTypes: SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE + # provider: pigpio-pwm # end::multiPWM[] # tag::multiOutput[] - multi-digital-output: - 4digit7segment: - name: 4 Digit 7 Segment Display - addresses: 1, 2, 24, 25 - shutdowns: LOW, LOW, LOW, LOW - initials: HIGH, HIGH, HIGH, HIGH - provider: pigpio-digital-output + multi-digital-output: + 4digit7segment: + name: 4 Digit 7 Segment Display + addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + provider: pigpio-digital-output # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 5163c683..dc467530 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -4,44 +4,86 @@ import com.pi4j.io.pwm.Pwm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.pi4j.io.gpio.digital.DigitalOutput; import com.opensourcewithslu.utilities.MultiPinConfiguration; -import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; public class FourDigitSevenSegmentDisplayHelper { private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); - private final DigitalOutput display1; - private final DigitalOutput display2; - private final DigitalOutput display3; - private final DigitalOutput display4; +/* private int[][] charMap = { + {1, 1, 1, 1, 1, 1, 0}, + {0, 1, 1, 0, 0, 0, 0}, + {1, 1, 0, 1, 1, 0, 1}, + {1, 1, 1, 1, 0, 0, 1}, + {0, 1, 1, 0, 0, 1, 1}, + {1, 0, 1, 1, 0, 1, 1}, + {1, 0, 1, 1, 1, 1, 1}, + {1, 1, 1, 0, 0, 0, 0}, + {1, 1, 1, 1, 1, 1, 1}, + {1, 1, 1, 1, 0, 1, 1}, + {0, 0, 0, 0, 0, 0, 1} + };*/ + + private enum CharMap { + ZERO(new int[]{1, 1, 1, 1, 1, 1, 0}), + ONE(new int[]{0, 1, 1, 0, 0, 0, 0}), + TWO(new int[]{1, 1, 0, 1, 1, 0, 1}), + THREE(new int[]{1, 1, 1, 1, 0, 0, 1}), + FOUR(new int[]{0, 1, 1, 0, 0, 1, 1}), + FIVE(new int[]{1, 0, 1, 1, 0, 1, 1}), + SIX(new int[]{1, 0, 1, 1, 1, 1, 1}), + SEVEN(new int[]{1, 1, 1, 0, 0, 0, 0}), + EIGHT(new int[]{1, 1, 1, 1, 1, 1, 1}), + NINE(new int[]{1, 1, 1, 1, 0, 1, 1}), + HYPHEN(new int[]{0, 0, 0, 0, 0, 0, 1}); + + private int[] segmentArray; + + public int[] getSegmentArray() { + return this.segmentArray; + } + + CharMap(int[] segmentArray) { + this.segmentArray = segmentArray; + } + } - private final SevenSegmentDisplayHelper sevenSegmentDisplay1; - private final SevenSegmentDisplayHelper sevenSegmentDisplay2; - private final SevenSegmentDisplayHelper sevenSegmentDisplay3; - private final SevenSegmentDisplayHelper sevenSegmentDisplay4; + private final DigitalOutput segmentA; + private final DigitalOutput segmentB; + private final DigitalOutput segmentC; + private final DigitalOutput segmentD; + private final DigitalOutput segmentE; + private final DigitalOutput segmentF; + private final DigitalOutput segmentG; + private final DigitalOutput DP; + private final DigitalOutput digit0; + private final DigitalOutput digit1; + private final DigitalOutput digit2; + private final DigitalOutput digit3; //tag::const[] - public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourdigitsevenseg, DigitalOutput[] displayList) + public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourDigitSevenSeg) //end::const[] { - DigitalOutput[] displays = (DigitalOutput[]) fourdigitsevenseg.getComponents(); - this.display1 = displays[0]; - this.display2 = displays[1]; - this.display3 = displays[2]; - this.display4 = displays[3]; - - sevenSegmentDisplay1 = new SevenSegmentDisplayHelper(); - sevenSegmentDisplay2 = new SevenSegmentDisplayHelper(); - sevenSegmentDisplay3 = new SevenSegmentDisplayHelper(); - sevenSegmentDisplay4 = new SevenSegmentDisplayHelper(); + DigitalOutput[] components = (DigitalOutput[]) fourDigitSevenSeg.getComponents(); + this.segmentA = components[0]; + this.segmentB = components[1]; + this.segmentC = components[2]; + this.segmentD = components[3]; + this.segmentE = components[4]; + this.segmentF = components[5]; + this.segmentG = components[6]; + this.DP = components[7]; + this.digit0 = components[8]; + this.digit1 = components[9]; + this.digit2 = components[10]; + this.digit3 = components[11]; } //tag::method[] - public void displayNumber(String number) + public void displayValue(String value) //end::method[] { - char[] num = number.toCharArray(); + char[] num = value.toCharArray(); if (num.length > 4) { log.error("Number is too long"); @@ -52,7 +94,7 @@ public void displayNumber(String number) return; } try { - int integerNumber = Integer.parseInt(number); + int integerNumber = Integer.parseInt(value); if (integerNumber < 0) { log.error("Display value must be positive"); return; @@ -62,54 +104,56 @@ public void displayNumber(String number) return; } - log.info("Displaying number: {}", number); + log.info("Displaying number: {}", value); - resetDisplay(); + // TODO: replace with actual implementation + setValue(digit0, CharMap.FOUR); // Render numbers with leading zeroes, e.g. 1 as 0001 - switch (num.length) { +/* switch (num.length) { case 1: - sevenSegmentDisplay1.displayNumber(0); + *//*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber(0); sevenSegmentDisplay3.displayNumber(0); - sevenSegmentDisplay4.displayNumber((int) num[0]); + sevenSegmentDisplay4.displayNumber((int) num[0]);*//* break; case 2: - sevenSegmentDisplay1.displayNumber(0); + *//*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber(0); sevenSegmentDisplay3.displayNumber((int) num[0]); - sevenSegmentDisplay4.displayNumber((int) num[1]); + sevenSegmentDisplay4.displayNumber((int) num[1]);*//* break; case 3: - sevenSegmentDisplay1.displayNumber(0); + *//*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber((int) num[0]); sevenSegmentDisplay3.displayNumber((int) num[1]); - sevenSegmentDisplay4.displayNumber((int) num[2]); + sevenSegmentDisplay4.displayNumber((int) num[2]);*//* break; case 4: - sevenSegmentDisplay1.displayNumber((int) num[0]); + *//*sevenSegmentDisplay1.displayNumber((int) num[0]); sevenSegmentDisplay2.displayNumber((int) num[1]); sevenSegmentDisplay3.displayNumber((int) num[2]); - sevenSegmentDisplay4.displayNumber((int) num[3]); + sevenSegmentDisplay4.displayNumber((int) num[3]);*//* break; - } + }*/ } - //tag::method[] - public void resetDisplay() + /*//tag::method[] + public String getValue() //end::method[] { - sevenSegmentDisplay1.resetDisplay(); - sevenSegmentDisplay2.resetDisplay(); - sevenSegmentDisplay3.resetDisplay(); - sevenSegmentDisplay4.resetDisplay(); - } + *//*int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; + return Integer.parseInt(String.valueOf(values));*//* + }*/ - //tag::method[] - public int getValue() - //end::method[] - { - int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; - return Integer.parseInt(String.valueOf(values)); + private void setValue(DigitalOutput digit, CharMap character) { + digit.high(); + segmentA.setState(character.getSegmentArray()[0]); + segmentB.setState(character.getSegmentArray()[1]); + segmentC.setState(character.getSegmentArray()[2]); + segmentD.setState(character.getSegmentArray()[3]); + segmentE.setState(character.getSegmentArray()[4]); + segmentF.setState(character.getSegmentArray()[5]); + segmentG.setState(character.getSegmentArray()[6]); } } diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java index fba0d8ad..94b1de6b 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -10,7 +10,7 @@ import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; public class FourDigitSevenSegmentDisplayHelperTest { - FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); + /*FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); SevenSegmentDisplayHelper sevenSegmentDisplayHelper = mock(SevenSegmentDisplayHelper.class); Logger log = mock(Logger.class); @@ -55,8 +55,8 @@ void displaysFourDigitNumber() { verify(displayHelper).displayNumber("1234"); verify(log).info("Displaying number: {}", "1234"); - int displayed = displayHelper.getValue(); - assertEquals(1234, displayed); +// int displayed = displayHelper.getValue(); +// assertEquals(1234, displayed); } @Test @@ -66,8 +66,8 @@ void displaysThreeDigitNumber() { verify(displayHelper).displayNumber("123"); verify(log).info("Displaying number: {}", "123"); - int displayed = displayHelper.getValue(); - assertEquals(123, displayed); +// int displayed = displayHelper.getValue(); +// assertEquals(123, displayed); } @Test @@ -77,8 +77,8 @@ void displaysTwoDigitNumber() { verify(displayHelper).displayNumber("34"); verify(log).info("Displaying number: {}", "34"); - int displayed = displayHelper.getValue(); - assertEquals(34, displayed); +// int displayed = displayHelper.getValue(); +// assertEquals(34, displayed); } @Test @@ -88,7 +88,7 @@ void displaysOneDigitNumber() { verify(displayHelper).displayNumber("4"); verify(log).info("Displaying number: {}", "4"); - int displayed = displayHelper.getValue(); - assertEquals(4, displayed); - } +// int displayed = displayHelper.getValue(); +// assertEquals(4, displayed); + }*/ } From 8a795a1c218634d8af74bdb682593b7c0cdfad43 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 2 Nov 2024 21:53:52 -0500 Subject: [PATCH 08/33] Added digital output multi-pin configuration, since the 4-digit 7-segment display requires multiple digital output pins. --- .../DigitalOutputMultiPinConfiguration.java | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java new file mode 100644 index 00000000..d4d56e3b --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java @@ -0,0 +1,132 @@ +package com.opensourcewithslu.utilities.MultiPinConfigs; + +import io.micronaut.context.annotation.EachProperty; +import io.micronaut.context.annotation.Parameter; +import io.micronaut.context.annotation.Prototype; + +import java.util.Arrays; + +/** + * This class handles the configuration of a digital output component that has multiple pins. + */ +@Prototype +@EachProperty("pi4j.multi-digital-output") +public class DigitalOutputMultiPinConfiguration { + private final String id; + private String name; + private int[] addresses; + private int[] initials; + private int[] shutdowns; + private String provider; + + /** + * The DigitalOutputMultiPinConfiguration constructor. + * + * @param id The configuration id as defined in the application.yml + */ + public DigitalOutputMultiPinConfiguration(@Parameter String id) { + this.id = id + "MultiPin"; + } + + /** + * Gets the id of the component. + * + * @return The id of the component. + */ + public String getId() { + return id; + } + + /** + * Gets the name of the component. + * + * @return The name of the component. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the component. + * + * @param name The string name to replace the existing name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the pin addresses for the component. + * + * @return An array of the pin addresses. + */ + public int[] getAddresses() { + return addresses; + } + + /** + * Sets the pin addresses for the component. All previously existing address are replaced. + * + * @param addresses Pin addresses separated by a comma. + */ + public void setAddresses(String addresses) { + addresses = addresses.replaceAll("\\s", ""); + this.addresses = Arrays.stream(addresses.split(",")).mapToInt(Integer::parseInt).toArray(); + } + + /** + * Gets the initial states that the component is in when first initialized. + * + * @return Array of integers representing the initial state for each pin. + */ + public int[] getInitials() { + return initials; + } + + /** + * Sets the initial states for the component. + * + * @param initials String of states separated by commas. + */ + public void setInitials(String initials) { + initials = initials.replaceAll("\\s", ""); + this.initials = Arrays.stream(initials.split(",")).mapToInt(Integer::parseInt).toArray(); + } + + /** + * Gets the shutdown states for the component. + * + * @return Array of integers representing the shutdowns. + */ + public int[] getShutdowns() { + return shutdowns; + } + + /** + * Sets the shutdown states for the component. Existing shutdowns are replaced. + * + * @param shutdowns String of shutdowns separated by commas. + */ + public void setShutdowns(String shutdowns) { + shutdowns = shutdowns.replaceAll("\\s", ""); + this.shutdowns = Arrays.stream(shutdowns.split(",")).mapToInt(Integer::parseInt).toArray(); + } + + /** + * Gets the provider for the component. + * + * @return A String representation of the provider. + */ + public String getProvider() { + return provider; + } + + /** + * Sets the provider. + * + * @param provider The new provider for the component. + */ + public void setProvider(String provider) { + this.provider = provider; + } +} From 70b7f75c3a2211292b7f5644d322da7a9babca8f Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 3 Nov 2024 14:37:30 -0600 Subject: [PATCH 09/33] multi pin factory can generate multi digital output config --- .../utilities/Pi4JMultiPinFactory.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java index 980708be..1e2e1e9c 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java @@ -2,8 +2,11 @@ import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalInputMultiPinConfiguration; import com.opensourcewithslu.utilities.MultiPinConfigs.PwmMultiPinConfiguration; +import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalOutputMultiPinConfiguration; import com.pi4j.context.Context; import com.pi4j.io.gpio.digital.DigitalInput; +import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.gpio.digital.DigitalState; import com.pi4j.io.pwm.Pwm; import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; @@ -77,4 +80,33 @@ public MultiPinConfiguration multiPinPwm(PwmMultiPinConfiguration config, Contex String multiPinId = config.getId().substring(0, config.getId().length() - 8); return new MultiPinConfiguration(multiPinId, allPWMs); } + + + /** + * Creates a MultiPinConfiguration object for a multi pin digital output component. + * + * @param config {@link DigitalOutputMultiPinConfiguration} Object. + * @param pi4jContext The Pi4J {@link Context}. + * @return A MultiPinConfiguration object. + */ + @Singleton + @EachBean(DigitalOutputMultiPinConfiguration.class) + public MultiPinConfiguration multiPinOutput(DigitalOutputMultiPinConfiguration config, Context pi4jContext) { + int[] addresses = config.getAddresses(); + DigitalOutput[] allOutputs = new DigitalOutput[addresses.length]; + + for (int i = 0; i < addresses.length; i++) { + var outputConfigBuilder = DigitalOutput.newConfigBuilder(pi4jContext) + .id(config.getId() + i) + .name(config.getName()) + .address(config.getAddresses()[i]) + .initial(DigitalState.state(config.getInitials()[i])) + .shutdown(DigitalState.state(config.getShutdowns()[i])) + .provider(config.getProvider()); + + allOutputs[i] = pi4jContext.create(outputConfigBuilder); + } + + return new MultiPinConfiguration(config.getId(), allOutputs); + } } From 2fa38447f202c681ce3636bb3a39e840ad200c24 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 3 Nov 2024 15:07:53 -0600 Subject: [PATCH 10/33] updates to my attempt to create a multi digital output configuration --- components/src/main/resources/application.yml | 29 ++++--- .../FourDigitSevenSegmentDisplayHelper.java | 79 +++++++++++++++++-- .../DigitalOutputMultiPinConfiguration.java | 23 +++--- .../utilities/Pi4JMultiPinFactory.java | 4 +- 4 files changed, 99 insertions(+), 36 deletions(-) diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 01b442a8..d28d75bc 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -199,22 +199,21 @@ pi4j: provider: pigpio-pwm initials: 0, 0, 0 shutdowns: 0, 0, 0 - # multi-digital-output: - # 4digit7segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - # pwmTypes: SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE - # provider: pigpio-pwm + # 4digit7segment: + # name: 4 Digit 7 Segment Display + # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # shutdowns: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + # initials: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + # pwmTypes: SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE + # provider: pigpio-pwm # end::multiPWM[] # tag::multiOutput[] - multi-digital-output: - 4digit7segment: - name: 4 Digit 7 Segment Display - addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - provider: pigpio-digital-output + multi-digital-output: + 4digit7segment: + name: 4 Digit 7 Segment Display + addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + provider: pigpio-digital-output # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index dc467530..3976233e 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -147,13 +147,78 @@ public String getValue() }*/ private void setValue(DigitalOutput digit, CharMap character) { + /*segmentA.off(); + segmentB.off(); + segmentC.off(); + segmentD.off(); + segmentE.off(); + segmentF.off(); + segmentG.off(); + + digit.on(100, 50); + for (int i = 0; i < 7; i++) { + if (character.getSegmentArray()[i] == 1) { + switch (i) { + case 0: + segmentA.on(100, 50); + break; + case 1: + segmentB.on(100, 50); + break; + case 2: + segmentC.on(100, 50); + break; + case 3: + segmentD.on(100, 50); + break; + case 4: + segmentE.on(100, 50); + break; + case 5: + segmentF.on(100, 50); + break; + case 6: + segmentG.on(100, 50); + break; + } + } + }*/ + + segmentA.low(); + segmentB.low(); + segmentC.low(); + segmentD.low(); + segmentE.low(); + segmentF.low(); + segmentG.low(); + digit.high(); - segmentA.setState(character.getSegmentArray()[0]); - segmentB.setState(character.getSegmentArray()[1]); - segmentC.setState(character.getSegmentArray()[2]); - segmentD.setState(character.getSegmentArray()[3]); - segmentE.setState(character.getSegmentArray()[4]); - segmentF.setState(character.getSegmentArray()[5]); - segmentG.setState(character.getSegmentArray()[6]); + for (int i = 0; i < 7; i++) { + if (character.getSegmentArray()[i] == 1) { + switch (i) { + case 0: + segmentA.high(); + break; + case 1: + segmentB.high(); + break; + case 2: + segmentC.high(); + break; + case 3: + segmentD.high(); + break; + case 4: + segmentE.high(); + break; + case 5: + segmentF.high(); + break; + case 6: + segmentG.high(); + break; + } + } + } } } diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java index d4d56e3b..0c13389a 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java @@ -1,5 +1,6 @@ package com.opensourcewithslu.utilities.MultiPinConfigs; +import com.pi4j.io.gpio.digital.DigitalState; import io.micronaut.context.annotation.EachProperty; import io.micronaut.context.annotation.Parameter; import io.micronaut.context.annotation.Prototype; @@ -15,8 +16,8 @@ public class DigitalOutputMultiPinConfiguration { private final String id; private String name; private int[] addresses; - private int[] initials; - private int[] shutdowns; + private DigitalState[] initials; + private DigitalState[] shutdowns; private String provider; /** @@ -79,18 +80,17 @@ public void setAddresses(String addresses) { * * @return Array of integers representing the initial state for each pin. */ - public int[] getInitials() { + public DigitalState[] getInitials() { return initials; } /** * Sets the initial states for the component. * - * @param initials String of states separated by commas. + * @param initials Array of initial Digital states. */ - public void setInitials(String initials) { - initials = initials.replaceAll("\\s", ""); - this.initials = Arrays.stream(initials.split(",")).mapToInt(Integer::parseInt).toArray(); + public void setInitials(DigitalState[] initials) { + this.initials = initials; } /** @@ -98,18 +98,17 @@ public void setInitials(String initials) { * * @return Array of integers representing the shutdowns. */ - public int[] getShutdowns() { + public DigitalState[] getShutdowns() { return shutdowns; } /** * Sets the shutdown states for the component. Existing shutdowns are replaced. * - * @param shutdowns String of shutdowns separated by commas. + * @param shutdowns Array of shutdowns Digital states. */ - public void setShutdowns(String shutdowns) { - shutdowns = shutdowns.replaceAll("\\s", ""); - this.shutdowns = Arrays.stream(shutdowns.split(",")).mapToInt(Integer::parseInt).toArray(); + public void setShutdowns(DigitalState[] shutdowns) { + this.shutdowns = shutdowns; } /** diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java index 1e2e1e9c..c4939f50 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java @@ -100,8 +100,8 @@ public MultiPinConfiguration multiPinOutput(DigitalOutputMultiPinConfiguration c .id(config.getId() + i) .name(config.getName()) .address(config.getAddresses()[i]) - .initial(DigitalState.state(config.getInitials()[i])) - .shutdown(DigitalState.state(config.getShutdowns()[i])) + .initial(config.getInitials()[i]) + .shutdown(config.getShutdowns()[i]) .provider(config.getProvider()); allOutputs[i] = pi4jContext.create(outputConfigBuilder); From 0260263e528d538cd0b8c359d29466e331998d74 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 3 Nov 2024 15:41:42 -0600 Subject: [PATCH 11/33] attempt to convert to i2c --- ...ourDigitSevenSegmentDisplayController.java | 6 +- components/src/main/resources/application.yml | 56 ++++- .../FourDigitSevenSegmentDisplayHelper.java | 220 +++--------------- 3 files changed, 88 insertions(+), 194 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 38c79452..3c1874a5 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -2,6 +2,8 @@ import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; import com.opensourcewithslu.utilities.MultiPinConfiguration; +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; @@ -11,9 +13,9 @@ public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg + public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") I2CConfig fourdigsevenseg, Context pi4jContext ) { - this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); + this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, pi4jContext); } @Get("/displayNumber/{value}") diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index d28d75bc..41aeaaa4 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -57,6 +57,48 @@ pi4j: name: lcd # <2> bus: 1 # <3> device: 0x27 # <4> + 4digit7segment: + name: 4 Digit 7 Segment Display + bus: 1 + device: 0x3F + segments: + segmentA: + pin: 18 + state: "LOW" + segmentB: + pin: 22 + state: "LOW" + segmentC: + pin: 24 + state: "LOW" + segmentD: + pin: 26 + state: "LOW" + segmentE: + pin: 28 + state: "LOW" + segmentF: + pin: 32 + state: "LOW" + segmentG: + pin: 36 + state: "LOW" + decimal-point: + pin: 38 + state: "LOW" + digits: + digit0: + pin: 11 + state: "LOW" + digit1: + pin: 13 + state: "LOW" + digit2: + pin: 15 + state: "LOW" + digit3: + pin: 17 + state: "LOW" # end::i2c[] # tag::digitalInput[] @@ -209,11 +251,11 @@ pi4j: # end::multiPWM[] # tag::multiOutput[] - multi-digital-output: - 4digit7segment: - name: 4 Digit 7 Segment Display - addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - provider: pigpio-digital-output + # multi-digital-output: + # 4digit7segment: + # name: 4 Digit 7 Segment Display + # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + # provider: pigpio-digital-output # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 3976233e..02ba1b88 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -1,95 +1,50 @@ package com.opensourcewithslu.outputdevices; -import com.pi4j.io.gpio.digital.DigitalOutput; -import com.pi4j.io.pwm.Pwm; +import com.pi4j.context.Context; +import com.pi4j.io.i2c.I2C; +import com.pi4j.io.i2c.I2CConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.opensourcewithslu.utilities.MultiPinConfiguration; public class FourDigitSevenSegmentDisplayHelper { - private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); -/* private int[][] charMap = { - {1, 1, 1, 1, 1, 1, 0}, - {0, 1, 1, 0, 0, 0, 0}, - {1, 1, 0, 1, 1, 0, 1}, - {1, 1, 1, 1, 0, 0, 1}, - {0, 1, 1, 0, 0, 1, 1}, - {1, 0, 1, 1, 0, 1, 1}, - {1, 0, 1, 1, 1, 1, 1}, - {1, 1, 1, 0, 0, 0, 0}, - {1, 1, 1, 1, 1, 1, 1}, - {1, 1, 1, 1, 0, 1, 1}, - {0, 0, 0, 0, 0, 0, 1} - };*/ + private final I2C i2c; private enum CharMap { - ZERO(new int[]{1, 1, 1, 1, 1, 1, 0}), - ONE(new int[]{0, 1, 1, 0, 0, 0, 0}), - TWO(new int[]{1, 1, 0, 1, 1, 0, 1}), - THREE(new int[]{1, 1, 1, 1, 0, 0, 1}), - FOUR(new int[]{0, 1, 1, 0, 0, 1, 1}), - FIVE(new int[]{1, 0, 1, 1, 0, 1, 1}), - SIX(new int[]{1, 0, 1, 1, 1, 1, 1}), - SEVEN(new int[]{1, 1, 1, 0, 0, 0, 0}), - EIGHT(new int[]{1, 1, 1, 1, 1, 1, 1}), - NINE(new int[]{1, 1, 1, 1, 0, 1, 1}), - HYPHEN(new int[]{0, 0, 0, 0, 0, 0, 1}); - - private int[] segmentArray; - - public int[] getSegmentArray() { - return this.segmentArray; + ZERO(0x3F), + ONE(0x06), + TWO(0x5B), + THREE(0x4F), + FOUR(0x66), + FIVE(0x6D), + SIX(0x7D), + SEVEN(0x07), + EIGHT(0x7F), + NINE(0x6F), + HYPHEN(0x40); + + private final int segmentValue; + + CharMap(int segmentValue) { + this.segmentValue = segmentValue; } - CharMap(int[] segmentArray) { - this.segmentArray = segmentArray; + public int getSegmentValue() { + return segmentValue; } } - private final DigitalOutput segmentA; - private final DigitalOutput segmentB; - private final DigitalOutput segmentC; - private final DigitalOutput segmentD; - private final DigitalOutput segmentE; - private final DigitalOutput segmentF; - private final DigitalOutput segmentG; - private final DigitalOutput DP; - private final DigitalOutput digit0; - private final DigitalOutput digit1; - private final DigitalOutput digit2; - private final DigitalOutput digit3; - - //tag::const[] - public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourDigitSevenSeg) - //end::const[] - { - DigitalOutput[] components = (DigitalOutput[]) fourDigitSevenSeg.getComponents(); - this.segmentA = components[0]; - this.segmentB = components[1]; - this.segmentC = components[2]; - this.segmentD = components[3]; - this.segmentE = components[4]; - this.segmentF = components[5]; - this.segmentG = components[6]; - this.DP = components[7]; - this.digit0 = components[8]; - this.digit1 = components[9]; - this.digit2 = components[10]; - this.digit3 = components[11]; + public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) { + this.i2c = pi4jContext.create(i2cConfig); } - //tag::method[] - public void displayValue(String value) - //end::method[] - { - char[] num = value.toCharArray(); - - if (num.length > 4) { + public void displayValue(String value) { + if (value.length() > 4) { log.error("Number is too long"); return; } - if (num.length == 0) { + if (value.length() == 0) { log.error("Number is too short"); return; } @@ -106,119 +61,14 @@ public void displayValue(String value) log.info("Displaying number: {}", value); - // TODO: replace with actual implementation - setValue(digit0, CharMap.FOUR); - - // Render numbers with leading zeroes, e.g. 1 as 0001 -/* switch (num.length) { - case 1: - *//*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber(0); - sevenSegmentDisplay3.displayNumber(0); - sevenSegmentDisplay4.displayNumber((int) num[0]);*//* - break; - case 2: - *//*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber(0); - sevenSegmentDisplay3.displayNumber((int) num[0]); - sevenSegmentDisplay4.displayNumber((int) num[1]);*//* - break; - case 3: - *//*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber((int) num[0]); - sevenSegmentDisplay3.displayNumber((int) num[1]); - sevenSegmentDisplay4.displayNumber((int) num[2]);*//* - break; - case 4: - *//*sevenSegmentDisplay1.displayNumber((int) num[0]); - sevenSegmentDisplay2.displayNumber((int) num[1]); - sevenSegmentDisplay3.displayNumber((int) num[2]); - sevenSegmentDisplay4.displayNumber((int) num[3]);*//* - break; - }*/ + for (int i = 0; i < value.length(); i++) { + CharMap charMap = CharMap.valueOf(String.valueOf(value.charAt(i))); + setValue(i, charMap); + } } - /*//tag::method[] - public String getValue() - //end::method[] - { - *//*int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; - return Integer.parseInt(String.valueOf(values));*//* - }*/ - - private void setValue(DigitalOutput digit, CharMap character) { - /*segmentA.off(); - segmentB.off(); - segmentC.off(); - segmentD.off(); - segmentE.off(); - segmentF.off(); - segmentG.off(); - - digit.on(100, 50); - for (int i = 0; i < 7; i++) { - if (character.getSegmentArray()[i] == 1) { - switch (i) { - case 0: - segmentA.on(100, 50); - break; - case 1: - segmentB.on(100, 50); - break; - case 2: - segmentC.on(100, 50); - break; - case 3: - segmentD.on(100, 50); - break; - case 4: - segmentE.on(100, 50); - break; - case 5: - segmentF.on(100, 50); - break; - case 6: - segmentG.on(100, 50); - break; - } - } - }*/ - - segmentA.low(); - segmentB.low(); - segmentC.low(); - segmentD.low(); - segmentE.low(); - segmentF.low(); - segmentG.low(); - - digit.high(); - for (int i = 0; i < 7; i++) { - if (character.getSegmentArray()[i] == 1) { - switch (i) { - case 0: - segmentA.high(); - break; - case 1: - segmentB.high(); - break; - case 2: - segmentC.high(); - break; - case 3: - segmentD.high(); - break; - case 4: - segmentE.high(); - break; - case 5: - segmentF.high(); - break; - case 6: - segmentG.high(); - break; - } - } - } + private void setValue(int digit, CharMap character) { + int segmentValue = character.getSegmentValue(); + i2c.writeRegister(digit, (byte) segmentValue); } } From ff7f3539389a7a553b2b2d3e60f1cf1e1d8d2ea9 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 3 Nov 2024 20:27:14 -0600 Subject: [PATCH 12/33] Commented out unused imports in 4-digit 7-segment display test class --- .../outputdevices/FourDigitSevenSegmentDisplayHelperTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java index 94b1de6b..165e1a5e 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -1,13 +1,13 @@ package com.opensourcewithslu.outputdevices; -import org.junit.jupiter.api.Test; +/*import org.junit.jupiter.api.Test; import org.slf4j.Logger; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; -import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; +import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper;*/ public class FourDigitSevenSegmentDisplayHelperTest { /*FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); From 24b07264a4aaa69d27364144ce8ca1a494beefe8 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 3 Nov 2024 23:04:37 -0600 Subject: [PATCH 13/33] Attempted to copy from https://github.com/Pi4J/pi4j-example-crowpi/blob/main/src/main/java/com/pi4j/crowpi/components/SevenSegmentComponent.java --- ...ourDigitSevenSegmentDisplayController.java | 16 +- components/src/main/resources/application.yml | 78 ++-- .../FourDigitSevenSegmentDisplayHelper.java | 366 +++++++++++++++--- 3 files changed, 374 insertions(+), 86 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 3c1874a5..d83bac75 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -1,26 +1,38 @@ package com.opensourcewithslu.components.controllers; import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; -import com.opensourcewithslu.utilities.MultiPinConfiguration; 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.Inject; import jakarta.inject.Named; +import com.pi4j.crowpi.components.internal.HT16K33; //tag::ex[] @Controller("/4digit7segment") public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; + @Inject public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") I2CConfig fourdigsevenseg, Context pi4jContext ) { this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, pi4jContext); } + @Get("/enable") + public void enable() { + fourDigitSevenSegmentDisplayHelper.enableDisplay(true); + } + @Get("/displayNumber/{value}") public void displayValue(String value) { - fourDigitSevenSegmentDisplayHelper.displayValue(value); + fourDigitSevenSegmentDisplayHelper.print(value); + } + + @Get("/disable") + public void disable() { + fourDigitSevenSegmentDisplayHelper.setEnabled(false); } } //end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 41aeaaa4..cb6b9db6 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -60,45 +60,45 @@ pi4j: 4digit7segment: name: 4 Digit 7 Segment Display bus: 1 - device: 0x3F - segments: - segmentA: - pin: 18 - state: "LOW" - segmentB: - pin: 22 - state: "LOW" - segmentC: - pin: 24 - state: "LOW" - segmentD: - pin: 26 - state: "LOW" - segmentE: - pin: 28 - state: "LOW" - segmentF: - pin: 32 - state: "LOW" - segmentG: - pin: 36 - state: "LOW" - decimal-point: - pin: 38 - state: "LOW" - digits: - digit0: - pin: 11 - state: "LOW" - digit1: - pin: 13 - state: "LOW" - digit2: - pin: 15 - state: "LOW" - digit3: - pin: 17 - state: "LOW" + device: 0x70 + # segments: + # segmentA: + # pin: 18 + # state: "LOW" + # segmentB: + # pin: 22 + # state: "LOW" + # segmentC: + # pin: 24 + # state: "LOW" + # segmentD: + # pin: 26 + # state: "LOW" + # segmentE: + # pin: 28 + # state: "LOW" + # segmentF: + # pin: 32 + # state: "LOW" + # segmentG: + # pin: 36 + # state: "LOW" + # decimal-point: + # pin: 38 + # state: "LOW" + # digits: + # digit0: + # pin: 11 + # state: "LOW" + # digit1: + # pin: 13 + # state: "LOW" + # digit2: + # pin: 15 + # state: "LOW" + # digit3: + # pin: 17 + # state: "LOW" # end::i2c[] # tag::digitalInput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 02ba1b88..f5248795 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -6,69 +6,345 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class FourDigitSevenSegmentDisplayHelper { +import com.pi4j.crowpi.components.internal.HT16K33; + + +import java.lang.reflect.Method; +import java.time.LocalTime; +import java.util.Map; + +public class FourDigitSevenSegmentDisplayHelper extends HT16K33 { private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + /** + * Internal buffer index for the colon of the seven-segment display + */ + private static final int COLON_INDEX = 4; + /** + * Internal buffer indices for the digits of the seven-segment display + */ + private static final int[] DIGIT_INDICES = new int[]{0, 2, 6, 8}; + + /** + * Mapping of characters to their respective byte representation. + * Each byte is a bitset where each bit specifies if a specific segment should be enabled (1) or disabled (0). + */ + protected static final Map CHAR_BITSETS = Map.ofEntries( + Map.entry(' ', fromSegments()), + Map.entry('-', fromSegments(Segment.CENTER)), + Map.entry('0', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('1', fromSegments(Segment.RIGHT_TOP, Segment.RIGHT_BOTTOM)), + Map.entry('2', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.BOTTOM)), + Map.entry('3', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('4', fromSegments(Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM)), + Map.entry('5', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('6', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('7', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.RIGHT_BOTTOM)), + Map.entry('8', fromSegments(Segment.LEFT_TOP, Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('9', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('A', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM)), + Map.entry('B', fromSegments(Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('C', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.LEFT_BOTTOM, Segment.BOTTOM)), + Map.entry('D', fromSegments(Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), + Map.entry('E', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.BOTTOM)), + Map.entry('F', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM)) + ); + private final I2C i2c; - private enum CharMap { - ZERO(0x3F), - ONE(0x06), - TWO(0x5B), - THREE(0x4F), - FOUR(0x66), - FIVE(0x6D), - SIX(0x7D), - SEVEN(0x07), - EIGHT(0x7F), - NINE(0x6F), - HYPHEN(0x40); - - private final int segmentValue; - - CharMap(int segmentValue) { - this.segmentValue = segmentValue; + public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) { + super(pi4jContext.create(i2cConfig)); + this.i2c = pi4jContext.create(i2cConfig); + + } + + /** + * Prints the given integer value to the seven-segment display. + * This works by converting the integer into a string and passing it to {@link #print(String)}. + * Please note that due to the limitations of the display only the first four digits will be displayed. + * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the number. + * + * @param i Integer to display + */ + public void print(int i) { + print(String.valueOf(i)); + } + + /** + * Prints the given double value to the seven-segment display, automatically setting the decimal point if applicable. + * This works by converting the double into a string and passing it to {@link #print(String)}. + * Please note that due to the limitations of the display only the first four digits will be displayed. + * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the number. + * + * @param d Double to display + */ + public void print(double d) { + print(String.valueOf(d)); + } + + /** + * Prints the hours and minutes of the given {@link LocalTime} instance to the seven-segment display. + * The time will be displayed in 24 hours format as HH:MM with the colon being active for every odd second. + * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the time. + * + * @param time Time to display + */ + public void print(LocalTime time) { + clear(); + setColon(time.getSecond() % 2 == 1); + setDigit(0, time.getHour() / 10); + setDigit(1, time.getHour() % 10); + setDigit(2, time.getMinute() / 10); + setDigit(3, time.getMinute() % 10); + refresh(); + } + + /** + * Prints the first four letters (or less if applicable) of the given string to the display. Additional letters are ignored. + * Adding a dot after a digit or putting a colon after two digits causes the respective symbols to be displayed too. + * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the text. + *

+ * Example: "1.2:34." will print the string "1234" to the display, with decimal point #1 and #4 as well as the colon symbol being set + * + * @param s String which should be printed + */ + public void print(String s) { + clear(); + + int idx = 0, pos = 0; + while (idx < s.length() && pos < 4) { + // Set digit to character at current index and advance + setDigit(pos, s.charAt(idx++)); + + // Exit early if we reached the end + if (idx >= s.length()) { + break; + } + + // Set decimal point if next character is dot + if (s.charAt(idx) == '.') { + setDecimalPoint(pos, true); + idx++; + } + + // Exit early if we reached the end + if (idx >= s.length()) { + break; + } + + // Advance to next digit + pos++; + + // Set colon if next character is a colon after two digits + if (pos == 2 && s.charAt(idx) == ':') { + setColon(true); + idx++; + } } - public int getSegmentValue() { - return segmentValue; + refresh(); + } + + /** + * Enables or disables the colon symbol of the seven-segment display. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param enabled Specify if colon should be enabled or disabled. + */ + public void setColon(boolean enabled) { + if (enabled) { + buffer[COLON_INDEX] = 0x02; + } else { + buffer[COLON_INDEX] = 0x00; } } - public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) { - this.i2c = pi4jContext.create(i2cConfig); + /** + * Enables or disables the decimal point at the given digit position. + * Please note that the decimal point is just an additional belonging to the same digit. + * This means that overriding the specific digit (e.g. using {@link #setRawDigit(int, byte)} will reset the decimal point. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param position Desired position of digit from 0-3. + * @param enabled Specify if decimal point should be enabled or disabled. + */ + public void setDecimalPoint(int position, boolean enabled) { + // Get current value of given digit and set the decimal point accordingly + byte value = getRawDigit(position); + if (enabled) { + value |= Segment.DECIMAL_POINT.getValue(); + } else { + value &= ~Segment.DECIMAL_POINT.getValue(); + } + + // Write updated value into digit + setRawDigit(position, value); } - public void displayValue(String value) { - if (value.length() > 4) { - log.error("Number is too long"); - return; + /** + * Sets the digit at the specified position to the given integer. + * The integer must not contain more than one number, in other words it has to be in the range 0-9. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param position Desired position of digit from 0-3. + * @param i Single digit number which should be displayed. + */ + public void setDigit(int position, int i) { + // Ensure integer consists of a single number in decimal representation + if (i < 0 || i > 9) { + throw new IllegalArgumentException("Digit must be an integer in the range 0-9"); } - if (value.length() == 0) { - log.error("Number is too short"); - return; + + // Calculate ASCII character by adding number to ASCII value of '0' + // This works as the ASCII table contains the numbers 0-9 in ascending order + setDigit(position, (char) ('0' + i)); + } + + /** + * Sets the digit at the specified position to the given character. + * The character must be representable by the seven-segment display, so only a subset of chars is supported. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param position Desired position of digit from 0-3. + * @param c Character which should be displayed. + */ + public void setDigit(int position, char c) { + // Lookup byte value for given character + final var value = CHAR_BITSETS.get(Character.toUpperCase(c)); + if (value == null) { + throw new IllegalArgumentException("Character is not supported by seven-segment display"); } - try { - int integerNumber = Integer.parseInt(value); - if (integerNumber < 0) { - log.error("Display value must be positive"); - return; - } - } catch (NumberFormatException e) { - log.error("Display value must be an integer"); - return; + + setRawDigit(position, value); + } + + /** + * Sets the digit at the specified position to match the given segments. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param position Desired position of digit from 0-3. + * @param segments Segments which should be displayed. + */ + public void setDigit(int position, Segment... segments) { + setRawDigit(position, fromSegments(segments)); + } + + /** + * Sets the raw digit at the specified position. This method will take a byte value which gets processed by the underlying chip. + * The byte represents a bitset where each bit belongs to a specific segment and decides if its enabled (1) or disabled (0). + * Valid values can be crafted using the {@link #fromSegments(Segment...)} method. + * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. + * + * @param position Desired position of digit from 0-3. + * @param value Raw byte value to be displayed. + */ + protected void setRawDigit(int position, byte value) { + buffer[resolveDigitIndex(position)] = value; + } + + /** + * Gets the raw digit at the specified position. This method will return the internal byte value of the underlying chip. + * The byte represents a bitset where each bit belongs to a specific segment and decides if its enabled (1) or disabled (0). + * + * @param position Desired position of digit from 0-3. + * @return Raw byte value at specified position. + */ + protected byte getRawDigit(int position) { + return buffer[resolveDigitIndex(position)]; + } + + /** + * Helper method for converting the human-readable position of a digit (e.g. second digit) to the actual buffer index. + * This will throw an {@link IndexOutOfBoundsException} when the given position is outside of the known indices. + * + * @param position Human-readable position of digit starting at zero + * @return Actual index of digit in the internal buffer + */ + private int resolveDigitIndex(int position) { + // Ensure position is within bounds + final var maxPosition = DIGIT_INDICES.length - 1; + if (position < 0 || position > maxPosition) { + throw new IndexOutOfBoundsException("Digit position is outside of range 0-" + maxPosition); } - log.info("Displaying number: {}", value); + // Lookup actual index based on position + return DIGIT_INDICES[position]; + } + + /** + * Returns the created I2C instance for the seven-segment display + * + * @return I2C instance + */ + protected I2C getI2C() { + return this.i2c; + } - for (int i = 0; i < value.length(); i++) { - CharMap charMap = CharMap.valueOf(String.valueOf(value.charAt(i))); - setValue(i, charMap); + /** + * Helper method for creating a raw digit value (byte) from 0-n segments. + * This can be used together with the {@link Segment} enumeration to create and display your own digits. + * All segments passed to this method will be flagged as active and enabled when passed to {@link #setRawDigit(int, byte)} + * + * @param segments Segments which should be enabled to together + * @return Raw digit value as byte + */ + protected static byte fromSegments(Segment... segments) { + byte result = 0; + for (Segment segment : segments) { + result |= segment.getValue(); } + return result; } - private void setValue(int digit, CharMap character) { - int segmentValue = character.getSegmentValue(); - i2c.writeRegister(digit, (byte) segmentValue); + /** + * Builds a new I2C instance for the seven-segment display + * + * @param pi4j Pi4J context + * @param bus Bus address + * @param device Device address + * @return I2C instance + */ + private static I2CConfig buildI2CConfig(Context pi4j, int bus, int device) { + return I2C.newConfigBuilder(pi4j) + .id("I2C-" + device + "@" + bus) + .name("Segment Display") + .bus(bus) + .device(device) + .build(); + } + + /** + * Mapping of segments to their respective bit according to the HT16K33. + * The chip uses the gfedcba encoding as can be found on Wikipedia: https://en.wikipedia.org/wiki/Seven-segment_display#Hexadecimal + */ + public enum Segment { + TOP(0), + RIGHT_TOP(1), + RIGHT_BOTTOM(2), + BOTTOM(3), + LEFT_BOTTOM(4), + LEFT_TOP(5), + CENTER(6), + DECIMAL_POINT(7); + + private final byte value; + + Segment(int bit) { + this.value = (byte) (1 << bit); + } + + byte getValue() { + return this.value; + } + } + + public void enableDisplay(boolean enabled) { + try { + Method setEnabledMethod = HT16K33.class.getDeclaredMethod("setEnabled", boolean.class); + setEnabledMethod.setAccessible(true); + setEnabledMethod.invoke(this, enabled); + } catch (Exception e) { + throw new RuntimeException("Failed to call setEnabled method", e); + } } } From c53d8e0484dfacfb42b0621658751e745890dbc0 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 4 Nov 2024 15:55:33 -0600 Subject: [PATCH 14/33] Copied parent class methods directly into the helper class --- ...ourDigitSevenSegmentDisplayController.java | 5 +- .../FourDigitSevenSegmentDisplayHelper.java | 127 ++++++++++++++++-- 2 files changed, 117 insertions(+), 15 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index d83bac75..e18059be 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -7,14 +7,13 @@ import io.micronaut.http.annotation.Get; import jakarta.inject.Inject; import jakarta.inject.Named; -import com.pi4j.crowpi.components.internal.HT16K33; //tag::ex[] @Controller("/4digit7segment") public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - @Inject + // @Inject public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") I2CConfig fourdigsevenseg, Context pi4jContext ) { this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, pi4jContext); @@ -22,7 +21,7 @@ public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") I2CConfig @Get("/enable") public void enable() { - fourDigitSevenSegmentDisplayHelper.enableDisplay(true); + fourDigitSevenSegmentDisplayHelper.setEnabled(true); } @Get("/displayNumber/{value}") diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index f5248795..6d89a6c1 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -6,14 +6,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.pi4j.crowpi.components.internal.HT16K33; - - -import java.lang.reflect.Method; import java.time.LocalTime; import java.util.Map; -public class FourDigitSevenSegmentDisplayHelper extends HT16K33 { +import java.util.Arrays; + +public class FourDigitSevenSegmentDisplayHelper { private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); /** @@ -53,7 +51,6 @@ public class FourDigitSevenSegmentDisplayHelper extends HT16K33 { private final I2C i2c; public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) { - super(pi4jContext.create(i2cConfig)); this.i2c = pi4jContext.create(i2cConfig); } @@ -338,13 +335,119 @@ byte getValue() { } } - public void enableDisplay(boolean enabled) { + /* Internal size of the buffer which gets flushed to the display. + */ + private static final int BUFFER_SIZE = 16; + + // HT16K33: Display Data Command + private static final int CMD_DISPLAY_DATA = 0b0000; + + // HT16K33: Dimming Set Command + private static final int CMD_DIMMING_SET = 0b1110; + + // HT16K33: System Setup Command + private static final int CMD_SYSTEM_SETUP = 0b0010; + private static final int SET_OSCILLATOR_ON = 0b0001; + private static final int SET_OSCILLATOR_OFF = 0b0000; + + // HT16K33: Display Setup Command + private static final int CMD_DISPLAY_SETUP = 0b1000; + private static final int SET_DISPLAY_ON = 0b0001; + private static final int SET_DISPLAY_OFF = 0b0000; + + /** + * Internal buffer where all digits get stored before being flushed to the display. + */ + protected final byte[] buffer = new byte[BUFFER_SIZE]; + + /** + * Clears the internal buffer without refreshing the display. + * This means that the current contents of the displays are still being shown until {@link #refresh()} is called. + */ + public void clear() { + Arrays.fill(buffer, (byte) 0); + } + + /** + * Flushes the internal buffer to the chip, causing it to be displayed. + * The contents of the buffer will be preserved by this command. + */ + public void refresh() { + execute(CMD_DISPLAY_DATA, 0); + i2c.writeRegister(0, buffer, BUFFER_SIZE); + } + + /** + * Specifies if the seven-segment display should be enabled or disabled. + * This will activate/deactivate the internal system oscillator and turn the LEDs on/off. + * + * @param enabled Display state + */ + public void setEnabled(boolean enabled) { + if (enabled) { + execute(CMD_SYSTEM_SETUP, SET_OSCILLATOR_ON); + execute(CMD_DISPLAY_SETUP, SET_DISPLAY_ON); + } else { + execute(CMD_SYSTEM_SETUP, SET_OSCILLATOR_OFF); + execute(CMD_DISPLAY_SETUP, SET_DISPLAY_OFF); + } + } + + /** + * Changes the desired blink rate for the seven-segment display. + * This method expects an integer value within the range 0-3, with 0 being equal to no blinking and 3 being the fastest choice. + * The whole display is affected by this command which gets immediately applied. + * + * @param rate Desired blink rate from 0-3 + */ + public void setBlinkRate(int rate) { + if (rate < 0 || rate > 3) + throw new IllegalArgumentException("Blink rate must be an integer in the range 0-3"); + execute(CMD_DISPLAY_SETUP, SET_DISPLAY_ON | (rate << 1)); + } + + /** + * Changes the desired brightness for the seven-segment display. + * This method expects an integer value within the range 0-15, with 0 being the dimmest and 15 the brightest possible value. + * The whole display is affected by this command which gets immediately applied. + * + * @param brightness Desired brightness from 0-15 + */ + public void setBrightness(int brightness) { + if (brightness < 0 || brightness > 15) + throw new IllegalArgumentException("Brightness must be an integer in the range 0-15"); + execute(CMD_DIMMING_SET, brightness); + } + + /** + * Helper method for sending a command to the HT16K33 chip. Communication with the chip happens by sending a NULL value to a given + * address. The address itself consists of a byte split in two, with the upper half/nibble being the command and the lower half/nibble + * being the desired data / setting for the command. + * + * @param command Command to be executed + * @param setting Optional setting / data for the selected command + */ + private void execute(int command, int setting) { + if (command < 0 || command > 0xF) + throw new IllegalArgumentException("Command must be nibble"); + if (setting < 0 || setting > 0xF) + throw new IllegalArgumentException("Setting must be nibble"); + + final var address = (command << 4) | setting; + i2c.writeRegister(address, 0); + } + + /** + * Utility function to sleep for the specified amount of milliseconds. + * An {@link InterruptedException} will be catched and ignored while setting the interrupt flag again. + * + * @param milliseconds Time in milliseconds to sleep + */ + protected void sleep(long milliseconds) { try { - Method setEnabledMethod = HT16K33.class.getDeclaredMethod("setEnabled", boolean.class); - setEnabledMethod.setAccessible(true); - setEnabledMethod.invoke(this, enabled); - } catch (Exception e) { - throw new RuntimeException("Failed to call setEnabled method", e); + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } } } From 768708419d9d1ad9cadd20082dbe233d47c7d195 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Thu, 7 Nov 2024 19:13:59 -0600 Subject: [PATCH 15/33] initial --- components/src/main/resources/application.yml | 88 +++++++++++++++++-- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index d28d75bc..f4fdcf99 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -161,6 +161,80 @@ pi4j: shutdown: LOW initial: LOW provider: pigpio-digital-output + 4digit7segment: + segments: + segment-a: + name: Segment A + address: 18 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-b: + name: Segment B + address: 22 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-c: + name: Segment C + address: 24 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-d: + name: Segment D + address: 26 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-e: + name: Segment E + address: 28 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-f: + name: Segment F + address: 32 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-g: + name: Segment G + address: 36 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-dot: + name: Segment DOT + address: 38 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-0: + name: Digit 0 + address: 11 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-1: + name: Digit 1 + address: 13 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-2: + name: Digit 2 + address: 15 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-3: + name: Digit 3 + address: 17 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output # end::digitalOutput[] # tag::multiInput[] @@ -209,11 +283,11 @@ pi4j: # end::multiPWM[] # tag::multiOutput[] - multi-digital-output: - 4digit7segment: - name: 4 Digit 7 Segment Display - addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - provider: pigpio-digital-output + # multi-digital-output: + # 4digit7segment: + # name: 4 Digit 7 Segment Display + # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + # provider: pigpio-digital-output # end::multiOutput[] From 9023b6263998beda71e5b66f828f9678f8d5ba82 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 12:39:16 -0600 Subject: [PATCH 16/33] alterations too start using custom code --- ...ourDigitSevenSegmentDisplayController.java | 26 +- components/src/main/resources/application.yml | 4 +- .../FourDigitSevenSegmentDisplayHelper.java | 434 +----------------- 3 files changed, 19 insertions(+), 445 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index e18059be..e46a506f 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -9,29 +9,29 @@ import jakarta.inject.Named; //tag::ex[] -@Controller("/4digit7segment") +@Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; // @Inject - public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") I2CConfig fourdigsevenseg, Context pi4jContext + 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("/displayNumber/{value}") - public void displayValue(String value) { - fourDigitSevenSegmentDisplayHelper.print(value); - } - - @Get("/disable") - public void disable() { - fourDigitSevenSegmentDisplayHelper.setEnabled(false); +// fourDigitSevenSegmentDisplayHelper.setEnabled(true); } +// +// @Get("/displayNumber/{value}") +// public void displayValue(String value) { +// fourDigitSevenSegmentDisplayHelper.print(value); +// } +// +// @Get("/disable") +// public void disable() { +// fourDigitSevenSegmentDisplayHelper.setEnabled(false); +// } } //end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index cb6b9db6..03b50f29 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -57,10 +57,10 @@ pi4j: name: lcd # <2> bus: 1 # <3> device: 0x27 # <4> - 4digit7segment: + four-digit-seven-segment: name: 4 Digit 7 Segment Display bus: 1 - device: 0x70 + device: 0x27 # segments: # segmentA: # pin: 18 diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 6d89a6c1..8de9f15c 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -14,440 +14,14 @@ public class FourDigitSevenSegmentDisplayHelper { private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); - /** - * Internal buffer index for the colon of the seven-segment display - */ - private static final int COLON_INDEX = 4; - /** - * Internal buffer indices for the digits of the seven-segment display - */ - private static final int[] DIGIT_INDICES = new int[]{0, 2, 6, 8}; - - /** - * Mapping of characters to their respective byte representation. - * Each byte is a bitset where each bit specifies if a specific segment should be enabled (1) or disabled (0). - */ - protected static final Map CHAR_BITSETS = Map.ofEntries( - Map.entry(' ', fromSegments()), - Map.entry('-', fromSegments(Segment.CENTER)), - Map.entry('0', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('1', fromSegments(Segment.RIGHT_TOP, Segment.RIGHT_BOTTOM)), - Map.entry('2', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.BOTTOM)), - Map.entry('3', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('4', fromSegments(Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM)), - Map.entry('5', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('6', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('7', fromSegments(Segment.TOP, Segment.RIGHT_TOP, Segment.RIGHT_BOTTOM)), - Map.entry('8', fromSegments(Segment.LEFT_TOP, Segment.TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('9', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('A', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM)), - Map.entry('B', fromSegments(Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('C', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.LEFT_BOTTOM, Segment.BOTTOM)), - Map.entry('D', fromSegments(Segment.RIGHT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.RIGHT_BOTTOM, Segment.BOTTOM)), - Map.entry('E', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM, Segment.BOTTOM)), - Map.entry('F', fromSegments(Segment.TOP, Segment.LEFT_TOP, Segment.CENTER, Segment.LEFT_BOTTOM)) - ); - private final I2C i2c; - public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) { + //tag::const[] + public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) + //end::const[] + { this.i2c = pi4jContext.create(i2cConfig); - - } - - /** - * Prints the given integer value to the seven-segment display. - * This works by converting the integer into a string and passing it to {@link #print(String)}. - * Please note that due to the limitations of the display only the first four digits will be displayed. - * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the number. - * - * @param i Integer to display - */ - public void print(int i) { - print(String.valueOf(i)); - } - - /** - * Prints the given double value to the seven-segment display, automatically setting the decimal point if applicable. - * This works by converting the double into a string and passing it to {@link #print(String)}. - * Please note that due to the limitations of the display only the first four digits will be displayed. - * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the number. - * - * @param d Double to display - */ - public void print(double d) { - print(String.valueOf(d)); - } - - /** - * Prints the hours and minutes of the given {@link LocalTime} instance to the seven-segment display. - * The time will be displayed in 24 hours format as HH:MM with the colon being active for every odd second. - * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the time. - * - * @param time Time to display - */ - public void print(LocalTime time) { - clear(); - setColon(time.getSecond() % 2 == 1); - setDigit(0, time.getHour() / 10); - setDigit(1, time.getHour() % 10); - setDigit(2, time.getMinute() / 10); - setDigit(3, time.getMinute() % 10); - refresh(); - } - - /** - * Prints the first four letters (or less if applicable) of the given string to the display. Additional letters are ignored. - * Adding a dot after a digit or putting a colon after two digits causes the respective symbols to be displayed too. - * This will clear the buffer and automatically call {@link #refresh()} afterwards to immediately display the text. - *

- * Example: "1.2:34." will print the string "1234" to the display, with decimal point #1 and #4 as well as the colon symbol being set - * - * @param s String which should be printed - */ - public void print(String s) { - clear(); - - int idx = 0, pos = 0; - while (idx < s.length() && pos < 4) { - // Set digit to character at current index and advance - setDigit(pos, s.charAt(idx++)); - - // Exit early if we reached the end - if (idx >= s.length()) { - break; - } - - // Set decimal point if next character is dot - if (s.charAt(idx) == '.') { - setDecimalPoint(pos, true); - idx++; - } - - // Exit early if we reached the end - if (idx >= s.length()) { - break; - } - - // Advance to next digit - pos++; - - // Set colon if next character is a colon after two digits - if (pos == 2 && s.charAt(idx) == ':') { - setColon(true); - idx++; - } - } - - refresh(); - } - - /** - * Enables or disables the colon symbol of the seven-segment display. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param enabled Specify if colon should be enabled or disabled. - */ - public void setColon(boolean enabled) { - if (enabled) { - buffer[COLON_INDEX] = 0x02; - } else { - buffer[COLON_INDEX] = 0x00; - } - } - - /** - * Enables or disables the decimal point at the given digit position. - * Please note that the decimal point is just an additional belonging to the same digit. - * This means that overriding the specific digit (e.g. using {@link #setRawDigit(int, byte)} will reset the decimal point. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param position Desired position of digit from 0-3. - * @param enabled Specify if decimal point should be enabled or disabled. - */ - public void setDecimalPoint(int position, boolean enabled) { - // Get current value of given digit and set the decimal point accordingly - byte value = getRawDigit(position); - if (enabled) { - value |= Segment.DECIMAL_POINT.getValue(); - } else { - value &= ~Segment.DECIMAL_POINT.getValue(); - } - - // Write updated value into digit - setRawDigit(position, value); - } - - /** - * Sets the digit at the specified position to the given integer. - * The integer must not contain more than one number, in other words it has to be in the range 0-9. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param position Desired position of digit from 0-3. - * @param i Single digit number which should be displayed. - */ - public void setDigit(int position, int i) { - // Ensure integer consists of a single number in decimal representation - if (i < 0 || i > 9) { - throw new IllegalArgumentException("Digit must be an integer in the range 0-9"); - } - - // Calculate ASCII character by adding number to ASCII value of '0' - // This works as the ASCII table contains the numbers 0-9 in ascending order - setDigit(position, (char) ('0' + i)); - } - - /** - * Sets the digit at the specified position to the given character. - * The character must be representable by the seven-segment display, so only a subset of chars is supported. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param position Desired position of digit from 0-3. - * @param c Character which should be displayed. - */ - public void setDigit(int position, char c) { - // Lookup byte value for given character - final var value = CHAR_BITSETS.get(Character.toUpperCase(c)); - if (value == null) { - throw new IllegalArgumentException("Character is not supported by seven-segment display"); - } - - setRawDigit(position, value); - } - - /** - * Sets the digit at the specified position to match the given segments. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param position Desired position of digit from 0-3. - * @param segments Segments which should be displayed. - */ - public void setDigit(int position, Segment... segments) { - setRawDigit(position, fromSegments(segments)); - } - - /** - * Sets the raw digit at the specified position. This method will take a byte value which gets processed by the underlying chip. - * The byte represents a bitset where each bit belongs to a specific segment and decides if its enabled (1) or disabled (0). - * Valid values can be crafted using the {@link #fromSegments(Segment...)} method. - * This will only affect the internal buffer and does not get displayed until {@link #refresh()} gets called. - * - * @param position Desired position of digit from 0-3. - * @param value Raw byte value to be displayed. - */ - protected void setRawDigit(int position, byte value) { - buffer[resolveDigitIndex(position)] = value; - } - - /** - * Gets the raw digit at the specified position. This method will return the internal byte value of the underlying chip. - * The byte represents a bitset where each bit belongs to a specific segment and decides if its enabled (1) or disabled (0). - * - * @param position Desired position of digit from 0-3. - * @return Raw byte value at specified position. - */ - protected byte getRawDigit(int position) { - return buffer[resolveDigitIndex(position)]; - } - - /** - * Helper method for converting the human-readable position of a digit (e.g. second digit) to the actual buffer index. - * This will throw an {@link IndexOutOfBoundsException} when the given position is outside of the known indices. - * - * @param position Human-readable position of digit starting at zero - * @return Actual index of digit in the internal buffer - */ - private int resolveDigitIndex(int position) { - // Ensure position is within bounds - final var maxPosition = DIGIT_INDICES.length - 1; - if (position < 0 || position > maxPosition) { - throw new IndexOutOfBoundsException("Digit position is outside of range 0-" + maxPosition); - } - - // Lookup actual index based on position - return DIGIT_INDICES[position]; - } - - /** - * Returns the created I2C instance for the seven-segment display - * - * @return I2C instance - */ - protected I2C getI2C() { - return this.i2c; - } - - /** - * Helper method for creating a raw digit value (byte) from 0-n segments. - * This can be used together with the {@link Segment} enumeration to create and display your own digits. - * All segments passed to this method will be flagged as active and enabled when passed to {@link #setRawDigit(int, byte)} - * - * @param segments Segments which should be enabled to together - * @return Raw digit value as byte - */ - protected static byte fromSegments(Segment... segments) { - byte result = 0; - for (Segment segment : segments) { - result |= segment.getValue(); - } - return result; - } - - /** - * Builds a new I2C instance for the seven-segment display - * - * @param pi4j Pi4J context - * @param bus Bus address - * @param device Device address - * @return I2C instance - */ - private static I2CConfig buildI2CConfig(Context pi4j, int bus, int device) { - return I2C.newConfigBuilder(pi4j) - .id("I2C-" + device + "@" + bus) - .name("Segment Display") - .bus(bus) - .device(device) - .build(); - } - - /** - * Mapping of segments to their respective bit according to the HT16K33. - * The chip uses the gfedcba encoding as can be found on Wikipedia: https://en.wikipedia.org/wiki/Seven-segment_display#Hexadecimal - */ - public enum Segment { - TOP(0), - RIGHT_TOP(1), - RIGHT_BOTTOM(2), - BOTTOM(3), - LEFT_BOTTOM(4), - LEFT_TOP(5), - CENTER(6), - DECIMAL_POINT(7); - - private final byte value; - - Segment(int bit) { - this.value = (byte) (1 << bit); - } - - byte getValue() { - return this.value; - } - } - - /* Internal size of the buffer which gets flushed to the display. - */ - private static final int BUFFER_SIZE = 16; - - // HT16K33: Display Data Command - private static final int CMD_DISPLAY_DATA = 0b0000; - - // HT16K33: Dimming Set Command - private static final int CMD_DIMMING_SET = 0b1110; - - // HT16K33: System Setup Command - private static final int CMD_SYSTEM_SETUP = 0b0010; - private static final int SET_OSCILLATOR_ON = 0b0001; - private static final int SET_OSCILLATOR_OFF = 0b0000; - - // HT16K33: Display Setup Command - private static final int CMD_DISPLAY_SETUP = 0b1000; - private static final int SET_DISPLAY_ON = 0b0001; - private static final int SET_DISPLAY_OFF = 0b0000; - - /** - * Internal buffer where all digits get stored before being flushed to the display. - */ - protected final byte[] buffer = new byte[BUFFER_SIZE]; - - /** - * Clears the internal buffer without refreshing the display. - * This means that the current contents of the displays are still being shown until {@link #refresh()} is called. - */ - public void clear() { - Arrays.fill(buffer, (byte) 0); - } - - /** - * Flushes the internal buffer to the chip, causing it to be displayed. - * The contents of the buffer will be preserved by this command. - */ - public void refresh() { - execute(CMD_DISPLAY_DATA, 0); - i2c.writeRegister(0, buffer, BUFFER_SIZE); } - /** - * Specifies if the seven-segment display should be enabled or disabled. - * This will activate/deactivate the internal system oscillator and turn the LEDs on/off. - * - * @param enabled Display state - */ - public void setEnabled(boolean enabled) { - if (enabled) { - execute(CMD_SYSTEM_SETUP, SET_OSCILLATOR_ON); - execute(CMD_DISPLAY_SETUP, SET_DISPLAY_ON); - } else { - execute(CMD_SYSTEM_SETUP, SET_OSCILLATOR_OFF); - execute(CMD_DISPLAY_SETUP, SET_DISPLAY_OFF); - } - } - /** - * Changes the desired blink rate for the seven-segment display. - * This method expects an integer value within the range 0-3, with 0 being equal to no blinking and 3 being the fastest choice. - * The whole display is affected by this command which gets immediately applied. - * - * @param rate Desired blink rate from 0-3 - */ - public void setBlinkRate(int rate) { - if (rate < 0 || rate > 3) - throw new IllegalArgumentException("Blink rate must be an integer in the range 0-3"); - execute(CMD_DISPLAY_SETUP, SET_DISPLAY_ON | (rate << 1)); - } - - /** - * Changes the desired brightness for the seven-segment display. - * This method expects an integer value within the range 0-15, with 0 being the dimmest and 15 the brightest possible value. - * The whole display is affected by this command which gets immediately applied. - * - * @param brightness Desired brightness from 0-15 - */ - public void setBrightness(int brightness) { - if (brightness < 0 || brightness > 15) - throw new IllegalArgumentException("Brightness must be an integer in the range 0-15"); - execute(CMD_DIMMING_SET, brightness); - } - - /** - * Helper method for sending a command to the HT16K33 chip. Communication with the chip happens by sending a NULL value to a given - * address. The address itself consists of a byte split in two, with the upper half/nibble being the command and the lower half/nibble - * being the desired data / setting for the command. - * - * @param command Command to be executed - * @param setting Optional setting / data for the selected command - */ - private void execute(int command, int setting) { - if (command < 0 || command > 0xF) - throw new IllegalArgumentException("Command must be nibble"); - if (setting < 0 || setting > 0xF) - throw new IllegalArgumentException("Setting must be nibble"); - - final var address = (command << 4) | setting; - i2c.writeRegister(address, 0); - } - - /** - * Utility function to sleep for the specified amount of milliseconds. - * An {@link InterruptedException} will be catched and ignored while setting the interrupt flag again. - * - * @param milliseconds Time in milliseconds to sleep - */ - protected void sleep(long milliseconds) { - try { - Thread.sleep(milliseconds); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } } From a713809fe65571c9beb94a0dc6f22e6291e2cb55 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 14:57:37 -0600 Subject: [PATCH 17/33] changed names, fixed indentation error (which didn't actually fix anything) --- ...ourDigitSevenSegmentDisplayController.java | 4 +- components/src/main/resources/application.yml | 163 +++++++++--------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 38c79452..20db6915 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -7,11 +7,11 @@ import jakarta.inject.Named; //tag::ex[] -@Controller("/4digit7segment") +@Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - public FourDigitSevenSegmentDisplayController(@Named("4digit7segment") MultiPinConfiguration fourdigsevenseg + public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") MultiPinConfiguration fourdigsevenseg ) { this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); } diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index f4fdcf99..5caf3085 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -161,80 +161,80 @@ pi4j: shutdown: LOW initial: LOW provider: pigpio-digital-output - 4digit7segment: - segments: - segment-a: - name: Segment A - address: 18 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-b: - name: Segment B - address: 22 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-c: - name: Segment C - address: 24 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-d: - name: Segment D - address: 26 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-e: - name: Segment E - address: 28 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-f: - name: Segment F - address: 32 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-g: - name: Segment G - address: 36 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-dot: - name: Segment DOT - address: 38 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - digit-0: - name: Digit 0 - address: 11 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - digit-1: - name: Digit 1 - address: 13 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - digit-2: - name: Digit 2 - address: 15 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - digit-3: - name: Digit 3 - address: 17 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output + # 4digit7segment: + # segments: + # segment-a: + # name: Segment A + # address: 18 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-b: + # name: Segment B + # address: 22 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-c: + # name: Segment C + # address: 24 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-d: + # name: Segment D + # address: 26 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-e: + # name: Segment E + # address: 28 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-f: + # name: Segment F + # address: 32 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-g: + # name: Segment G + # address: 36 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # segment-dot: + # name: Segment DOT + # address: 38 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # digit-0: + # name: Digit 0 + # address: 11 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # digit-1: + # name: Digit 1 + # address: 13 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # digit-2: + # name: Digit 2 + # address: 15 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output + # digit-3: + # name: Digit 3 + # address: 17 + # shutdown: LOW + # initial: LOW + # provider: pigpio-digital-output # end::digitalOutput[] # tag::multiInput[] @@ -283,11 +283,12 @@ pi4j: # end::multiPWM[] # tag::multiOutput[] - # multi-digital-output: - # 4digit7segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - # provider: pigpio-digital-output + multi-digital-output: + four-digit-seven-segment: + name: 4 Digit 7 Segment Display + # addresses: 18, 16, 12, 11, 13, 15, 19 # 74HC595 pin 14, 74HC595 pin 12, 74HC595 pin 11, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + provider: pigpio-digital-output # end::multiOutput[] From b2f706811e987f934c3ccbd8c1175c56ac1d06bb Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 17:58:00 -0600 Subject: [PATCH 18/33] segments as separate digital outputs --- ...ourDigitSevenSegmentDisplayController.java | 54 +++++- components/src/main/resources/application.yml | 162 +++++++++--------- .../outputdevices/DigitHelper.java | 18 ++ .../FourDigitSevenSegmentDisplayHelper.java | 71 +++++--- .../outputdevices/SegmentHelper.java | 28 +++ ...ourDigitSevenSegmentDisplayHelperTest.java | 94 ---------- 6 files changed, 225 insertions(+), 202 deletions(-) create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java delete mode 100644 pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 20db6915..0ea4951b 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -1,7 +1,10 @@ package com.opensourcewithslu.components.controllers; +import com.opensourcewithslu.outputdevices.DigitHelper; import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; -import com.opensourcewithslu.utilities.MultiPinConfiguration; +//import com.opensourcewithslu.utilities.MultiPinConfiguration; +import com.opensourcewithslu.outputdevices.SegmentHelper; +import com.pi4j.io.gpio.digital.DigitalOutput; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import jakarta.inject.Named; @@ -9,16 +12,57 @@ //tag::ex[] @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { - private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; + // private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; + private final SegmentHelper segmentA; + private final SegmentHelper segmentB; + private final SegmentHelper segmentC; + private final SegmentHelper segmentD; + private final SegmentHelper segmentE; + private final SegmentHelper segmentF; + private final SegmentHelper segmentG; + private final SegmentHelper segmentDot; + private final DigitHelper digit0; + private final DigitHelper digit1; + private final DigitHelper digit2; + private final DigitHelper digit3; - public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") MultiPinConfiguration fourdigsevenseg + public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput segmentA, + @Named("segment-b") DigitalOutput segmentB, + @Named("segment-c") DigitalOutput segmentC, + @Named("segment-d") DigitalOutput segmentD, + @Named("segment-e") DigitalOutput segmentE, + @Named("segment-f") DigitalOutput segmentF, + @Named("segment-g") DigitalOutput segmentG, + @Named("segment-dot") DigitalOutput segmentDot, + @Named("digit-0") DigitalOutput digit0, + @Named("digit-1") DigitalOutput digit1, + @Named("digit-2") DigitalOutput digit2, + @Named("digit-3") DigitalOutput digit3 ) { - this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); +// this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(segmentA, segmentB, segmentC, segmentD, segmentE, segmentF, segmentG, segmentDot, digit0, digit1, digit2, digit3); +// this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); + this.segmentA = new SegmentHelper(segmentA); + this.segmentB = new SegmentHelper(segmentB); + this.segmentC = new SegmentHelper(segmentC); + this.segmentD = new SegmentHelper(segmentD); + this.segmentE = new SegmentHelper(segmentE); + this.segmentF = new SegmentHelper(segmentF); + this.segmentG = new SegmentHelper(segmentG); + this.segmentDot = new SegmentHelper(segmentDot); + this.digit0 = new DigitHelper(digit0); + this.digit1 = new DigitHelper(digit1); + this.digit2 = new DigitHelper(digit2); + this.digit3 = new DigitHelper(digit3); } - @Get("/displayNumber/{value}") + /*@Get("/displayNumber/{value}") public void displayValue(String value) { fourDigitSevenSegmentDisplayHelper.displayValue(value); + }*/ + + @Get("/test") + public void test() { + segmentA.segmentOn(); } } //end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 5caf3085..59119912 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -161,80 +161,78 @@ pi4j: shutdown: LOW initial: LOW provider: pigpio-digital-output - # 4digit7segment: - # segments: - # segment-a: - # name: Segment A - # address: 18 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-b: - # name: Segment B - # address: 22 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-c: - # name: Segment C - # address: 24 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-d: - # name: Segment D - # address: 26 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-e: - # name: Segment E - # address: 28 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-f: - # name: Segment F - # address: 32 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-g: - # name: Segment G - # address: 36 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # segment-dot: - # name: Segment DOT - # address: 38 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # digit-0: - # name: Digit 0 - # address: 11 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # digit-1: - # name: Digit 1 - # address: 13 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # digit-2: - # name: Digit 2 - # address: 15 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output - # digit-3: - # name: Digit 3 - # address: 17 - # shutdown: LOW - # initial: LOW - # provider: pigpio-digital-output + segment-a: + name: Segment A + address: 18 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-b: + name: Segment B + address: 22 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-c: + name: Segment C + address: 24 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-d: + name: Segment D + address: 26 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-e: + name: Segment E + address: 28 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-f: + name: Segment F + address: 32 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-g: + name: Segment G + address: 36 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + segment-dot: + name: Segment DOT + address: 38 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-0: + name: Digit 0 + address: 11 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-1: + name: Digit 1 + address: 13 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-2: + name: Digit 2 + address: 15 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output + digit-3: + name: Digit 3 + address: 17 + shutdown: LOW + initial: LOW + provider: pigpio-digital-output # end::digitalOutput[] # tag::multiInput[] @@ -283,12 +281,12 @@ pi4j: # end::multiPWM[] # tag::multiOutput[] - multi-digital-output: - four-digit-seven-segment: - name: 4 Digit 7 Segment Display - # addresses: 18, 16, 12, 11, 13, 15, 19 # 74HC595 pin 14, 74HC595 pin 12, 74HC595 pin 11, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - provider: pigpio-digital-output + # multi-digital-output: + # four-digit-seven-segment: + # name: 4 Digit 7 Segment Display + # addresses: 18, 16, 12, 11, 13, 15, 19 # 74HC595 pin 14, 74HC595 pin 12, 74HC595 pin 11, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 + # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW + # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH + # provider: pigpio-digital-output # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java new file mode 100644 index 00000000..3cbd6099 --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java @@ -0,0 +1,18 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DigitHelper { + private static Logger log = LoggerFactory.getLogger(DigitHelper.class); + + private final DigitalOutput digitOutput; + + //tag::const[] + public DigitHelper(DigitalOutput digitOutput) + //end::const[] + { + this.digitOutput = digitOutput; + } +} diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 3976233e..a404bc5f 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -4,11 +4,13 @@ import com.pi4j.io.pwm.Pwm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.opensourcewithslu.utilities.MultiPinConfiguration; +//import com.opensourcewithslu.utilities.MultiPinConfiguration; public class FourDigitSevenSegmentDisplayHelper { +/* private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); +*/ /* private int[][] charMap = { {1, 1, 1, 1, 1, 1, 0}, {0, 1, 1, 0, 0, 0, 0}, @@ -22,6 +24,8 @@ public class FourDigitSevenSegmentDisplayHelper { {1, 1, 1, 1, 0, 1, 1}, {0, 0, 0, 0, 0, 0, 1} };*/ + /* + private enum CharMap { ZERO(new int[]{1, 1, 1, 1, 1, 1, 0}), @@ -59,12 +63,13 @@ public int[] getSegmentArray() { private final DigitalOutput digit1; private final DigitalOutput digit2; private final DigitalOutput digit3; +*/ //tag::const[] - public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourDigitSevenSeg) + public FourDigitSevenSegmentDisplayHelper(/*DigitalOutput segmentA, DigitalOutput segmentB, DigitalOutput segmentC, DigitalOutput segmentD, DigitalOutput segmentE, DigitalOutput segmentF, DigitalOutput segmentG, DigitalOutput segmentDot, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3*/) //end::const[] { - DigitalOutput[] components = (DigitalOutput[]) fourDigitSevenSeg.getComponents(); + /*DigitalOutput[] components = (DigitalOutput[]) fourDigitSevenSeg.getComponents(); this.segmentA = components[0]; this.segmentB = components[1]; this.segmentC = components[2]; @@ -76,11 +81,23 @@ public FourDigitSevenSegmentDisplayHelper(MultiPinConfiguration fourDigitSevenSe this.digit0 = components[8]; this.digit1 = components[9]; this.digit2 = components[10]; - this.digit3 = components[11]; + this.digit3 = components[11];*/ +/* this.segmentA = segmentA; + this.segmentB = segmentB; + this.segmentC = segmentC; + this.segmentD = segmentD; + this.segmentE = segmentE; + this.segmentF = segmentF; + this.segmentG = segmentG; + this.DP = segmentDot; + this.digit0 = digit0; + this.digit1 = digit1; + this.digit2 = digit2; + this.digit3 = digit3;*/ } //tag::method[] - public void displayValue(String value) +/* public void displayValue(String value) //end::method[] { char[] num = value.toCharArray(); @@ -107,47 +124,58 @@ public void displayValue(String value) log.info("Displaying number: {}", value); // TODO: replace with actual implementation - setValue(digit0, CharMap.FOUR); + setValue(digit0, CharMap.FOUR);*/ // Render numbers with leading zeroes, e.g. 1 as 0001 /* switch (num.length) { case 1: - *//*sevenSegmentDisplay1.displayNumber(0); + */ + /*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber(0); sevenSegmentDisplay3.displayNumber(0); - sevenSegmentDisplay4.displayNumber((int) num[0]);*//* + sevenSegmentDisplay4.displayNumber((int) num[0]);*/ + /* break; case 2: - *//*sevenSegmentDisplay1.displayNumber(0); + */ + /*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber(0); sevenSegmentDisplay3.displayNumber((int) num[0]); - sevenSegmentDisplay4.displayNumber((int) num[1]);*//* + sevenSegmentDisplay4.displayNumber((int) num[1]);*/ + /* break; case 3: - *//*sevenSegmentDisplay1.displayNumber(0); + */ + /*sevenSegmentDisplay1.displayNumber(0); sevenSegmentDisplay2.displayNumber((int) num[0]); sevenSegmentDisplay3.displayNumber((int) num[1]); - sevenSegmentDisplay4.displayNumber((int) num[2]);*//* + sevenSegmentDisplay4.displayNumber((int) num[2]);*/ + /* break; case 4: - *//*sevenSegmentDisplay1.displayNumber((int) num[0]); + */ + /*sevenSegmentDisplay1.displayNumber((int) num[0]); sevenSegmentDisplay2.displayNumber((int) num[1]); sevenSegmentDisplay3.displayNumber((int) num[2]); - sevenSegmentDisplay4.displayNumber((int) num[3]);*//* + sevenSegmentDisplay4.displayNumber((int) num[3]);*/ + /* break; - }*/ - } + } + }*/ /*//tag::method[] public String getValue() //end::method[] { - *//*int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; - return Integer.parseInt(String.valueOf(values));*//* + */ + /*int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; + return Integer.parseInt(String.valueOf(values));*/ + /* }*/ - private void setValue(DigitalOutput digit, CharMap character) { - /*segmentA.off(); + /* private void setValue(DigitalOutput digit, CharMap character) { + */ + /*segmentA.off(); segmentB.off(); segmentC.off(); segmentD.off(); @@ -183,6 +211,7 @@ private void setValue(DigitalOutput digit, CharMap character) { } } }*/ + /* segmentA.low(); segmentB.low(); @@ -220,5 +249,5 @@ private void setValue(DigitalOutput digit, CharMap character) { } } } - } + }*/ } diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java new file mode 100644 index 00000000..997380ba --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java @@ -0,0 +1,28 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SegmentHelper { + private static Logger log = LoggerFactory.getLogger(SegmentHelper.class); + + private final DigitalOutput segmentOutput; + + //tag::const[] + public SegmentHelper(DigitalOutput segmentOutput) + //end::const[] + { + this.segmentOutput = segmentOutput; + } + + //tag::method[] + public void segmentOn() + //end::method[] + { + if (segmentOutput.isLow()) { + log.info("Turning on Segment"); + segmentOutput.high(); + } + } +} diff --git a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java deleted file mode 100644 index 94b1de6b..00000000 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.opensourcewithslu.outputdevices; - -import org.junit.jupiter.api.Test; - -import org.slf4j.Logger; - -import static org.mockito.Mockito.*; -import static org.junit.jupiter.api.Assertions.*; - -import com.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper; - -public class FourDigitSevenSegmentDisplayHelperTest { - /*FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); - SevenSegmentDisplayHelper sevenSegmentDisplayHelper = mock(SevenSegmentDisplayHelper.class); - Logger log = mock(Logger.class); - - @Test - void longNumberFails() { - String number = "12345"; - displayHelper.displayNumber(number); - verify(log).error("Number is too long"); - verify(log, never()).info("Displaying number: {}", "12345"); - verify(sevenSegmentDisplayHelper, never()).displayNumber(1); - verify(sevenSegmentDisplayHelper, never()).displayNumber(2); - verify(sevenSegmentDisplayHelper, never()).displayNumber(3); - verify(sevenSegmentDisplayHelper, never()).displayNumber(4); - verify(sevenSegmentDisplayHelper, never()).displayNumber(5); - } - - void shortNumberFails() { - String number = ""; - displayHelper.displayNumber(number); - verify(log).error("Number is too short"); - verify(log, never()).info("Displaying number: {}", ""); - } - - @Test - void nonNumberFails() { - String number = "abc"; - displayHelper.displayNumber(number); - verify(log).error("Display value must be an integer"); - } - - @Test - void negativeNumberFails() { - String number = "-1234"; - displayHelper.displayNumber(number); - verify(log).error("Display value must be positive"); - } - - @Test - void displaysFourDigitNumber() { - String number = "1234"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("1234"); - verify(log).info("Displaying number: {}", "1234"); - -// int displayed = displayHelper.getValue(); -// assertEquals(1234, displayed); - } - - @Test - void displaysThreeDigitNumber() { - String number = "123"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("123"); - verify(log).info("Displaying number: {}", "123"); - -// int displayed = displayHelper.getValue(); -// assertEquals(123, displayed); - } - - @Test - void displaysTwoDigitNumber() { - String number = "34"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("34"); - verify(log).info("Displaying number: {}", "34"); - -// int displayed = displayHelper.getValue(); -// assertEquals(34, displayed); - } - - @Test - void displaysOneDigitNumber() { - String number = "4"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("4"); - verify(log).info("Displaying number: {}", "4"); - -// int displayed = displayHelper.getValue(); -// assertEquals(4, displayed); - }*/ -} From 7ec115c49f038ab42b578ebbcc35eb1440302c70 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 18:09:30 -0600 Subject: [PATCH 19/33] Removed things from the now deprecated, ill-fated, late multi digital output configuration. You will not be missed. --- ...ourDigitSevenSegmentDisplayController.java | 1 - components/src/main/resources/application.yml | 18 -- .../FourDigitSevenSegmentDisplayHelper.java | 253 ------------------ .../DigitalOutputMultiPinConfiguration.java | 131 --------- .../utilities/Pi4JMultiPinFactory.java | 32 --- 5 files changed, 435 deletions(-) delete mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java delete mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 0ea4951b..eb65bd86 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -1,7 +1,6 @@ package com.opensourcewithslu.components.controllers; import com.opensourcewithslu.outputdevices.DigitHelper; -import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; //import com.opensourcewithslu.utilities.MultiPinConfiguration; import com.opensourcewithslu.outputdevices.SegmentHelper; import com.pi4j.io.gpio.digital.DigitalOutput; diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 59119912..0e88d1a6 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -271,22 +271,4 @@ pi4j: provider: pigpio-pwm initials: 0, 0, 0 shutdowns: 0, 0, 0 - # 4digit7segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - # initials: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - # pwmTypes: SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE - # provider: pigpio-pwm # end::multiPWM[] - - # tag::multiOutput[] - # multi-digital-output: - # four-digit-seven-segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 16, 12, 11, 13, 15, 19 # 74HC595 pin 14, 74HC595 pin 12, 74HC595 pin 11, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - # provider: pigpio-digital-output - # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java deleted file mode 100644 index a404bc5f..00000000 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.opensourcewithslu.outputdevices; - -import com.pi4j.io.gpio.digital.DigitalOutput; -import com.pi4j.io.pwm.Pwm; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -//import com.opensourcewithslu.utilities.MultiPinConfiguration; - -public class FourDigitSevenSegmentDisplayHelper { -/* - private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); - -*/ -/* private int[][] charMap = { - {1, 1, 1, 1, 1, 1, 0}, - {0, 1, 1, 0, 0, 0, 0}, - {1, 1, 0, 1, 1, 0, 1}, - {1, 1, 1, 1, 0, 0, 1}, - {0, 1, 1, 0, 0, 1, 1}, - {1, 0, 1, 1, 0, 1, 1}, - {1, 0, 1, 1, 1, 1, 1}, - {1, 1, 1, 0, 0, 0, 0}, - {1, 1, 1, 1, 1, 1, 1}, - {1, 1, 1, 1, 0, 1, 1}, - {0, 0, 0, 0, 0, 0, 1} - };*/ - /* - - - private enum CharMap { - ZERO(new int[]{1, 1, 1, 1, 1, 1, 0}), - ONE(new int[]{0, 1, 1, 0, 0, 0, 0}), - TWO(new int[]{1, 1, 0, 1, 1, 0, 1}), - THREE(new int[]{1, 1, 1, 1, 0, 0, 1}), - FOUR(new int[]{0, 1, 1, 0, 0, 1, 1}), - FIVE(new int[]{1, 0, 1, 1, 0, 1, 1}), - SIX(new int[]{1, 0, 1, 1, 1, 1, 1}), - SEVEN(new int[]{1, 1, 1, 0, 0, 0, 0}), - EIGHT(new int[]{1, 1, 1, 1, 1, 1, 1}), - NINE(new int[]{1, 1, 1, 1, 0, 1, 1}), - HYPHEN(new int[]{0, 0, 0, 0, 0, 0, 1}); - - private int[] segmentArray; - - public int[] getSegmentArray() { - return this.segmentArray; - } - - CharMap(int[] segmentArray) { - this.segmentArray = segmentArray; - } - } - - private final DigitalOutput segmentA; - private final DigitalOutput segmentB; - private final DigitalOutput segmentC; - private final DigitalOutput segmentD; - private final DigitalOutput segmentE; - private final DigitalOutput segmentF; - private final DigitalOutput segmentG; - private final DigitalOutput DP; - private final DigitalOutput digit0; - private final DigitalOutput digit1; - private final DigitalOutput digit2; - private final DigitalOutput digit3; -*/ - - //tag::const[] - public FourDigitSevenSegmentDisplayHelper(/*DigitalOutput segmentA, DigitalOutput segmentB, DigitalOutput segmentC, DigitalOutput segmentD, DigitalOutput segmentE, DigitalOutput segmentF, DigitalOutput segmentG, DigitalOutput segmentDot, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3*/) - //end::const[] - { - /*DigitalOutput[] components = (DigitalOutput[]) fourDigitSevenSeg.getComponents(); - this.segmentA = components[0]; - this.segmentB = components[1]; - this.segmentC = components[2]; - this.segmentD = components[3]; - this.segmentE = components[4]; - this.segmentF = components[5]; - this.segmentG = components[6]; - this.DP = components[7]; - this.digit0 = components[8]; - this.digit1 = components[9]; - this.digit2 = components[10]; - this.digit3 = components[11];*/ -/* this.segmentA = segmentA; - this.segmentB = segmentB; - this.segmentC = segmentC; - this.segmentD = segmentD; - this.segmentE = segmentE; - this.segmentF = segmentF; - this.segmentG = segmentG; - this.DP = segmentDot; - this.digit0 = digit0; - this.digit1 = digit1; - this.digit2 = digit2; - this.digit3 = digit3;*/ - } - - //tag::method[] -/* public void displayValue(String value) - //end::method[] - { - char[] num = value.toCharArray(); - - if (num.length > 4) { - log.error("Number is too long"); - return; - } - if (num.length == 0) { - log.error("Number is too short"); - return; - } - try { - int integerNumber = Integer.parseInt(value); - if (integerNumber < 0) { - log.error("Display value must be positive"); - return; - } - } catch (NumberFormatException e) { - log.error("Display value must be an integer"); - return; - } - - log.info("Displaying number: {}", value); - - // TODO: replace with actual implementation - setValue(digit0, CharMap.FOUR);*/ - - // Render numbers with leading zeroes, e.g. 1 as 0001 -/* switch (num.length) { - case 1: - */ - /*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber(0); - sevenSegmentDisplay3.displayNumber(0); - sevenSegmentDisplay4.displayNumber((int) num[0]);*/ - /* - break; - case 2: - */ - /*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber(0); - sevenSegmentDisplay3.displayNumber((int) num[0]); - sevenSegmentDisplay4.displayNumber((int) num[1]);*/ - /* - break; - case 3: - */ - /*sevenSegmentDisplay1.displayNumber(0); - sevenSegmentDisplay2.displayNumber((int) num[0]); - sevenSegmentDisplay3.displayNumber((int) num[1]); - sevenSegmentDisplay4.displayNumber((int) num[2]);*/ - /* - break; - case 4: - */ - /*sevenSegmentDisplay1.displayNumber((int) num[0]); - sevenSegmentDisplay2.displayNumber((int) num[1]); - sevenSegmentDisplay3.displayNumber((int) num[2]); - sevenSegmentDisplay4.displayNumber((int) num[3]);*/ - /* - break; - } - }*/ - - /*//tag::method[] - public String getValue() - //end::method[] - { - */ - /*int[] values = {sevenSegmentDisplay1.getValue(), sevenSegmentDisplay2.getValue(), sevenSegmentDisplay3.getValue(), sevenSegmentDisplay4.getValue()}; - return Integer.parseInt(String.valueOf(values));*/ - /* - }*/ - - /* private void setValue(DigitalOutput digit, CharMap character) { - */ - /*segmentA.off(); - segmentB.off(); - segmentC.off(); - segmentD.off(); - segmentE.off(); - segmentF.off(); - segmentG.off(); - - digit.on(100, 50); - for (int i = 0; i < 7; i++) { - if (character.getSegmentArray()[i] == 1) { - switch (i) { - case 0: - segmentA.on(100, 50); - break; - case 1: - segmentB.on(100, 50); - break; - case 2: - segmentC.on(100, 50); - break; - case 3: - segmentD.on(100, 50); - break; - case 4: - segmentE.on(100, 50); - break; - case 5: - segmentF.on(100, 50); - break; - case 6: - segmentG.on(100, 50); - break; - } - } - }*/ - /* - - segmentA.low(); - segmentB.low(); - segmentC.low(); - segmentD.low(); - segmentE.low(); - segmentF.low(); - segmentG.low(); - - digit.high(); - for (int i = 0; i < 7; i++) { - if (character.getSegmentArray()[i] == 1) { - switch (i) { - case 0: - segmentA.high(); - break; - case 1: - segmentB.high(); - break; - case 2: - segmentC.high(); - break; - case 3: - segmentD.high(); - break; - case 4: - segmentE.high(); - break; - case 5: - segmentF.high(); - break; - case 6: - segmentG.high(); - break; - } - } - } - }*/ -} diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java deleted file mode 100644 index 0c13389a..00000000 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.opensourcewithslu.utilities.MultiPinConfigs; - -import com.pi4j.io.gpio.digital.DigitalState; -import io.micronaut.context.annotation.EachProperty; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Prototype; - -import java.util.Arrays; - -/** - * This class handles the configuration of a digital output component that has multiple pins. - */ -@Prototype -@EachProperty("pi4j.multi-digital-output") -public class DigitalOutputMultiPinConfiguration { - private final String id; - private String name; - private int[] addresses; - private DigitalState[] initials; - private DigitalState[] shutdowns; - private String provider; - - /** - * The DigitalOutputMultiPinConfiguration constructor. - * - * @param id The configuration id as defined in the application.yml - */ - public DigitalOutputMultiPinConfiguration(@Parameter String id) { - this.id = id + "MultiPin"; - } - - /** - * Gets the id of the component. - * - * @return The id of the component. - */ - public String getId() { - return id; - } - - /** - * Gets the name of the component. - * - * @return The name of the component. - */ - public String getName() { - return name; - } - - /** - * Sets the name of the component. - * - * @param name The string name to replace the existing name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Gets the pin addresses for the component. - * - * @return An array of the pin addresses. - */ - public int[] getAddresses() { - return addresses; - } - - /** - * Sets the pin addresses for the component. All previously existing address are replaced. - * - * @param addresses Pin addresses separated by a comma. - */ - public void setAddresses(String addresses) { - addresses = addresses.replaceAll("\\s", ""); - this.addresses = Arrays.stream(addresses.split(",")).mapToInt(Integer::parseInt).toArray(); - } - - /** - * Gets the initial states that the component is in when first initialized. - * - * @return Array of integers representing the initial state for each pin. - */ - public DigitalState[] getInitials() { - return initials; - } - - /** - * Sets the initial states for the component. - * - * @param initials Array of initial Digital states. - */ - public void setInitials(DigitalState[] initials) { - this.initials = initials; - } - - /** - * Gets the shutdown states for the component. - * - * @return Array of integers representing the shutdowns. - */ - public DigitalState[] getShutdowns() { - return shutdowns; - } - - /** - * Sets the shutdown states for the component. Existing shutdowns are replaced. - * - * @param shutdowns Array of shutdowns Digital states. - */ - public void setShutdowns(DigitalState[] shutdowns) { - this.shutdowns = shutdowns; - } - - /** - * Gets the provider for the component. - * - * @return A String representation of the provider. - */ - public String getProvider() { - return provider; - } - - /** - * Sets the provider. - * - * @param provider The new provider for the component. - */ - public void setProvider(String provider) { - this.provider = provider; - } -} diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java index c4939f50..980708be 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java @@ -2,11 +2,8 @@ import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalInputMultiPinConfiguration; import com.opensourcewithslu.utilities.MultiPinConfigs.PwmMultiPinConfiguration; -import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalOutputMultiPinConfiguration; import com.pi4j.context.Context; import com.pi4j.io.gpio.digital.DigitalInput; -import com.pi4j.io.gpio.digital.DigitalOutput; -import com.pi4j.io.gpio.digital.DigitalState; import com.pi4j.io.pwm.Pwm; import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; @@ -80,33 +77,4 @@ public MultiPinConfiguration multiPinPwm(PwmMultiPinConfiguration config, Contex String multiPinId = config.getId().substring(0, config.getId().length() - 8); return new MultiPinConfiguration(multiPinId, allPWMs); } - - - /** - * Creates a MultiPinConfiguration object for a multi pin digital output component. - * - * @param config {@link DigitalOutputMultiPinConfiguration} Object. - * @param pi4jContext The Pi4J {@link Context}. - * @return A MultiPinConfiguration object. - */ - @Singleton - @EachBean(DigitalOutputMultiPinConfiguration.class) - public MultiPinConfiguration multiPinOutput(DigitalOutputMultiPinConfiguration config, Context pi4jContext) { - int[] addresses = config.getAddresses(); - DigitalOutput[] allOutputs = new DigitalOutput[addresses.length]; - - for (int i = 0; i < addresses.length; i++) { - var outputConfigBuilder = DigitalOutput.newConfigBuilder(pi4jContext) - .id(config.getId() + i) - .name(config.getName()) - .address(config.getAddresses()[i]) - .initial(config.getInitials()[i]) - .shutdown(config.getShutdowns()[i]) - .provider(config.getProvider()); - - allOutputs[i] = pi4jContext.create(outputConfigBuilder); - } - - return new MultiPinConfiguration(config.getId(), allOutputs); - } } From 179c71884a5de2e03a3986722e575022275f12ec Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 18:14:57 -0600 Subject: [PATCH 20/33] further deprecation for multi digital output that i missed --- .../controllers/FourDigitSevenSegmentDisplayController.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index eb65bd86..663b5889 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -1,7 +1,6 @@ package com.opensourcewithslu.components.controllers; import com.opensourcewithslu.outputdevices.DigitHelper; -//import com.opensourcewithslu.utilities.MultiPinConfiguration; import com.opensourcewithslu.outputdevices.SegmentHelper; import com.pi4j.io.gpio.digital.DigitalOutput; import io.micronaut.http.annotation.Controller; @@ -11,7 +10,6 @@ //tag::ex[] @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { - // private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; private final SegmentHelper segmentA; private final SegmentHelper segmentB; private final SegmentHelper segmentC; @@ -38,8 +36,6 @@ public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput @Named("digit-2") DigitalOutput digit2, @Named("digit-3") DigitalOutput digit3 ) { -// this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(segmentA, segmentB, segmentC, segmentD, segmentE, segmentF, segmentG, segmentDot, digit0, digit1, digit2, digit3); -// this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg); this.segmentA = new SegmentHelper(segmentA); this.segmentB = new SegmentHelper(segmentB); this.segmentC = new SegmentHelper(segmentC); @@ -56,7 +52,6 @@ public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput /*@Get("/displayNumber/{value}") public void displayValue(String value) { - fourDigitSevenSegmentDisplayHelper.displayValue(value); }*/ @Get("/test") From 44efcedf7d57194e1cd388f4a7fa79af13eedfcd Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 18:21:44 -0600 Subject: [PATCH 21/33] remove stuff from my attempt 2 do a multi digital output configur8ion --- ...ourDigitSevenSegmentDisplayController.java | 2 - components/src/main/resources/application.yml | 55 ------------------- .../utilities/Pi4JMultiPinFactory.java | 32 ----------- 3 files changed, 89 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index e46a506f..1b050065 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -5,7 +5,6 @@ import com.pi4j.io.i2c.I2CConfig; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import jakarta.inject.Inject; import jakarta.inject.Named; //tag::ex[] @@ -13,7 +12,6 @@ public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper fourDigitSevenSegmentDisplayHelper; - // @Inject public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") I2CConfig fourdigsevenseg, Context pi4jContext ) { this.fourDigitSevenSegmentDisplayHelper = new FourDigitSevenSegmentDisplayHelper(fourdigsevenseg, pi4jContext); diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 03b50f29..c8351c1e 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -61,44 +61,6 @@ pi4j: name: 4 Digit 7 Segment Display bus: 1 device: 0x27 - # segments: - # segmentA: - # pin: 18 - # state: "LOW" - # segmentB: - # pin: 22 - # state: "LOW" - # segmentC: - # pin: 24 - # state: "LOW" - # segmentD: - # pin: 26 - # state: "LOW" - # segmentE: - # pin: 28 - # state: "LOW" - # segmentF: - # pin: 32 - # state: "LOW" - # segmentG: - # pin: 36 - # state: "LOW" - # decimal-point: - # pin: 38 - # state: "LOW" - # digits: - # digit0: - # pin: 11 - # state: "LOW" - # digit1: - # pin: 13 - # state: "LOW" - # digit2: - # pin: 15 - # state: "LOW" - # digit3: - # pin: 17 - # state: "LOW" # end::i2c[] # tag::digitalInput[] @@ -241,21 +203,4 @@ pi4j: provider: pigpio-pwm initials: 0, 0, 0 shutdowns: 0, 0, 0 - # 4digit7segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - # initials: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - # pwmTypes: SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE, SOFTWARE - # provider: pigpio-pwm # end::multiPWM[] - - # tag::multiOutput[] - # multi-digital-output: - # 4digit7segment: - # name: 4 Digit 7 Segment Display - # addresses: 18, 22, 24, 26, 28, 32, 36, 38, 11, 13, 15, 17 # A, B, C, D, E, F, G, DP, DIGIT1, DIGIT2, DIGIT3, DIGIT4 - # shutdowns: LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW - # initials: HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH, HIGH - # provider: pigpio-digital-output - # end::multiOutput[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java index c4939f50..980708be 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/Pi4JMultiPinFactory.java @@ -2,11 +2,8 @@ import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalInputMultiPinConfiguration; import com.opensourcewithslu.utilities.MultiPinConfigs.PwmMultiPinConfiguration; -import com.opensourcewithslu.utilities.MultiPinConfigs.DigitalOutputMultiPinConfiguration; import com.pi4j.context.Context; import com.pi4j.io.gpio.digital.DigitalInput; -import com.pi4j.io.gpio.digital.DigitalOutput; -import com.pi4j.io.gpio.digital.DigitalState; import com.pi4j.io.pwm.Pwm; import io.micronaut.context.annotation.EachBean; import io.micronaut.context.annotation.Factory; @@ -80,33 +77,4 @@ public MultiPinConfiguration multiPinPwm(PwmMultiPinConfiguration config, Contex String multiPinId = config.getId().substring(0, config.getId().length() - 8); return new MultiPinConfiguration(multiPinId, allPWMs); } - - - /** - * Creates a MultiPinConfiguration object for a multi pin digital output component. - * - * @param config {@link DigitalOutputMultiPinConfiguration} Object. - * @param pi4jContext The Pi4J {@link Context}. - * @return A MultiPinConfiguration object. - */ - @Singleton - @EachBean(DigitalOutputMultiPinConfiguration.class) - public MultiPinConfiguration multiPinOutput(DigitalOutputMultiPinConfiguration config, Context pi4jContext) { - int[] addresses = config.getAddresses(); - DigitalOutput[] allOutputs = new DigitalOutput[addresses.length]; - - for (int i = 0; i < addresses.length; i++) { - var outputConfigBuilder = DigitalOutput.newConfigBuilder(pi4jContext) - .id(config.getId() + i) - .name(config.getName()) - .address(config.getAddresses()[i]) - .initial(config.getInitials()[i]) - .shutdown(config.getShutdowns()[i]) - .provider(config.getProvider()); - - allOutputs[i] = pi4jContext.create(outputConfigBuilder); - } - - return new MultiPinConfiguration(config.getId(), allOutputs); - } } From 50a8d38f9c0cad7e0fd071bcacead84382f0f95f Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 18:23:41 -0600 Subject: [PATCH 22/33] delete DigitalOutputMultiPinConfiguration.java --- .../DigitalOutputMultiPinConfiguration.java | 131 ------------------ 1 file changed, 131 deletions(-) delete mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java deleted file mode 100644 index 0c13389a..00000000 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/utilities/MultiPinConfigs/DigitalOutputMultiPinConfiguration.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.opensourcewithslu.utilities.MultiPinConfigs; - -import com.pi4j.io.gpio.digital.DigitalState; -import io.micronaut.context.annotation.EachProperty; -import io.micronaut.context.annotation.Parameter; -import io.micronaut.context.annotation.Prototype; - -import java.util.Arrays; - -/** - * This class handles the configuration of a digital output component that has multiple pins. - */ -@Prototype -@EachProperty("pi4j.multi-digital-output") -public class DigitalOutputMultiPinConfiguration { - private final String id; - private String name; - private int[] addresses; - private DigitalState[] initials; - private DigitalState[] shutdowns; - private String provider; - - /** - * The DigitalOutputMultiPinConfiguration constructor. - * - * @param id The configuration id as defined in the application.yml - */ - public DigitalOutputMultiPinConfiguration(@Parameter String id) { - this.id = id + "MultiPin"; - } - - /** - * Gets the id of the component. - * - * @return The id of the component. - */ - public String getId() { - return id; - } - - /** - * Gets the name of the component. - * - * @return The name of the component. - */ - public String getName() { - return name; - } - - /** - * Sets the name of the component. - * - * @param name The string name to replace the existing name. - */ - public void setName(String name) { - this.name = name; - } - - /** - * Gets the pin addresses for the component. - * - * @return An array of the pin addresses. - */ - public int[] getAddresses() { - return addresses; - } - - /** - * Sets the pin addresses for the component. All previously existing address are replaced. - * - * @param addresses Pin addresses separated by a comma. - */ - public void setAddresses(String addresses) { - addresses = addresses.replaceAll("\\s", ""); - this.addresses = Arrays.stream(addresses.split(",")).mapToInt(Integer::parseInt).toArray(); - } - - /** - * Gets the initial states that the component is in when first initialized. - * - * @return Array of integers representing the initial state for each pin. - */ - public DigitalState[] getInitials() { - return initials; - } - - /** - * Sets the initial states for the component. - * - * @param initials Array of initial Digital states. - */ - public void setInitials(DigitalState[] initials) { - this.initials = initials; - } - - /** - * Gets the shutdown states for the component. - * - * @return Array of integers representing the shutdowns. - */ - public DigitalState[] getShutdowns() { - return shutdowns; - } - - /** - * Sets the shutdown states for the component. Existing shutdowns are replaced. - * - * @param shutdowns Array of shutdowns Digital states. - */ - public void setShutdowns(DigitalState[] shutdowns) { - this.shutdowns = shutdowns; - } - - /** - * Gets the provider for the component. - * - * @return A String representation of the provider. - */ - public String getProvider() { - return provider; - } - - /** - * Sets the provider. - * - * @param provider The new provider for the component. - */ - public void setProvider(String provider) { - this.provider = provider; - } -} From 350e1c4a5a5dc3059c856ffe8270431120ad8a48 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 19:08:47 -0600 Subject: [PATCH 23/33] temp --- ...ourDigitSevenSegmentDisplayController.java | 74 +++++++++++++++++-- .../outputdevices/DigitHelper.java | 21 ++++-- .../outputdevices/SegmentHelper.java | 19 ++--- 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 663b5889..be2931e8 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -7,7 +7,6 @@ import io.micronaut.http.annotation.Get; import jakarta.inject.Named; -//tag::ex[] @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { private final SegmentHelper segmentA; @@ -23,6 +22,9 @@ public class FourDigitSevenSegmentDisplayController { private final DigitHelper digit2; private final DigitHelper digit3; + private static final int[] number = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; + private int counter = 0; + public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput segmentA, @Named("segment-b") DigitalOutput segmentB, @Named("segment-c") DigitalOutput segmentC, @@ -34,8 +36,7 @@ public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput @Named("digit-0") DigitalOutput digit0, @Named("digit-1") DigitalOutput digit1, @Named("digit-2") DigitalOutput digit2, - @Named("digit-3") DigitalOutput digit3 - ) { + @Named("digit-3") DigitalOutput digit3) { this.segmentA = new SegmentHelper(segmentA); this.segmentB = new SegmentHelper(segmentB); this.segmentC = new SegmentHelper(segmentC); @@ -50,13 +51,70 @@ public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput this.digit3 = new DigitHelper(digit3); } - /*@Get("/displayNumber/{value}") - public void displayValue(String value) { - }*/ + private void pickDigit(int digit) { + digit0.digitOff(); + digit1.digitOff(); + digit2.digitOff(); + digit3.digitOff(); + + switch (digit) { + case 0: + digit0.digitOn(); + break; + case 1: + digit1.digitOn(); + break; + case 2: + digit2.digitOn(); + break; + case 3: + digit3.digitOn(); + break; + } + } + + private void hc595_shift(int data) { + for (int i = 0; i < 8; i++) { + if ((data & (1 << (7 - i))) != 0) { + segmentA.segmentOn(); + } else { + segmentA.segmentOff(); + } + segmentB.segmentOn(); + segmentB.segmentOff(); + } + segmentC.segmentOn(); + segmentC.segmentOff(); + } + + private void clearDisplay() { + for (int i = 0; i < 8; i++) { + segmentA.segmentOff(); + segmentB.segmentOn(); + segmentB.segmentOff(); + } + segmentC.segmentOn(); + segmentC.segmentOff(); + } @Get("/test") public void test() { - segmentA.segmentOn(); + while (true) { + clearDisplay(); + pickDigit(0); + hc595_shift(number[counter % 10]); + + clearDisplay(); + pickDigit(1); + hc595_shift(number[counter % 100 / 10]); + + clearDisplay(); + pickDigit(2); + hc595_shift(number[counter % 1000 / 100]); + + clearDisplay(); + pickDigit(3); + hc595_shift(number[counter % 10000 / 1000]); + } } } -//end::ex[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java index 3cbd6099..40a45bd8 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java @@ -5,14 +5,25 @@ import org.slf4j.LoggerFactory; public class DigitHelper { - private static Logger log = LoggerFactory.getLogger(DigitHelper.class); + private static final Logger log = LoggerFactory.getLogger(DigitHelper.class); private final DigitalOutput digitOutput; - //tag::const[] - public DigitHelper(DigitalOutput digitOutput) - //end::const[] - { + public DigitHelper(DigitalOutput digitOutput) { this.digitOutput = digitOutput; } + + public void digitOn() { + if (digitOutput.isLow()) { + log.info("Turning on Digit"); + digitOutput.high(); + } + } + + public void digitOff() { + if (digitOutput.isHigh()) { + log.info("Turning off Digit"); + digitOutput.low(); + } + } } diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java index 997380ba..3e3df09e 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java @@ -5,24 +5,25 @@ import org.slf4j.LoggerFactory; public class SegmentHelper { - private static Logger log = LoggerFactory.getLogger(SegmentHelper.class); + private static final Logger log = LoggerFactory.getLogger(SegmentHelper.class); private final DigitalOutput segmentOutput; - //tag::const[] - public SegmentHelper(DigitalOutput segmentOutput) - //end::const[] - { + public SegmentHelper(DigitalOutput segmentOutput) { this.segmentOutput = segmentOutput; } - //tag::method[] - public void segmentOn() - //end::method[] - { + public void segmentOn() { if (segmentOutput.isLow()) { log.info("Turning on Segment"); segmentOutput.high(); } } + + public void segmentOff() { + if (segmentOutput.isHigh()) { + log.info("Turning off Segment"); + segmentOutput.low(); + } + } } From 9a0f4533666ef77a33deaca2c9a89887ab9c81d8 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Fri, 8 Nov 2024 20:01:20 -0600 Subject: [PATCH 24/33] ok --- ...ourDigitSevenSegmentDisplayController.java | 51 +++--------- components/src/main/resources/application.yml | 77 +++++++------------ .../outputdevices/SegmentHelper.java | 37 ++++++--- 3 files changed, 60 insertions(+), 105 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index be2931e8..77e83966 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -3,20 +3,14 @@ import com.opensourcewithslu.outputdevices.DigitHelper; import com.opensourcewithslu.outputdevices.SegmentHelper; import com.pi4j.io.gpio.digital.DigitalOutput; +import com.pi4j.io.spi.SpiConfig; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import jakarta.inject.Named; @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { - private final SegmentHelper segmentA; - private final SegmentHelper segmentB; - private final SegmentHelper segmentC; - private final SegmentHelper segmentD; - private final SegmentHelper segmentE; - private final SegmentHelper segmentF; - private final SegmentHelper segmentG; - private final SegmentHelper segmentDot; + private final SegmentHelper segmentHelper; private final DigitHelper digit0; private final DigitHelper digit1; private final DigitHelper digit2; @@ -25,26 +19,15 @@ public class FourDigitSevenSegmentDisplayController { private static final int[] number = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; private int counter = 0; - public FourDigitSevenSegmentDisplayController(@Named("segment-a") DigitalOutput segmentA, - @Named("segment-b") DigitalOutput segmentB, - @Named("segment-c") DigitalOutput segmentC, - @Named("segment-d") DigitalOutput segmentD, - @Named("segment-e") DigitalOutput segmentE, - @Named("segment-f") DigitalOutput segmentF, - @Named("segment-g") DigitalOutput segmentG, - @Named("segment-dot") DigitalOutput segmentDot, + public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") SpiConfig spi, + @Named("sdi") DigitalOutput sdi, + @Named("rclk") DigitalOutput rclk, + @Named("srclk") DigitalOutput srclk, @Named("digit-0") DigitalOutput digit0, @Named("digit-1") DigitalOutput digit1, @Named("digit-2") DigitalOutput digit2, @Named("digit-3") DigitalOutput digit3) { - this.segmentA = new SegmentHelper(segmentA); - this.segmentB = new SegmentHelper(segmentB); - this.segmentC = new SegmentHelper(segmentC); - this.segmentD = new SegmentHelper(segmentD); - this.segmentE = new SegmentHelper(segmentE); - this.segmentF = new SegmentHelper(segmentF); - this.segmentG = new SegmentHelper(segmentG); - this.segmentDot = new SegmentHelper(segmentDot); + this.segmentHelper = new SegmentHelper(sdi, rclk, srclk); this.digit0 = new DigitHelper(digit0); this.digit1 = new DigitHelper(digit1); this.digit2 = new DigitHelper(digit2); @@ -74,27 +57,11 @@ private void pickDigit(int digit) { } private void hc595_shift(int data) { - for (int i = 0; i < 8; i++) { - if ((data & (1 << (7 - i))) != 0) { - segmentA.segmentOn(); - } else { - segmentA.segmentOff(); - } - segmentB.segmentOn(); - segmentB.segmentOff(); - } - segmentC.segmentOn(); - segmentC.segmentOff(); + segmentHelper.shiftOut(data); } private void clearDisplay() { - for (int i = 0; i < 8; i++) { - segmentA.segmentOff(); - segmentB.segmentOn(); - segmentB.segmentOff(); - } - segmentC.segmentOn(); - segmentC.segmentOff(); + segmentHelper.clear(); } @Get("/test") diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 0e88d1a6..b952435d 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -10,6 +10,11 @@ pi4j: address: 8 # <3> baud: 500000 # <4> reset-pin: 25 # <5> + four-digit-seven-segment: + name: Four Digit Seven Segment Display + address: 0 + baud: 1000000 + reset-pin: 25 # end::spi[] # tag::pwm[] @@ -161,75 +166,45 @@ pi4j: shutdown: LOW initial: LOW provider: pigpio-digital-output - segment-a: - name: Segment A - address: 18 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-b: - name: Segment B - address: 22 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-c: - name: Segment C - address: 24 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-d: - name: Segment D - address: 26 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-e: - name: Segment E - address: 28 - shutdown: LOW - initial: LOW - provider: pigpio-digital-output - segment-f: - name: Segment F - address: 32 + digit-0: + name: Digit 0 + address: 17 shutdown: LOW initial: LOW provider: pigpio-digital-output - segment-g: - name: Segment G - address: 36 + digit-1: + name: Digit 1 + address: 27 shutdown: LOW initial: LOW provider: pigpio-digital-output - segment-dot: - name: Segment DOT - address: 38 + digit-2: + name: Digit 2 + address: 22 shutdown: LOW initial: LOW provider: pigpio-digital-output - digit-0: - name: Digit 0 - address: 11 + digit-3: + name: Digit 3 + address: 10 shutdown: LOW initial: LOW provider: pigpio-digital-output - digit-1: - name: Digit 1 - address: 13 + sdi: + name: SDI + address: 24 shutdown: LOW initial: LOW provider: pigpio-digital-output - digit-2: - name: Digit 2 - address: 15 + rclk: + name: RCLK + address: 23 shutdown: LOW initial: LOW provider: pigpio-digital-output - digit-3: - name: Digit 3 - address: 17 + srclk: + name: SRCLK + address: 18 shutdown: LOW initial: LOW provider: pigpio-digital-output diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java index 3e3df09e..47e2b8ed 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java @@ -7,23 +7,36 @@ public class SegmentHelper { private static final Logger log = LoggerFactory.getLogger(SegmentHelper.class); - private final DigitalOutput segmentOutput; + private final DigitalOutput sdi; + private final DigitalOutput rclk; + private final DigitalOutput srclk; - public SegmentHelper(DigitalOutput segmentOutput) { - this.segmentOutput = segmentOutput; + public SegmentHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk) { + this.sdi = sdi; + this.rclk = rclk; + this.srclk = srclk; } - public void segmentOn() { - if (segmentOutput.isLow()) { - log.info("Turning on Segment"); - segmentOutput.high(); + public void shiftOut(int data) { + try { + for (int i = 0; i < 8; i++) { + srclk.low(); + if ((data & (1 << (7 - i))) != 0) { + sdi.high(); + } else { + sdi.low(); + } + srclk.high(); + } + rclk.high(); + rclk.low(); + log.info("Shifted out data: " + Integer.toHexString(data)); + } catch (Exception e) { + log.error("Error shifting out data", e); } } - public void segmentOff() { - if (segmentOutput.isHigh()) { - log.info("Turning off Segment"); - segmentOutput.low(); - } + public void clear() { + shiftOut(0x00); } } From 36af03ac85d998e85a3e3c0a8471a9e7b744434b Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 9 Nov 2024 19:46:20 -0600 Subject: [PATCH 25/33] Use Pi4J's seven segment component --- ...ourDigitSevenSegmentDisplayController.java | 27 ++++---- .../FourDigitSevenSegmentDisplayHelper.java | 66 ++++++++++++++++--- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 1b050065..390c6606 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -19,17 +19,22 @@ public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") @Get("/enable") public void enable() { -// fourDigitSevenSegmentDisplayHelper.setEnabled(true); + 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(); } -// -// @Get("/displayNumber/{value}") -// public void displayValue(String value) { -// fourDigitSevenSegmentDisplayHelper.print(value); -// } -// -// @Get("/disable") -// public void disable() { -// fourDigitSevenSegmentDisplayHelper.setEnabled(false); -// } } //end::ex[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 8de9f15c..03d3ba31 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -5,22 +5,72 @@ import com.pi4j.io.i2c.I2CConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.time.LocalTime; -import java.util.Map; - -import java.util.Arrays; +import com.pi4j.crowpi.components.SevenSegmentComponent; public class FourDigitSevenSegmentDisplayHelper { private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); - - private final I2C i2c; + private final SevenSegmentComponent display; //tag::const[] public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jContext) //end::const[] { - this.i2c = pi4jContext.create(i2cConfig); + this.display = new SevenSegmentComponent(pi4jContext, i2cConfig.getBus(), i2cConfig.getDevice()); + } + + public void setEnabled(boolean enabled) { + display.setEnabled(enabled); + } + + public void displayValue(String value) { + /* Validate value. Must satisfy following properties: + - No more than 4 non-decimal point characters long + - No consecutive decimal points + - If there are 4 non-decimal point characters, then decimal points must not appear on the ends + - Non-decimal point characters must be digits 0 to 1, letters A to F (case-insensitive), -, or space + */ + + // 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 = "1234567890ABCDEF- "; + for (char character : noDecimals.toCharArray()) { + if (valid.indexOf(character) == -1) { + log.error("Each display value digit must be numeric, a letter A to F, a hyphen, or a space"); + return; + } + } + + display.print(value); + display.refresh(); + } + + public void clear() { + for (int i = 0; i < 4; i++) { + display.clear(); + display.setDecimalPoint(i, false); + } + display.setColon(false); + display.refresh(); } From 114e6eecad2219f3169aa553b60ec0aad49391a6 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 9 Nov 2024 19:46:46 -0600 Subject: [PATCH 26/33] Remove unused import --- .../outputdevices/FourDigitSevenSegmentDisplayHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 03d3ba31..985347e2 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -1,7 +1,6 @@ package com.opensourcewithslu.outputdevices; import com.pi4j.context.Context; -import com.pi4j.io.i2c.I2C; import com.pi4j.io.i2c.I2CConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 0e8afd73264c2b6f03095829c62a5e49c2f824d6 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 10 Nov 2024 15:21:47 -0600 Subject: [PATCH 27/33] Added test and documentation comments to the helper. Added displayValue field and getter. --- .../FourDigitSevenSegmentDisplayHelper.java | 72 ++++- ...ourDigitSevenSegmentDisplayHelperTest.java | 276 ++++++++++++++---- 2 files changed, 286 insertions(+), 62 deletions(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 985347e2..52bdf20e 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -7,9 +7,16 @@ import com.pi4j.crowpi.components.SevenSegmentComponent; public class FourDigitSevenSegmentDisplayHelper { - private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + 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[] @@ -17,18 +24,31 @@ public FourDigitSevenSegmentDisplayHelper(I2CConfig i2cConfig, Context pi4jConte this.display = new SevenSegmentComponent(pi4jContext, i2cConfig.getBus(), i2cConfig.getDevice()); } - public void setEnabled(boolean enabled) { + /** + * 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 = ""; } - public void displayValue(String value) { - /* Validate value. Must satisfy following properties: - - No more than 4 non-decimal point characters long - - No consecutive decimal points - - If there are 4 non-decimal point characters, then decimal points must not appear on the ends - - Non-decimal point characters must be digits 0 to 1, letters A to F (case-insensitive), -, or space - */ - + /** + * 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("\\.", ""); @@ -51,26 +71,52 @@ public void displayValue(String value) { } // Check: Non-decimal point characters must be digits 0 to 1, letters A to F (case-insensitive), -, or space - String valid = "1234567890ABCDEF- "; + 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, a hyphen, or a space"); + 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; } - public void clear() { + /** + * 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 index 165e1a5e..48163b5c 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -1,94 +1,272 @@ package com.opensourcewithslu.outputdevices; -/*import org.junit.jupiter.api.Test; +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.opensourcewithslu.outputdevices.SevenSegmentDisplayHelper;*/ +import com.pi4j.context.Context; +import com.pi4j.crowpi.components.SevenSegmentComponent; + +import java.lang.reflect.Field; public class FourDigitSevenSegmentDisplayHelperTest { - /*FourDigitSevenSegmentDisplayHelper displayHelper = mock(FourDigitSevenSegmentDisplayHelper.class); - SevenSegmentDisplayHelper sevenSegmentDisplayHelper = mock(SevenSegmentDisplayHelper.class); + 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 number = "12345"; - displayHelper.displayNumber(number); - verify(log).error("Number is too long"); - verify(log, never()).info("Displaying number: {}", "12345"); - verify(sevenSegmentDisplayHelper, never()).displayNumber(1); - verify(sevenSegmentDisplayHelper, never()).displayNumber(2); - verify(sevenSegmentDisplayHelper, never()).displayNumber(3); - verify(sevenSegmentDisplayHelper, never()).displayNumber(4); - verify(sevenSegmentDisplayHelper, never()).displayNumber(5); - } - - void shortNumberFails() { - String number = ""; - displayHelper.displayNumber(number); - verify(log).error("Number is too short"); - verify(log, never()).info("Displaying number: {}", ""); + 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 nonNumberFails() { - String number = "abc"; - displayHelper.displayNumber(number); - verify(log).error("Display value must be an integer"); + 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 negativeNumberFails() { - String number = "-1234"; - displayHelper.displayNumber(number); - verify(log).error("Display value must be positive"); + 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.displayNumber(number); - verify(displayHelper).displayNumber("1234"); - verify(log).info("Displaying number: {}", "1234"); + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "1234"); -// int displayed = displayHelper.getValue(); -// assertEquals(1234, displayed); + String displayed = displayHelper.getDisplayValue(); + assertEquals("1234", displayed); } @Test void displaysThreeDigitNumber() { String number = "123"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("123"); - verify(log).info("Displaying number: {}", "123"); + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "123"); -// int displayed = displayHelper.getValue(); -// assertEquals(123, displayed); + String displayed = displayHelper.getDisplayValue(); + assertEquals("123", displayed); } @Test void displaysTwoDigitNumber() { String number = "34"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("34"); - verify(log).info("Displaying number: {}", "34"); + displayHelper.displayValue(number); + verify(log).info("Displaying value: {}", "34"); -// int displayed = displayHelper.getValue(); -// assertEquals(34, displayed); + String displayed = displayHelper.getDisplayValue(); + assertEquals("34", displayed); } @Test void displaysOneDigitNumber() { String number = "4"; - displayHelper.displayNumber(number); - verify(displayHelper).displayNumber("4"); - verify(log).info("Displaying 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); + } -// int displayed = displayHelper.getValue(); -// assertEquals(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); + } } From c1285c133396e3d87474d6eff20eb936d5858432 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 11 Nov 2024 15:33:40 -0600 Subject: [PATCH 28/33] Added 4-digit 7-segment display documentation --- .../fourDigitSevenSegment.adoc | 118 ++++++++++++++++++ .../docs/asciidoc/img/four_digit_circuit.webp | Bin 0 -> 134648 bytes .../asciidoc/img/four_digit_schematic.webp | Bin 0 -> 24956 bytes 3 files changed, 118 insertions(+) create mode 100644 pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc create mode 100644 pi4micronaut-utils/src/docs/asciidoc/img/four_digit_circuit.webp create mode 100644 pi4micronaut-utils/src/docs/asciidoc/img/four_digit_schematic.webp 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..72b02e2d --- /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[] + +==== Motor + +[.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 0000000000000000000000000000000000000000..8761985a2ad4f3d03fef061d8e1a5e213a07b845 GIT binary patch literal 134648 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 Date: Mon, 11 Nov 2024 15:40:52 -0600 Subject: [PATCH 29/33] Typo --- .../components/outputComponents/fourDigitSevenSegment.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc b/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc index 72b02e2d..e4f69753 100644 --- a/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc +++ b/pi4micronaut-utils/src/docs/asciidoc/components/outputComponents/fourDigitSevenSegment.adoc @@ -8,7 +8,7 @@ ifdef::rootpath[] :imagesdir: {rootpath}{imagesdir} endif::rootpath[] -==== Motor +==== 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] From 9dd92e0a7c7762dda4ad86e885d754f00f932316 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sat, 16 Nov 2024 13:38:44 -0600 Subject: [PATCH 30/33] removed unnecessary SPI configuration, moved logic to FourDigitSevenSegmentDisplayHelper.java, made something that actually works as tested with hardware :) --- ...ourDigitSevenSegmentDisplayController.java | 77 +++-------- components/src/main/resources/application.yml | 5 - .../outputdevices/DigitHelper.java | 29 ----- .../FourDigitSevenSegmentDisplayHelper.java | 123 ++++++++++++++++++ .../outputdevices/SegmentHelper.java | 42 ------ 5 files changed, 139 insertions(+), 137 deletions(-) delete mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java create mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java delete mode 100644 pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 77e83966..df98701b 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -1,87 +1,42 @@ package com.opensourcewithslu.components.controllers; -import com.opensourcewithslu.outputdevices.DigitHelper; -import com.opensourcewithslu.outputdevices.SegmentHelper; +import com.opensourcewithslu.outputdevices.FourDigitSevenSegmentDisplayHelper; import com.pi4j.io.gpio.digital.DigitalOutput; -import com.pi4j.io.spi.SpiConfig; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import jakarta.inject.Named; @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { - private final SegmentHelper segmentHelper; - private final DigitHelper digit0; - private final DigitHelper digit1; - private final DigitHelper digit2; - private final DigitHelper digit3; + private final FourDigitSevenSegmentDisplayHelper displayHelper; - private static final int[] number = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; - private int counter = 0; - - public FourDigitSevenSegmentDisplayController(@Named("four-digit-seven-segment") SpiConfig spi, - @Named("sdi") DigitalOutput sdi, + public FourDigitSevenSegmentDisplayController(@Named("sdi") DigitalOutput sdi, @Named("rclk") DigitalOutput rclk, @Named("srclk") DigitalOutput srclk, @Named("digit-0") DigitalOutput digit0, @Named("digit-1") DigitalOutput digit1, @Named("digit-2") DigitalOutput digit2, @Named("digit-3") DigitalOutput digit3) { - this.segmentHelper = new SegmentHelper(sdi, rclk, srclk); - this.digit0 = new DigitHelper(digit0); - this.digit1 = new DigitHelper(digit1); - this.digit2 = new DigitHelper(digit2); - this.digit3 = new DigitHelper(digit3); + this.displayHelper = new FourDigitSevenSegmentDisplayHelper(sdi, rclk, srclk, digit0, digit1, digit2, digit3); } - private void pickDigit(int digit) { - digit0.digitOff(); - digit1.digitOff(); - digit2.digitOff(); - digit3.digitOff(); - - switch (digit) { - case 0: - digit0.digitOn(); - break; - case 1: - digit1.digitOn(); - break; - case 2: - digit2.digitOn(); - break; - case 3: - digit3.digitOn(); - break; - } + @Get("/enable") + public void enable() { + displayHelper.enable(); } - private void hc595_shift(int data) { - segmentHelper.shiftOut(data); + @Get("/disable") + public void disable() { + displayHelper.disable(); } - private void clearDisplay() { - segmentHelper.clear(); + @Get("/display/{value}") + public void displayValue(String value) { + displayHelper.displayValue(value); } - @Get("/test") - public void test() { - while (true) { - clearDisplay(); - pickDigit(0); - hc595_shift(number[counter % 10]); - - clearDisplay(); - pickDigit(1); - hc595_shift(number[counter % 100 / 10]); - - clearDisplay(); - pickDigit(2); - hc595_shift(number[counter % 1000 / 100]); - - clearDisplay(); - pickDigit(3); - hc595_shift(number[counter % 10000 / 1000]); - } + @Get("/clear") + public void clearDisplay() { + displayHelper.clear(); } } diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index b952435d..5fabfe03 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -10,11 +10,6 @@ pi4j: address: 8 # <3> baud: 500000 # <4> reset-pin: 25 # <5> - four-digit-seven-segment: - name: Four Digit Seven Segment Display - address: 0 - baud: 1000000 - reset-pin: 25 # end::spi[] # tag::pwm[] diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java deleted file mode 100644 index 40a45bd8..00000000 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/DigitHelper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.opensourcewithslu.outputdevices; - -import com.pi4j.io.gpio.digital.DigitalOutput; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DigitHelper { - private static final Logger log = LoggerFactory.getLogger(DigitHelper.class); - - private final DigitalOutput digitOutput; - - public DigitHelper(DigitalOutput digitOutput) { - this.digitOutput = digitOutput; - } - - public void digitOn() { - if (digitOutput.isLow()) { - log.info("Turning on Digit"); - digitOutput.high(); - } - } - - public void digitOff() { - if (digitOutput.isHigh()) { - log.info("Turning off Digit"); - digitOutput.low(); - } - } -} 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..8d7f8509 --- /dev/null +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -0,0 +1,123 @@ +package com.opensourcewithslu.outputdevices; + +import com.pi4j.io.gpio.digital.DigitalOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FourDigitSevenSegmentDisplayHelper { + private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + + private final DigitalOutput sdi; + private final DigitalOutput rclk; + private final DigitalOutput srclk; + private final DigitalOutput digit0; + private final DigitalOutput digit1; + private final DigitalOutput digit2; + private final DigitalOutput digit3; + + private boolean enabled = false; + private int[] numbers = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; + private String displayValue; + + public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3) { + this.sdi = sdi; + this.rclk = rclk; + this.srclk = srclk; + this.digit0 = digit0; + this.digit1 = digit1; + this.digit2 = digit2; + this.digit3 = digit3; + + this.displayValue = ""; + } + + public void shiftOut(int data, boolean decimalPoint) { + try { + for (int i = 0; i < 8; i++) { + if (i == 0 && decimalPoint) { + sdi.high(); + } else if ((data & (1 << (7 - i))) != 0) { + sdi.high(); + } else { + sdi.low(); + } +// sdi.setState(0x80 & (data << i)); + srclk.high(); + srclk.low(); + } + rclk.high(); + rclk.low(); + } catch (Exception e) { + log.error("Error shifting out data", e); + } + } + + public void clear() { + displayValue = ""; + } + + public void setDigit(int digit, int value, boolean decimalPoint) { + digit0.low(); + digit1.low(); + digit2.low(); + digit3.low(); + + switch (digit) { + case 0: + digit0.high(); + break; + case 1: + digit1.high(); + break; + case 2: + digit2.high(); + break; + case 3: + digit3.high(); + break; + } + + try { + shiftOut(numbers[value], decimalPoint); + } catch (Exception e) { + log.error(String.format("Error shifting out value %d to digit %d", value, digit), e); + } + } + + public void enable() { + this.enabled = true; + this.startDisplayThread(); + } + + public void disable() { + this.clear(); + this.enabled = false; + } + + public void displayValue(String value) { + this.displayValue = value; + log.info("Displaying value: " + displayValue); + } + + public void startDisplayThread() { + Thread displayThread = new Thread(() -> { + while (enabled) { + for (int i = 0; i < 4; i++) { + if (i < displayValue.length()) { + int value = Character.getNumericValue(displayValue.charAt(i)); + setDigit(i, value, true); + } else { + setDigit(i, 0, false); + } + try { + Thread.sleep(5); // Adjust the delay as needed + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Display thread interrupted", e); + } + } + } + }); + displayThread.start(); + } +} diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java deleted file mode 100644 index 47e2b8ed..00000000 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/SegmentHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.opensourcewithslu.outputdevices; - -import com.pi4j.io.gpio.digital.DigitalOutput; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class SegmentHelper { - private static final Logger log = LoggerFactory.getLogger(SegmentHelper.class); - - private final DigitalOutput sdi; - private final DigitalOutput rclk; - private final DigitalOutput srclk; - - public SegmentHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk) { - this.sdi = sdi; - this.rclk = rclk; - this.srclk = srclk; - } - - public void shiftOut(int data) { - try { - for (int i = 0; i < 8; i++) { - srclk.low(); - if ((data & (1 << (7 - i))) != 0) { - sdi.high(); - } else { - sdi.low(); - } - srclk.high(); - } - rclk.high(); - rclk.low(); - log.info("Shifted out data: " + Integer.toHexString(data)); - } catch (Exception e) { - log.error("Error shifting out data", e); - } - } - - public void clear() { - shiftOut(0x00); - } -} From 93bd8952b3b4189fe116ca88d202546a1a363ae8 Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 17 Nov 2024 19:06:38 -0600 Subject: [PATCH 31/33] Simplified decimal point, moved error handling, support more characters on display --- .../FourDigitSevenSegmentDisplayHelper.java | 83 ++++++++++++++----- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index 8d7f8509..dfe13fdf 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -4,6 +4,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; + public class FourDigitSevenSegmentDisplayHelper { private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); @@ -16,7 +18,34 @@ public class FourDigitSevenSegmentDisplayHelper { private final DigitalOutput digit3; private boolean enabled = false; - private int[] numbers = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90}; + private final int decimalPoint = 0x7f; + + /** + * Mapping of characters to their respective byte representation. + * Each byte is a bitset where each bit specifies if a specific segment should be disabled (1) or enabled (0). + * Note carefully that the bits are inverted, so 0 means enabled and 1 means disabled. + */ + protected static final Map CHAR_BITSETS = Map.ofEntries( + Map.entry(' ', 0xff), + Map.entry('-', 0x02), + Map.entry('0', 0xc0), + Map.entry('1', 0xf9), + Map.entry('2', 0xa4), + Map.entry('3', 0xb0), + Map.entry('4', 0x99), + Map.entry('5', 0x92), + Map.entry('6', 0x82), + Map.entry('7', 0xf8), + Map.entry('8', 0x80), + Map.entry('9', 0x90), + Map.entry('A', 0x88), + Map.entry('B', 0x83), + Map.entry('C', 0xc6), + Map.entry('D', 0xA1), + Map.entry('E', 0x86), + Map.entry('F', 0x8e) + ); + private String displayValue; public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3) { @@ -31,32 +60,31 @@ public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, this.displayValue = ""; } - public void shiftOut(int data, boolean decimalPoint) { - try { - for (int i = 0; i < 8; i++) { - if (i == 0 && decimalPoint) { - sdi.high(); - } else if ((data & (1 << (7 - i))) != 0) { - sdi.high(); - } else { - sdi.low(); - } -// sdi.setState(0x80 & (data << i)); - srclk.high(); - srclk.low(); + public void shiftOut(Integer data, boolean decimalPointEnabled) { + int value; + for (int i = 0; i < 8; i++) { // Loop through each bit in the byte, one for each of the 7 segment and the decimal point + if (decimalPointEnabled) { + value = (data & (1 << (7 - i)) & this.decimalPoint); + } else { + value = (data & (1 << (7 - i)) | ~this.decimalPoint); } - rclk.high(); - rclk.low(); - } catch (Exception e) { - log.error("Error shifting out data", e); + if (value != 0) { + sdi.high(); // Disables segment + } else { + sdi.low(); // Enables segment + } + srclk.high(); + srclk.low(); } + rclk.high(); + rclk.low(); } public void clear() { displayValue = ""; } - public void setDigit(int digit, int value, boolean decimalPoint) { + public void setDigit(int digit, char c, boolean decimalPoint) { digit0.low(); digit1.low(); digit2.low(); @@ -77,10 +105,19 @@ public void setDigit(int digit, int value, boolean decimalPoint) { break; } + // Lookup byte value for given character + final var value = CHAR_BITSETS.get(Character.toUpperCase(c)); + if (value == null) { + throw new IllegalArgumentException("Character is not supported by seven-segment display"); + } + +// log.info("previous value: " + numbers[Character.getNumericValue(c)] + " and now trying to do " + value); + try { - shiftOut(numbers[value], decimalPoint); +// shiftOut(numbers[Character.getNumericValue(c)], decimalPoint); + shiftOut(value, decimalPoint); } catch (Exception e) { - log.error(String.format("Error shifting out value %d to digit %d", value, digit), e); + log.error(String.format("Error shifting out value %d to digit %d:", value, digit), e); } } @@ -104,10 +141,10 @@ public void startDisplayThread() { while (enabled) { for (int i = 0; i < 4; i++) { if (i < displayValue.length()) { - int value = Character.getNumericValue(displayValue.charAt(i)); + char value = displayValue.charAt(i); setDigit(i, value, true); } else { - setDigit(i, 0, false); + setDigit(i, '0', false); } try { Thread.sleep(5); // Adjust the delay as needed From 973de470da549e68bff830c7af75c221c37fc15d Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Sun, 17 Nov 2024 23:08:32 -0600 Subject: [PATCH 32/33] Add setLog and getDisplayValue, add value validation, add doc comments, update method name --- ...ourDigitSevenSegmentDisplayController.java | 2 +- .../FourDigitSevenSegmentDisplayHelper.java | 122 ++++++++++++++++-- 2 files changed, 109 insertions(+), 15 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index df98701b..5ca8eb6a 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -30,7 +30,7 @@ public void disable() { displayHelper.disable(); } - @Get("/display/{value}") + @Get("/displayValue/{value}") public void displayValue(String value) { displayHelper.displayValue(value); } diff --git a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java index dfe13fdf..3d8ca8b7 100644 --- a/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java +++ b/pi4micronaut-utils/src/main/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelper.java @@ -7,7 +7,7 @@ import java.util.Map; public class FourDigitSevenSegmentDisplayHelper { - private static final Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); + private static Logger log = LoggerFactory.getLogger(FourDigitSevenSegmentDisplayHelper.class); private final DigitalOutput sdi; private final DigitalOutput rclk; @@ -17,6 +17,7 @@ public class FourDigitSevenSegmentDisplayHelper { private final DigitalOutput digit2; private final DigitalOutput digit3; + private String displayValue; private boolean enabled = false; private final int decimalPoint = 0x7f; @@ -46,9 +47,21 @@ public class FourDigitSevenSegmentDisplayHelper { Map.entry('F', 0x8e) ); - private String displayValue; - - public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3) { + /** + * Constructor for the FourDigitSevenSegmentDisplayHelper class. + * + * @param sdi Serial data input + * @param rclk Register clock + * @param srclk Shift register clock + * @param digit0 The first digit + * @param digit1 The second digit + * @param digit2 The third digit + * @param digit3 The fourth digit + */ + //tag::const[] + public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, DigitalOutput srclk, DigitalOutput digit0, DigitalOutput digit1, DigitalOutput digit2, DigitalOutput digit3) + //end::const[] + { this.sdi = sdi; this.rclk = rclk; this.srclk = srclk; @@ -60,7 +73,7 @@ public FourDigitSevenSegmentDisplayHelper(DigitalOutput sdi, DigitalOutput rclk, this.displayValue = ""; } - public void shiftOut(Integer data, boolean decimalPointEnabled) { + private void shiftOut(Integer data, boolean decimalPointEnabled) { int value; for (int i = 0; i < 8; i++) { // Loop through each bit in the byte, one for each of the 7 segment and the decimal point if (decimalPointEnabled) { @@ -80,7 +93,13 @@ public void shiftOut(Integer data, boolean decimalPointEnabled) { rclk.low(); } - public void clear() { + /** + * Clears the display. + */ + //tag::method[] + public void clear() + //end::method[] + { displayValue = ""; } @@ -111,32 +130,83 @@ public void setDigit(int digit, char c, boolean decimalPoint) { throw new IllegalArgumentException("Character is not supported by seven-segment display"); } -// log.info("previous value: " + numbers[Character.getNumericValue(c)] + " and now trying to do " + value); - try { -// shiftOut(numbers[Character.getNumericValue(c)], decimalPoint); shiftOut(value, decimalPoint); } catch (Exception e) { log.error(String.format("Error shifting out value %d to digit %d:", value, digit), e); } } - public void enable() { + /** + * Enables the display. + */ + //tag::method[] + public void enable() + //end::method[] + { this.enabled = true; this.startDisplayThread(); } - public void disable() { + /** + * Disables the display. + */ + //tag::method[] + public void disable() + //end::method[] + { this.clear(); this.enabled = false; } - public void displayValue(String value) { + /** + * 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; + } + } + + value = value.toUpperCase(); this.displayValue = value; - log.info("Displaying value: " + displayValue); + log.info("Displaying value: {}", value); } - public void startDisplayThread() { + private void startDisplayThread() { Thread displayThread = new Thread(() -> { while (enabled) { for (int i = 0; i < 4; i++) { @@ -157,4 +227,28 @@ public void startDisplayThread() { }); displayThread.start(); } + + /** + * Sets the logger object. + * + * @param log Logger object to set the logger to. + */ + //tag::method[] + public void setLog(Logger log) + //end::method[] + { + FourDigitSevenSegmentDisplayHelper.log = log; + } + + /** + * Gets the display value. + * + * @return The display value + */ + //tag::method[] + public String getDisplayValue() + //end::method[] + { + return displayValue; + } } From a68bb7aae87e513ca11d471c07ba47cd97f1531b Mon Sep 17 00:00:00 2001 From: Leandru Martin Date: Mon, 18 Nov 2024 00:03:06 -0600 Subject: [PATCH 33/33] unnecessary application.yml config, fixed test, undid accidental change a previous commit had done to the servo motor config --- ...ourDigitSevenSegmentDisplayController.java | 2 ++ components/src/main/resources/application.yml | 6 +--- ...ourDigitSevenSegmentDisplayHelperTest.java | 28 ++++++++----------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java index 5ca8eb6a..14ec7c79 100644 --- a/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java +++ b/components/src/main/java/com/opensourcewithslu/components/controllers/FourDigitSevenSegmentDisplayController.java @@ -6,6 +6,7 @@ import io.micronaut.http.annotation.Get; import jakarta.inject.Named; +//tag::ex[] @Controller("/four-digit-seven-segment") public class FourDigitSevenSegmentDisplayController { private final FourDigitSevenSegmentDisplayHelper displayHelper; @@ -40,3 +41,4 @@ public void clearDisplay() { displayHelper.clear(); } } +//end::ex[] diff --git a/components/src/main/resources/application.yml b/components/src/main/resources/application.yml index 2049a3f5..3de3df04 100644 --- a/components/src/main/resources/application.yml +++ b/components/src/main/resources/application.yml @@ -35,7 +35,7 @@ pi4j: shutdown: 0 servo-motor: name: Servo Motor - address: 17 + address: 18 pwmType: SOFTWARE provider: pigpio-pwm initial: 0 @@ -62,10 +62,6 @@ 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/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java index 48163b5c..ac58f40a 100644 --- a/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java +++ b/pi4micronaut-utils/src/test/java/com/opensourcewithslu/outputdevices/FourDigitSevenSegmentDisplayHelperTest.java @@ -1,6 +1,7 @@ package com.opensourcewithslu.outputdevices; import com.pi4j.context.ContextProperties; +import com.pi4j.io.gpio.digital.DigitalOutput; import com.pi4j.io.i2c.I2CConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,14 +17,17 @@ 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); + DigitalOutput sdi = mock(DigitalOutput.class); + DigitalOutput rclk = mock(DigitalOutput.class); + DigitalOutput srclk = mock(DigitalOutput.class); + DigitalOutput digit0 = mock(DigitalOutput.class); + DigitalOutput digit1 = mock(DigitalOutput.class); + DigitalOutput digit2 = mock(DigitalOutput.class); + DigitalOutput digit3 = mock(DigitalOutput.class); + FourDigitSevenSegmentDisplayHelper displayHelper = new FourDigitSevenSegmentDisplayHelper(sdi, rclk, srclk, digit0, digit1, digit2, digit3); Logger log = mock(Logger.class); - @BeforeEach + /*@BeforeEach void setUp() throws Exception { // Mock the Context to return a non-null ContextProperties when(pi4jContext.properties()).thenReturn(contextProperties); @@ -34,7 +38,7 @@ void setUp() throws Exception { Field displayField = FourDigitSevenSegmentDisplayHelper.class.getDeclaredField("display"); displayField.setAccessible(true); displayField.set(displayHelper, displayComponent); - } + }*/ @BeforeEach public void openMocks() { @@ -47,7 +51,6 @@ void longNumberFails() { 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 @@ -56,7 +59,6 @@ void consecutiveDecimalPointsFails() { 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 @@ -65,7 +67,6 @@ void decimalPointOnLeftEndFails() { 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 @@ -74,7 +75,6 @@ void decimalPointOnRightEndFails() { 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 @@ -83,7 +83,6 @@ void decimalPointsOnBothEndsFails() { 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 @@ -92,7 +91,6 @@ void invalidCharacterFails() { 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 @@ -261,10 +259,6 @@ void clearDisplay() { 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);