From f900d1254fd8ccfe73f832972a19790d1c494589 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:24 -0400 Subject: [PATCH 01/15] Add support for LL-style status LEDs --- .../files/Limelight2+/hardwareConfig.json | 3 + .../files/Limelight2/hardwareConfig.json | 3 + docs/source/docs/hardware/customhardware.md | 17 ++-- .../common/configuration/HardwareConfig.java | 36 +++++--- .../common/hardware/HardwareManager.java | 14 +-- .../hardware/statusLED/LLStatusLED.java | 91 +++++++++++++++++++ .../RGBStatusLED.java} | 9 +- .../common/hardware/statusLED/StatusLED.java | 45 +++++++++ .../hardware/statusLED/StatusLEDType.java | 23 +++++ 9 files changed, 212 insertions(+), 29 deletions(-) create mode 100644 photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java rename photon-core/src/main/java/org/photonvision/common/hardware/{StatusLED.java => statusLED/RGBStatusLED.java} (92%) create mode 100644 photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java create mode 100644 photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json index e43b1e7940..27130015de 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json @@ -3,5 +3,8 @@ "ledPins" : [ 13, 18 ], "ledsCanDim" : true, "ledPWMFrequency" : 1000, + "statusLEDType": "LL", + "statusLEDPins": [ 5, 4 ], + "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 } diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json index 33afd3cf4f..f2e8f9b8e6 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json @@ -2,5 +2,8 @@ "deviceName" : "Limelight 2", "ledPins" : [ 17, 18 ], "ledsCanDim" : false, + "statusLEDType": "LL", + "statusLEDPins": [ 5, 4 ], + "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 } diff --git a/docs/source/docs/hardware/customhardware.md b/docs/source/docs/hardware/customhardware.md index b18c54e5ef..717a5d7dc8 100644 --- a/docs/source/docs/hardware/customhardware.md +++ b/docs/source/docs/hardware/customhardware.md @@ -19,14 +19,16 @@ When running on Linux, PhotonVision can use [diozero](https://www.diozero.com) t "ledsCanDim" : true, "ledBrightnessRange" : [ 0, 100 ], "ledPWMFrequency" : 0, - "statusRGBPins" : [ ], - "statusRGBActiveHigh" : false, + "statusLEDType": "RGB", + "statusLEDPins" : [ ], + "statusLEDActiveHigh" : false, } ``` -:::{note} -No hardware boards with status RGB LED pins or non-dimming LED's have been tested yet. Please reach out to the development team if these features are desired, they can assist with configuration and testing. -::: +There are currently two types of status LEDs supported: + +* RGB (default): A singular LED mixing separate red, green, and blue inputs +* LL: A pair of independent Green and yellow LEDs ### GPIO Pinout @@ -134,8 +136,9 @@ Here is a complete example `hardwareConfig.json`: "ledsCanDim" : true, "ledBrightnessRange" : [ 0, 100 ], "ledPWMFrequency" : 0, - "statusRGBPins" : [ ], - "statusRGBActiveHigh" : false, + "statusLEDType": "RGB", + "statusLEDPins" : [ ], + "statusLEDActiveHigh" : false, "getGPIOCommand" : "getGPIO {p}", "setGPIOCommand" : "setGPIO {p} {s}", "setPWMCommand" : "setPWM {p} {v}", diff --git a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java index 387ba6bf3a..23d875bfa3 100644 --- a/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java +++ b/photon-core/src/main/java/org/photonvision/common/configuration/HardwareConfig.java @@ -17,8 +17,10 @@ package org.photonvision.common.configuration; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.ArrayList; +import org.photonvision.common.hardware.statusLED.StatusLEDType; @JsonIgnoreProperties(ignoreUnknown = true) public class HardwareConfig { @@ -29,8 +31,13 @@ public class HardwareConfig { public final boolean ledsCanDim; public final ArrayList ledBrightnessRange; public final int ledPWMFrequency; - public final ArrayList statusRGBPins; - public final boolean statusRGBActiveHigh; + public final StatusLEDType statusLEDType; + + @JsonAlias("statusRGBPins") + public final ArrayList statusLEDPins; + + @JsonAlias("statusRGBActiveHigh") + public final boolean statusLEDActiveHigh; // Custom GPIO public final String getGPIOCommand; @@ -49,8 +56,9 @@ public HardwareConfig( boolean ledsCanDim, ArrayList ledBrightnessRange, int ledPwmFrequency, - ArrayList statusRGBPins, - boolean statusRGBActiveHigh, + StatusLEDType statusLEDType, + ArrayList statusLEDPins, + boolean statusLEDActiveHigh, String getGPIOCommand, String setGPIOCommand, String setPWMCommand, @@ -63,8 +71,9 @@ public HardwareConfig( this.ledsCanDim = ledsCanDim; this.ledBrightnessRange = ledBrightnessRange; this.ledPWMFrequency = ledPwmFrequency; - this.statusRGBPins = statusRGBPins; - this.statusRGBActiveHigh = statusRGBActiveHigh; + this.statusLEDType = statusLEDType; + this.statusLEDPins = statusLEDPins; + this.statusLEDActiveHigh = statusLEDActiveHigh; this.getGPIOCommand = getGPIOCommand; this.setGPIOCommand = setGPIOCommand; this.setPWMCommand = setPWMCommand; @@ -80,8 +89,9 @@ public HardwareConfig() { ledsCanDim = false; ledBrightnessRange = new ArrayList<>(); ledPWMFrequency = 0; - statusRGBPins = new ArrayList<>(); - statusRGBActiveHigh = false; + statusLEDType = StatusLEDType.RGB; + statusLEDPins = new ArrayList<>(); + statusLEDActiveHigh = false; getGPIOCommand = ""; setGPIOCommand = ""; setPWMCommand = ""; @@ -121,10 +131,12 @@ public String toString() { + ledBrightnessRange + ", ledPWMFrequency=" + ledPWMFrequency - + ", statusRGBPins=" - + statusRGBPins - + ", statusRGBActiveHigh" - + statusRGBActiveHigh + + ", statusLEDType=" + + statusLEDType + + ", statusLEDPins=" + + statusLEDPins + + ", statusLEDActiveHigh" + + statusLEDActiveHigh + ", getGPIOCommand=" + getGPIOCommand + ", setGPIOCommand=" diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java index 98e223c124..50e699c59c 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -33,6 +33,7 @@ import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.hardware.gpio.CustomAdapter; import org.photonvision.common.hardware.gpio.CustomDeviceFactory; +import org.photonvision.common.hardware.statusLED.StatusLED; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.ShellExec; @@ -102,12 +103,11 @@ public NativeDeviceFactoryInterface get() { }; statusLED = - hardwareConfig.statusRGBPins.size() == 3 - ? new StatusLED( - lazyDeviceFactory.get(), - hardwareConfig.statusRGBPins, - hardwareConfig.statusRGBActiveHigh) - : null; + StatusLED.ofType( + hardwareConfig.statusLEDType, + lazyDeviceFactory.get(), + hardwareConfig.statusLEDPins, + hardwareConfig.statusLEDActiveHigh); var hasBrightnessRange = hardwareConfig.ledBrightnessRange.size() == 2; visionLED = @@ -161,7 +161,7 @@ public static NativeDeviceFactoryInterface configureCustomGPIO(HardwareConfig ha pinInfo.addGpioPinInfo(pin, pin, List.of(DeviceMode.DIGITAL_OUTPUT)); } } - for (int pin : hardwareConfig.statusRGBPins) { + for (int pin : hardwareConfig.statusLEDPins) { pinInfo.addGpioPinInfo(pin, pin, List.of(DeviceMode.DIGITAL_OUTPUT)); } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java new file mode 100644 index 0000000000..9358a3d700 --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.statusLED; + +import com.diozero.devices.LED; +import com.diozero.internal.spi.NativeDeviceFactoryInterface; +import java.util.List; +import org.photonvision.common.hardware.PhotonStatus; +import org.photonvision.common.util.TimedTaskManager; + +/** A pair of green and yellow LEDs, as used on the Limelight cameras */ +public class LLStatusLED implements StatusLED { + public final LED greenLED; + public final LED yellowLED; + protected int blinkCounter; + + protected PhotonStatus status = PhotonStatus.GENERIC_ERROR; + + public LLStatusLED( + NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { + // fill unassigned pins with -1 to disable + if (statusLedPins.size() != 3) { + for (int i = 0; i < 3 - statusLedPins.size(); i++) { + statusLedPins.add(-1); + } + } + + // Outputs are active-low for a common-anode RGB LED + greenLED = new LED(deviceFactory, statusLedPins.get(0), activeHigh, false); + yellowLED = new LED(deviceFactory, statusLedPins.get(1), activeHigh, false); + + TimedTaskManager.getInstance().addTask("StatusLEDUpdate", this::updateLED, 75); + } + + protected void setLEDs(boolean green, boolean yellow) { + greenLED.setOn(green); + yellowLED.setOn(yellow); + } + + @Override + public void setStatus(PhotonStatus status) { + this.status = status; + } + + protected void updateLED() { + boolean slowBlink = blinkCounter > 1; + boolean fastBlink = (blinkCounter % 2) > 0; + + switch (status) { + case NT_CONNECTED_TARGETS_VISIBLE -> + // Green fast, yellow on + setLEDs(fastBlink, true); + case NT_CONNECTED_TARGETS_MISSING -> + // Green slow, yellow on + setLEDs(slowBlink, true); + case NT_DISCONNECTED_TARGETS_VISIBLE -> + // Green fast, yellow slow + setLEDs(fastBlink, slowBlink); + case NT_DISCONNECTED_TARGETS_MISSING -> + // Green slow, yellow slow + setLEDs(slowBlink, slowBlink); + case GENERIC_ERROR -> + // No lights + setLEDs(false, false); + } + + blinkCounter++; + blinkCounter %= 6; + } + + @Override + public void close() throws Exception { + greenLED.close(); + yellowLED.close(); + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java similarity index 92% rename from photon-core/src/main/java/org/photonvision/common/hardware/StatusLED.java rename to photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java index 81a916ae9b..a5c04f9939 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java @@ -15,14 +15,16 @@ * along with this program. If not, see . */ -package org.photonvision.common.hardware; +package org.photonvision.common.hardware.statusLED; import com.diozero.devices.LED; import com.diozero.internal.spi.NativeDeviceFactoryInterface; import java.util.List; +import org.photonvision.common.hardware.PhotonStatus; import org.photonvision.common.util.TimedTaskManager; -public class StatusLED implements AutoCloseable { +/** Basic RGB LED with individual control over each pin */ +public class RGBStatusLED implements StatusLED { public final LED redLED; public final LED greenLED; public final LED blueLED; @@ -30,7 +32,7 @@ public class StatusLED implements AutoCloseable { protected PhotonStatus status = PhotonStatus.GENERIC_ERROR; - public StatusLED( + public RGBStatusLED( NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { // fill unassigned pins with -1 to disable if (statusLedPins.size() != 3) { @@ -53,6 +55,7 @@ protected void setRGB(boolean r, boolean g, boolean b) { blueLED.setOn(b); } + @Override public void setStatus(PhotonStatus status) { this.status = status; } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java new file mode 100644 index 0000000000..c3aede1bc8 --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.statusLED; + +import com.diozero.internal.spi.NativeDeviceFactoryInterface; +import java.util.List; +import org.jetbrains.annotations.Nullable; +import org.photonvision.common.hardware.PhotonStatus; + +public interface StatusLED extends AutoCloseable { + public void setStatus(PhotonStatus status); + + @Nullable + static StatusLED ofType( + StatusLEDType type, + NativeDeviceFactoryInterface deviceFactory, + List statusLedPins, + boolean activeHigh) { + return switch (type) { + case RGB -> + statusLedPins.size() == 3 + ? new RGBStatusLED(deviceFactory, statusLedPins, activeHigh) + : null; + case LL -> + statusLedPins.size() == 2 + ? new LLStatusLED(deviceFactory, statusLedPins, activeHigh) + : null; + }; + } +} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java new file mode 100644 index 0000000000..bc15f10fa6 --- /dev/null +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) Photon Vision. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.photonvision.common.hardware.statusLED; + +public enum StatusLEDType { + RGB, + LL; +} From 77bfd91602f441a128866edc98b3a33212addd4a Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:38 -0400 Subject: [PATCH 02/15] Rename LL led type to GY/Green-Yellow --- .../sw_install/files/Limelight2+/hardwareConfig.json | 2 +- .../sw_install/files/Limelight2/hardwareConfig.json | 2 +- docs/source/docs/hardware/customhardware.md | 4 ++-- .../statusLED/{LLStatusLED.java => GYStatusLED.java} | 4 ++-- .../photonvision/common/hardware/statusLED/StatusLED.java | 4 ++-- .../common/hardware/statusLED/StatusLEDType.java | 5 ++++- 6 files changed, 12 insertions(+), 9 deletions(-) rename photon-core/src/main/java/org/photonvision/common/hardware/statusLED/{LLStatusLED.java => GYStatusLED.java} (97%) diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json index 27130015de..6d0b568756 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json @@ -3,7 +3,7 @@ "ledPins" : [ 13, 18 ], "ledsCanDim" : true, "ledPWMFrequency" : 1000, - "statusLEDType": "LL", + "statusLEDType": "Green-Yellow", "statusLEDPins": [ 5, 4 ], "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json index f2e8f9b8e6..62f40f888e 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json @@ -2,7 +2,7 @@ "deviceName" : "Limelight 2", "ledPins" : [ 17, 18 ], "ledsCanDim" : false, - "statusLEDType": "LL", + "statusLEDType": "Green-Yellow", "statusLEDPins": [ 5, 4 ], "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 diff --git a/docs/source/docs/hardware/customhardware.md b/docs/source/docs/hardware/customhardware.md index 717a5d7dc8..36fea5cd97 100644 --- a/docs/source/docs/hardware/customhardware.md +++ b/docs/source/docs/hardware/customhardware.md @@ -27,8 +27,8 @@ When running on Linux, PhotonVision can use [diozero](https://www.diozero.com) t There are currently two types of status LEDs supported: -* RGB (default): A singular LED mixing separate red, green, and blue inputs -* LL: A pair of independent Green and yellow LEDs +* `RGB` (default): A singular LED mixing separate red, green, and blue inputs +* `GY`/`Green-Yellow`: A pair of independent green and yellow LEDs ### GPIO Pinout diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java similarity index 97% rename from photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java rename to photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java index 9358a3d700..6af2e43e86 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/LLStatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java @@ -24,14 +24,14 @@ import org.photonvision.common.util.TimedTaskManager; /** A pair of green and yellow LEDs, as used on the Limelight cameras */ -public class LLStatusLED implements StatusLED { +public class GYStatusLED implements StatusLED { public final LED greenLED; public final LED yellowLED; protected int blinkCounter; protected PhotonStatus status = PhotonStatus.GENERIC_ERROR; - public LLStatusLED( + public GYStatusLED( NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { // fill unassigned pins with -1 to disable if (statusLedPins.size() != 3) { diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java index c3aede1bc8..c91fa668a3 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -36,9 +36,9 @@ static StatusLED ofType( statusLedPins.size() == 3 ? new RGBStatusLED(deviceFactory, statusLedPins, activeHigh) : null; - case LL -> + case GY -> statusLedPins.size() == 2 - ? new LLStatusLED(deviceFactory, statusLedPins, activeHigh) + ? new GYStatusLED(deviceFactory, statusLedPins, activeHigh) : null; }; } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java index bc15f10fa6..b217e5587d 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java @@ -17,7 +17,10 @@ package org.photonvision.common.hardware.statusLED; +import com.fasterxml.jackson.annotation.JsonAlias; + public enum StatusLEDType { RGB, - LL; + @JsonAlias("Green-Yellow") + GY; } From 1de40274637100e1de7c4f0d3dfad277d0048f5a Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 03/15] Fix Windows lazy device factory initialization --- .../org/photonvision/common/hardware/HardwareManager.java | 2 +- .../photonvision/common/hardware/statusLED/StatusLED.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java index 50e699c59c..8a45286f65 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -105,7 +105,7 @@ public NativeDeviceFactoryInterface get() { statusLED = StatusLED.ofType( hardwareConfig.statusLEDType, - lazyDeviceFactory.get(), + lazyDeviceFactory, hardwareConfig.statusLEDPins, hardwareConfig.statusLEDActiveHigh); diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java index c91fa668a3..5a356505b4 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -19,6 +19,7 @@ import com.diozero.internal.spi.NativeDeviceFactoryInterface; import java.util.List; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; import org.photonvision.common.hardware.PhotonStatus; @@ -28,17 +29,17 @@ public interface StatusLED extends AutoCloseable { @Nullable static StatusLED ofType( StatusLEDType type, - NativeDeviceFactoryInterface deviceFactory, + Supplier lazyDeviceFactory, List statusLedPins, boolean activeHigh) { return switch (type) { case RGB -> statusLedPins.size() == 3 - ? new RGBStatusLED(deviceFactory, statusLedPins, activeHigh) + ? new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) : null; case GY -> statusLedPins.size() == 2 - ? new GYStatusLED(deviceFactory, statusLedPins, activeHigh) + ? new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) : null; }; } From b18ee68093f12e50da16888cce01bbd7acae31b7 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 04/15] Add status LED documentation --- .../docs/troubleshooting/images/led.svg | 15 +++ docs/source/docs/troubleshooting/index.md | 1 + .../docs/troubleshooting/status-leds.md | 92 +++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 docs/source/docs/troubleshooting/images/led.svg create mode 100644 docs/source/docs/troubleshooting/status-leds.md diff --git a/docs/source/docs/troubleshooting/images/led.svg b/docs/source/docs/troubleshooting/images/led.svg new file mode 100644 index 0000000000..7c40b9fc84 --- /dev/null +++ b/docs/source/docs/troubleshooting/images/led.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/source/docs/troubleshooting/index.md b/docs/source/docs/troubleshooting/index.md index 00fe776352..64110cda0d 100644 --- a/docs/source/docs/troubleshooting/index.md +++ b/docs/source/docs/troubleshooting/index.md @@ -5,6 +5,7 @@ common-errors logging +status-leds camera-troubleshooting networking-troubleshooting unix-commands diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md new file mode 100644 index 0000000000..f025386653 --- /dev/null +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -0,0 +1,92 @@ +--- +myst: + substitutions: + led_loader: | + ```{image} images/led.svg + :height: 0 + ``` + led_green: | + ```{raw} html + + + + ``` + led_blue_solid: | + ```{raw} html + + + + + ``` + led_yellow: | + ```{raw} html + + + + ``` + led_blue: | + ```{raw} html + + + + ``` + led_red: | + ```{raw} html + + + + ``` + led_off: | + ```{raw} html + + + + ``` + led_fast_green: | + ```{raw} html + + + + + + ``` + led_fast_yellow: | + ```{raw} html + + + + + + ``` +--- + +# Status LEDs + +PhotonVision has support for multiple kinds of status LEDs. Make sure you reference the correct table for the type present on your hardware. + +## RGB LED + + Color | Flashing | Preview | Status +--------|----------|----------------------|----------------------------------------------- + Green | Yes | {{ led_green }} | Running normally, no targets visible + Blue | No | {{ led_blue_solid }} | Running normally, targets visible + Yellow | Yes | {{ led_yellow }} | NT Disconnected, no targets visible + Blue | Yes | {{ led_blue }} | NT Disconnected, targets visible + Red | Yes | {{ led_red }} | Initializing or faulted, not running + Off | No | {{ led_off }} | No power or initialization fault, not running + +## Green and Yellow LEDs + +Present on Limelight 1, 2, 2+, 3, 3G, and 3A + +Green and Yellow LED patterns may be active at the same time + + Color | Pattern | Preview | Status +--------|----------------|-------------------------------------|------------------------------------------------- + Green | Slow Flashing | {{ led_green }} {{ led_off }} | No targets visible + Green | Quick Flashing | {{ led_fast_green }} {{ led_off }} | Targets visible + Yellow | Slow Flashing | {{ led_off }} {{ led_yellow }} | NT Disconnected + Yellow | Solid | {{ led_off }} {{ led_fast_yellow }} | NT Connected + Both | Off | {{ led_off }} {{ led_off }} | No power, initializing, or faulted, not running + +{{ led_loader }} From d4c7547e738e166e8928ecf10ebc0ac69bb6eb99 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 05/15] Remove unused sphinx-tabs dependency --- docs/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 20bbd59f03..4a80bea4dd 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -36,7 +36,6 @@ sphinx-autobuild==2024.10.3 sphinx-basic-ng==1.0.0b2 sphinx-notfound-page==1.1.0 sphinx-rtd-theme==3.0.2 -sphinx-tabs==3.4.7 sphinx_design==0.6.1 sphinxcontrib-applehelp==2.0.0 sphinxcontrib-devhelp==2.0.0 From 69df68e1de6590ffb4a9e31a76add68aa59075e2 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 06/15] Add link to status LED table from custom hardware docs --- docs/source/docs/hardware/customhardware.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/docs/hardware/customhardware.md b/docs/source/docs/hardware/customhardware.md index 36fea5cd97..8e0f6449ae 100644 --- a/docs/source/docs/hardware/customhardware.md +++ b/docs/source/docs/hardware/customhardware.md @@ -30,6 +30,8 @@ There are currently two types of status LEDs supported: * `RGB` (default): A singular LED mixing separate red, green, and blue inputs * `GY`/`Green-Yellow`: A pair of independent green and yellow LEDs +For an explanation of the colors used for status LEDs, see {ref}`Status LEDs` + ### GPIO Pinout ::::{tab-set} From 8ed4d5994acef746977ed2dc589979ef9c285ab5 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 07/15] Center LED indicators --- docs/source/docs/troubleshooting/status-leds.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md index f025386653..f3b8aadc28 100644 --- a/docs/source/docs/troubleshooting/status-leds.md +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -67,7 +67,7 @@ PhotonVision has support for multiple kinds of status LEDs. Make sure you refere ## RGB LED Color | Flashing | Preview | Status ---------|----------|----------------------|----------------------------------------------- +--------|----------|:--------------------:|----------------------------------------------- Green | Yes | {{ led_green }} | Running normally, no targets visible Blue | No | {{ led_blue_solid }} | Running normally, targets visible Yellow | Yes | {{ led_yellow }} | NT Disconnected, no targets visible @@ -82,7 +82,7 @@ Present on Limelight 1, 2, 2+, 3, 3G, and 3A Green and Yellow LED patterns may be active at the same time Color | Pattern | Preview | Status ---------|----------------|-------------------------------------|------------------------------------------------- +--------|----------------|:-----------------------------------:|------------------------------------------------- Green | Slow Flashing | {{ led_green }} {{ led_off }} | No targets visible Green | Quick Flashing | {{ led_fast_green }} {{ led_off }} | Targets visible Yellow | Slow Flashing | {{ led_off }} {{ led_yellow }} | NT Disconnected From 3a548eea4169cac3a2edc803dec84d28aacec466 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 08/15] Fix LED dark mode rendering --- docs/source/docs/troubleshooting/images/led.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/docs/troubleshooting/images/led.svg b/docs/source/docs/troubleshooting/images/led.svg index 7c40b9fc84..ba34139089 100644 --- a/docs/source/docs/troubleshooting/images/led.svg +++ b/docs/source/docs/troubleshooting/images/led.svg @@ -1,4 +1,4 @@ - + From d8ccfc7ee1354efe0f86494cfd5fae6196f659d3 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 09/15] Reword LED used on message --- docs/source/docs/troubleshooting/status-leds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md index f3b8aadc28..8608f41b98 100644 --- a/docs/source/docs/troubleshooting/status-leds.md +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -77,7 +77,7 @@ PhotonVision has support for multiple kinds of status LEDs. Make sure you refere ## Green and Yellow LEDs -Present on Limelight 1, 2, 2+, 3, 3G, and 3A +Used on Limelight 1, 2, 2+, 3, 3G, and 3A Green and Yellow LED patterns may be active at the same time From 409e100c2642a4279df20162e217fea0099b4bac Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Sat, 28 Mar 2026 10:31:39 -0400 Subject: [PATCH 10/15] Fix GY yellow led documentation and update solid color LEDs to actually not blink. --- .../docs/troubleshooting/status-leds.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md index 8608f41b98..58510dc5b0 100644 --- a/docs/source/docs/troubleshooting/status-leds.md +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -11,11 +11,11 @@ myst: ``` - led_blue_solid: | + led_solid_blue: | ```{raw} html - + ``` led_yellow: | @@ -40,6 +40,7 @@ myst: ```{raw} html + ``` led_fast_green: | @@ -50,12 +51,11 @@ myst: ``` - led_fast_yellow: | + led_solid_yellow: | ```{raw} html - - + ``` --- @@ -69,7 +69,7 @@ PhotonVision has support for multiple kinds of status LEDs. Make sure you refere Color | Flashing | Preview | Status --------|----------|:--------------------:|----------------------------------------------- Green | Yes | {{ led_green }} | Running normally, no targets visible - Blue | No | {{ led_blue_solid }} | Running normally, targets visible + Blue | No | {{ led_solid_blue }} | Running normally, targets visible Yellow | Yes | {{ led_yellow }} | NT Disconnected, no targets visible Blue | Yes | {{ led_blue }} | NT Disconnected, targets visible Red | Yes | {{ led_red }} | Initializing or faulted, not running @@ -81,12 +81,12 @@ Used on Limelight 1, 2, 2+, 3, 3G, and 3A Green and Yellow LED patterns may be active at the same time - Color | Pattern | Preview | Status ---------|----------------|:-----------------------------------:|------------------------------------------------- - Green | Slow Flashing | {{ led_green }} {{ led_off }} | No targets visible - Green | Quick Flashing | {{ led_fast_green }} {{ led_off }} | Targets visible - Yellow | Slow Flashing | {{ led_off }} {{ led_yellow }} | NT Disconnected - Yellow | Solid | {{ led_off }} {{ led_fast_yellow }} | NT Connected - Both | Off | {{ led_off }} {{ led_off }} | No power, initializing, or faulted, not running + Color | Pattern | Preview | Status +--------|----------------|:------------------------------------:|------------------------------------------------- + Green | Slow Flashing | {{ led_green }} {{ led_off }} | No targets visible + Green | Quick Flashing | {{ led_fast_green }} {{ led_off }} | Targets visible + Yellow | Flashing | {{ led_off }} {{ led_yellow }} | NT Disconnected + Yellow | Solid | {{ led_off }} {{ led_solid_yellow }} | NT Connected + Both | Off | {{ led_off }} {{ led_off }} | No power, initializing, or faulted, not running {{ led_loader }} From 7ad949c99adf85f02a82323b07e4f90a01191781 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Thu, 2 Apr 2026 23:36:30 -0400 Subject: [PATCH 11/15] Rework documentation leds to use svg tag + css instead of object tag + params --- docs/source/conf.py | 2 +- .../docs/troubleshooting/images/led.svg | 23 ++-- .../docs/troubleshooting/status-leds.md | 130 +++++++++--------- 3 files changed, 74 insertions(+), 81 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3414fc7db5..887e3da799 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -181,4 +181,4 @@ def setup(app): linkcheck_auth = [(R"https://github.com/.+", token)] # MyST configuration (https://myst-parser.readthedocs.io/en/latest/configuration.html) -myst_enable_extensions = ["colon_fence", "substitution"] +myst_enable_extensions = ["colon_fence", "substitution", "attrs_inline"] diff --git a/docs/source/docs/troubleshooting/images/led.svg b/docs/source/docs/troubleshooting/images/led.svg index ba34139089..be28767808 100644 --- a/docs/source/docs/troubleshooting/images/led.svg +++ b/docs/source/docs/troubleshooting/images/led.svg @@ -1,15 +1,10 @@ - + - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md index 58510dc5b0..c1dc964227 100644 --- a/docs/source/docs/troubleshooting/status-leds.md +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -5,60 +5,58 @@ myst: ```{image} images/led.svg :height: 0 ``` - led_green: | + led: | ```{raw} html - - - - ``` - led_solid_blue: | - ```{raw} html - - - - - ``` - led_yellow: | - ```{raw} html - - - - ``` - led_blue: | - ```{raw} html - - - - ``` - led_red: | - ```{raw} html - - - - ``` - led_off: | - ```{raw} html - - - - - ``` - led_fast_green: | - ```{raw} html - - - - - - ``` - led_solid_yellow: | - ```{raw} html - - - - + + + ``` --- + + # Status LEDs @@ -66,14 +64,14 @@ PhotonVision has support for multiple kinds of status LEDs. Make sure you refere ## RGB LED - Color | Flashing | Preview | Status ---------|----------|:--------------------:|----------------------------------------------- - Green | Yes | {{ led_green }} | Running normally, no targets visible - Blue | No | {{ led_solid_blue }} | Running normally, targets visible - Yellow | Yes | {{ led_yellow }} | NT Disconnected, no targets visible - Blue | Yes | {{ led_blue }} | NT Disconnected, targets visible - Red | Yes | {{ led_red }} | Initializing or faulted, not running - Off | No | {{ led_off }} | No power or initialization fault, not running + Color | Flashing | Preview | Status +--------|----------|:-------------------------:|----------------------------------------------- + Green | Yes | [{{ led }}]{.green} | Running normally, no targets visible + Blue | No | [{{ led }}]{.solid .blue} | Running normally, targets visible + Yellow | Yes | [{{ led }}]{.yellow} | NT Disconnected, no targets visible + Blue | Yes | [{{ led }}]{.blue} | NT Disconnected, targets visible + Red | Yes | [{{ led }}]{.red} | Initializing or faulted, not running + Off | No | [{{ led }}]{.off} | No power or initialization fault, not running ## Green and Yellow LEDs @@ -81,12 +79,12 @@ Used on Limelight 1, 2, 2+, 3, 3G, and 3A Green and Yellow LED patterns may be active at the same time - Color | Pattern | Preview | Status ---------|----------------|:------------------------------------:|------------------------------------------------- - Green | Slow Flashing | {{ led_green }} {{ led_off }} | No targets visible - Green | Quick Flashing | {{ led_fast_green }} {{ led_off }} | Targets visible - Yellow | Flashing | {{ led_off }} {{ led_yellow }} | NT Disconnected - Yellow | Solid | {{ led_off }} {{ led_solid_yellow }} | NT Connected - Both | Off | {{ led_off }} {{ led_off }} | No power, initializing, or faulted, not running + Color | Pattern | Preview | Status +--------|----------------|:---------------------------------------------:|------------------------------------------------- + Green | Slow Flashing | [{{ led }}]{.green} [{{ led }}]{.off} | No targets visible + Green | Quick Flashing | [{{ led }}]{.fast .green} [{{ led }}]{.off} | Targets visible + Yellow | Flashing | [{{ led }}]{.off} [{{ led }}]{.yellow} | NT Disconnected + Yellow | Solid | [{{ led }}]{.off} [{{ led }}]{.solid .yellow} | NT Connected + Both | Off | [{{ led }}]{.off} [{{ led }}]{.off} | No power, initializing, or faulted, not running {{ led_loader }} From 64f36bc8d383d1524b084df5f254628f6a671583 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Thu, 2 Apr 2026 23:54:32 -0400 Subject: [PATCH 12/15] Change GY led error indication to alternating lights --- .../docs/troubleshooting/status-leds.md | 29 +++++++++++++------ .../hardware/statusLED/GYStatusLED.java | 9 +++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/docs/source/docs/troubleshooting/status-leds.md b/docs/source/docs/troubleshooting/status-leds.md index c1dc964227..c3e1bc23d8 100644 --- a/docs/source/docs/troubleshooting/status-leds.md +++ b/docs/source/docs/troubleshooting/status-leds.md @@ -29,17 +29,22 @@ myst: animation: led-blink 0.45s steps(1) infinite; } - @keyframes led-fast-blink { + @keyframes led-even-blink { 50% { color: var(--off-color); } } :not(.solid).fast > svg.led { - animation-name: led-fast-blink; + animation-name: led-even-blink; animation-duration: 150ms; } + :not(.solid).error > svg.led { + animation-name: led-even-blink; + animation-duration: 0.90s; + } + .green > svg.led { --on-color: limegreen; } @@ -53,6 +58,11 @@ myst: --on-color: red; } + .anti-yellow > svg.led { + --on-color: transparent; + --off-color: yellow; + } + .off > svg.led { color: var(--off-color); } @@ -79,12 +89,13 @@ Used on Limelight 1, 2, 2+, 3, 3G, and 3A Green and Yellow LED patterns may be active at the same time - Color | Pattern | Preview | Status ---------|----------------|:---------------------------------------------:|------------------------------------------------- - Green | Slow Flashing | [{{ led }}]{.green} [{{ led }}]{.off} | No targets visible - Green | Quick Flashing | [{{ led }}]{.fast .green} [{{ led }}]{.off} | Targets visible - Yellow | Flashing | [{{ led }}]{.off} [{{ led }}]{.yellow} | NT Disconnected - Yellow | Solid | [{{ led }}]{.off} [{{ led }}]{.solid .yellow} | NT Connected - Both | Off | [{{ led }}]{.off} [{{ led }}]{.off} | No power, initializing, or faulted, not running + Color | Pattern | Preview | Status +--------|----------------|:-----------------------------------------------------------:|------------------------------------------------- + Green | Slow Flashing | [{{ led }}]{.green} [{{ led }}]{.off} | No targets visible + Green | Quick Flashing | [{{ led }}]{.fast .green} [{{ led }}]{.off} | Targets visible + Yellow | Flashing | [{{ led }}]{.off} [{{ led }}]{.yellow} | NT Disconnected + Yellow | Solid | [{{ led }}]{.off} [{{ led }}]{.solid .yellow} | NT Connected + Both | Alternating | [{{ led }}]{.green .error} [{{ led }}]{.anti-yellow .error} | Initializing or faulted, not running + Both | Off | [{{ led }}]{.off} [{{ led }}]{.off} | No power or initialization fault, not running {{ led_loader }} diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java index 6af2e43e86..1c8db610a5 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java @@ -58,8 +58,9 @@ public void setStatus(PhotonStatus status) { } protected void updateLED() { - boolean slowBlink = blinkCounter > 1; + boolean slowBlink = (blinkCounter % 6) > 1; boolean fastBlink = (blinkCounter % 2) > 0; + boolean errorBlink = blinkCounter > 5; switch (status) { case NT_CONNECTED_TARGETS_VISIBLE -> @@ -75,12 +76,12 @@ protected void updateLED() { // Green slow, yellow slow setLEDs(slowBlink, slowBlink); case GENERIC_ERROR -> - // No lights - setLEDs(false, false); + // Extra slow alternating blink + setLEDs(errorBlink, !errorBlink); } blinkCounter++; - blinkCounter %= 6; + blinkCounter %= 12; } @Override From 7aca9f19874d1aee76c5755ee1c14ce9e9f35ac7 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Tue, 14 Apr 2026 19:09:11 -0400 Subject: [PATCH 13/15] Use optional instead of null for led types --- .../common/hardware/HardwareManager.java | 66 +++++++++---------- .../common/hardware/statusLED/StatusLED.java | 26 ++++---- .../vision/processes/VisionModule.java | 10 ++- .../src/main/java/org/photonvision/Main.java | 3 +- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java index 8a45286f65..783e7cb50a 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import org.photonvision.common.configuration.ConfigManager; @@ -49,18 +50,15 @@ public class HardwareManager { private final HardwareConfig hardwareConfig; private final HardwareSettings hardwareSettings; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private final StatusLED statusLED; + private final Optional statusLED; - @SuppressWarnings("FieldCanBeLocal") private final IntegerSubscriber ledModeRequest; private final IntegerPublisher ledModeState; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private final NTDataChangeListener ledModeListener; + private final Optional ledModeListener; - public final VisionLED visionLED; // May be null if no LED is specified + public final Optional visionLED; public static HardwareManager getInstance() { if (instance == null) { @@ -112,30 +110,32 @@ public NativeDeviceFactoryInterface get() { var hasBrightnessRange = hardwareConfig.ledBrightnessRange.size() == 2; visionLED = hardwareConfig.ledPins.isEmpty() - ? null - : new VisionLED( - lazyDeviceFactory.get(), - hardwareConfig.ledPins, - hardwareConfig.ledsCanDim, - hasBrightnessRange ? hardwareConfig.ledBrightnessRange.get(0) : 0, - hasBrightnessRange ? hardwareConfig.ledBrightnessRange.get(1) : 100, - hardwareConfig.ledPWMFrequency, - ledModeState::set); + ? Optional.empty() + : Optional.of( + new VisionLED( + lazyDeviceFactory.get(), + hardwareConfig.ledPins, + hardwareConfig.ledsCanDim, + hasBrightnessRange ? hardwareConfig.ledBrightnessRange.get(0) : 0, + hasBrightnessRange ? hardwareConfig.ledBrightnessRange.get(1) : 100, + hardwareConfig.ledPWMFrequency, + ledModeState::set)); ledModeListener = - visionLED == null - ? null - : new NTDataChangeListener( - NetworkTablesManager.getInstance().kRootTable.getInstance(), - ledModeRequest, - visionLED::onLedModeChange); + visionLED.map( + visionLED -> + new NTDataChangeListener( + NetworkTablesManager.getInstance().kRootTable.getInstance(), + ledModeRequest, + visionLED::onLedModeChange)); Runtime.getRuntime().addShutdownHook(new Thread(this::onJvmExit)); - if (visionLED != null) { - visionLED.setBrightness(hardwareSettings.ledBrightnessPercentage); - visionLED.blink(85, 4); // bootup blink - } + visionLED.ifPresent( + visionLED -> { + visionLED.setBrightness(hardwareSettings.ledBrightnessPercentage); + visionLED.blink(85, 4); // bootup blink + }); // Start hardware metrics thread (Disabled until implemented) // if (Platform.isLinux()) MetricsPublisher.getInstance().startTask(); @@ -171,7 +171,7 @@ public static NativeDeviceFactoryInterface configureCustomGPIO(HardwareConfig ha public void setBrightnessPercent(int percent) { if (percent != hardwareSettings.ledBrightnessPercentage) { hardwareSettings.ledBrightnessPercentage = percent; - if (visionLED != null) visionLED.setBrightness(percent); + visionLED.ifPresent(visionLED -> visionLED.setBrightness(percent)); ConfigManager.getInstance().requestSave(); logger.info("Setting led brightness to " + percent + "%"); } @@ -179,7 +179,7 @@ public void setBrightnessPercent(int percent) { private void onJvmExit() { logger.info("Shutting down LEDs..."); - if (visionLED != null) visionLED.setState(false); + visionLED.ifPresent(visionLED -> visionLED.setState(false)); ConfigManager.getInstance().onJvmExit(); } @@ -220,16 +220,16 @@ public void setNTConnected(boolean isConnected) { updateStatus(); } - public void setError(PhotonStatus status) { - if (status == null || !status.isError()) { + public void setError(Optional status) { + if (status.isEmpty() || !status.get().isError()) { updateStatus(); - } else if (statusLED != null) { - statusLED.setStatus(status); + } else { + statusLED.ifPresent(statusLED -> statusLED.setStatus(status.get())); } } private void updateStatus() { - if (statusLED == null) { + if (statusLED.isEmpty()) { return; } PhotonStatus status; @@ -247,6 +247,6 @@ private void updateStatus() { status = PhotonStatus.NT_DISCONNECTED_TARGETS_MISSING; } } - statusLED.setStatus(status); + statusLED.ifPresent(statusLED -> statusLED.setStatus(status)); } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java index 5a356505b4..21c9a5dcb3 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -19,28 +19,28 @@ import com.diozero.internal.spi.NativeDeviceFactoryInterface; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; -import org.jetbrains.annotations.Nullable; import org.photonvision.common.hardware.PhotonStatus; public interface StatusLED extends AutoCloseable { public void setStatus(PhotonStatus status); - @Nullable - static StatusLED ofType( + static Optional ofType( StatusLEDType type, Supplier lazyDeviceFactory, List statusLedPins, boolean activeHigh) { - return switch (type) { - case RGB -> - statusLedPins.size() == 3 - ? new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) - : null; - case GY -> - statusLedPins.size() == 2 - ? new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) - : null; - }; + return Optional.ofNullable( + switch (type) { + case RGB -> + statusLedPins.size() == 3 + ? new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) + : null; + case GY -> + statusLedPins.size() == 2 + ? new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) + : null; + }); } } diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java index d9afb714a1..888b659812 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -178,7 +178,10 @@ public VisionModule(PipelineManager pipelineManager, VisionSource visionSource) if (HardwareManager.getInstance().visionLED != null && this.camShouldControlLEDs()) { HardwareManager.getInstance() .visionLED - .setPipelineModeSupplier(() -> pipelineManager.getCurrentPipelineSettings().ledMode); + .ifPresent( + (visionLED) -> + visionLED.setPipelineModeSupplier( + () -> pipelineManager.getCurrentPipelineSettings().ledMode)); setVisionLEDs(pipelineManager.getCurrentPipelineSettings().ledMode); } @@ -513,8 +516,9 @@ private boolean camShouldControlLEDs() { } private void setVisionLEDs(boolean on) { - if (camShouldControlLEDs() && HardwareManager.getInstance().visionLED != null) - HardwareManager.getInstance().visionLED.setState(on); + if (camShouldControlLEDs()) { + HardwareManager.getInstance().visionLED.ifPresent((visionLED) -> visionLED.setState(on)); + } } public void saveModule() { diff --git a/photon-server/src/main/java/org/photonvision/Main.java b/photon-server/src/main/java/org/photonvision/Main.java index d3eacb838b..13df4b7ddf 100644 --- a/photon-server/src/main/java/org/photonvision/Main.java +++ b/photon-server/src/main/java/org/photonvision/Main.java @@ -21,6 +21,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.apache.commons.cli.*; import org.opencv.core.Size; import org.photonvision.common.LoadJNI; @@ -355,7 +356,7 @@ public static void main(String[] args) { VisionSourceManager.getInstance().registerTimedTasks(); logger.info("Starting server..."); - HardwareManager.getInstance().setError(null); + HardwareManager.getInstance().setError(Optional.empty()); Server.initialize(DEFAULT_WEBPORT); } } From 769a68214e975268b56d21d277368bbc61e8549b Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Tue, 14 Apr 2026 20:42:30 -0400 Subject: [PATCH 14/15] Log warning for status led pin array length mismatch --- .../common/hardware/HardwareManager.java | 13 +++++++----- .../hardware/statusLED/GYStatusLED.java | 15 +++++++++---- .../hardware/statusLED/RGBStatusLED.java | 14 +++++++++---- .../common/hardware/statusLED/StatusLED.java | 21 +++++++------------ 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java index 783e7cb50a..94be8c4414 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/HardwareManager.java @@ -101,11 +101,14 @@ public NativeDeviceFactoryInterface get() { }; statusLED = - StatusLED.ofType( - hardwareConfig.statusLEDType, - lazyDeviceFactory, - hardwareConfig.statusLEDPins, - hardwareConfig.statusLEDActiveHigh); + hardwareConfig.statusLEDPins.isEmpty() + ? Optional.empty() + : Optional.of( + StatusLED.ofType( + hardwareConfig.statusLEDType, + lazyDeviceFactory, + hardwareConfig.statusLEDPins, + hardwareConfig.statusLEDActiveHigh)); var hasBrightnessRange = hardwareConfig.ledBrightnessRange.size() == 2; visionLED = diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java index 1c8db610a5..0c035ff982 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java @@ -19,12 +19,17 @@ import com.diozero.devices.LED; import com.diozero.internal.spi.NativeDeviceFactoryInterface; +import java.util.Collections; import java.util.List; import org.photonvision.common.hardware.PhotonStatus; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; import org.photonvision.common.util.TimedTaskManager; /** A pair of green and yellow LEDs, as used on the Limelight cameras */ public class GYStatusLED implements StatusLED { + private final Logger logger = new Logger(GYStatusLED.class, LogGroup.General); + public final LED greenLED; public final LED yellowLED; protected int blinkCounter; @@ -33,11 +38,13 @@ public class GYStatusLED implements StatusLED { public GYStatusLED( NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { + if (statusLedPins.size() != 2) { + logger.warn( + pinErrorTemplate.formatted(2, "Green and Yellow status LEDs", statusLedPins.size())); + } // fill unassigned pins with -1 to disable - if (statusLedPins.size() != 3) { - for (int i = 0; i < 3 - statusLedPins.size(); i++) { - statusLedPins.add(-1); - } + if (statusLedPins.size() < 2) { + statusLedPins.addAll(Collections.nCopies(statusLedPins.size() - 2, -1)); } // Outputs are active-low for a common-anode RGB LED diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java index a5c04f9939..d4103239b4 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/RGBStatusLED.java @@ -19,12 +19,17 @@ import com.diozero.devices.LED; import com.diozero.internal.spi.NativeDeviceFactoryInterface; +import java.util.Collections; import java.util.List; import org.photonvision.common.hardware.PhotonStatus; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; import org.photonvision.common.util.TimedTaskManager; /** Basic RGB LED with individual control over each pin */ public class RGBStatusLED implements StatusLED { + private final Logger logger = new Logger(RGBStatusLED.class, LogGroup.General); + public final LED redLED; public final LED greenLED; public final LED blueLED; @@ -34,11 +39,12 @@ public class RGBStatusLED implements StatusLED { public RGBStatusLED( NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { - // fill unassigned pins with -1 to disable if (statusLedPins.size() != 3) { - for (int i = 0; i < 3 - statusLedPins.size(); i++) { - statusLedPins.add(-1); - } + logger.warn(pinErrorTemplate.formatted(3, "a RGB status LED", statusLedPins.size())); + } + // fill unassigned pins with -1 to disable + if (statusLedPins.size() < 3) { + statusLedPins.addAll(Collections.nCopies(statusLedPins.size() - 3, -1)); } // Outputs are active-low for a common-anode RGB LED diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java index 21c9a5dcb3..d82d3bfd86 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -19,28 +19,23 @@ import com.diozero.internal.spi.NativeDeviceFactoryInterface; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; import org.photonvision.common.hardware.PhotonStatus; public interface StatusLED extends AutoCloseable { + static final String pinErrorTemplate = + "Expected %d pins for %s, but found %n pins; unassigned pins will be skipped, extra pins will be ignored"; + public void setStatus(PhotonStatus status); - static Optional ofType( + static StatusLED ofType( StatusLEDType type, Supplier lazyDeviceFactory, List statusLedPins, boolean activeHigh) { - return Optional.ofNullable( - switch (type) { - case RGB -> - statusLedPins.size() == 3 - ? new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) - : null; - case GY -> - statusLedPins.size() == 2 - ? new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh) - : null; - }); + return switch (type) { + case RGB -> new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh); + case GY -> new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh); + }; } } From 1c26be5f94a74042f361c67a4100c807fdc52138 Mon Sep 17 00:00:00 2001 From: thatcomputerguy0101 Date: Tue, 14 Apr 2026 20:44:27 -0400 Subject: [PATCH 15/15] Rename GY led type to GreenYellow --- .../sw_install/files/Limelight2+/hardwareConfig.json | 2 +- .../sw_install/files/Limelight2/hardwareConfig.json | 2 +- docs/source/docs/hardware/customhardware.md | 2 +- .../{GYStatusLED.java => GreenYellowStatusLED.java} | 6 +++--- .../photonvision/common/hardware/statusLED/StatusLED.java | 2 +- .../common/hardware/statusLED/StatusLEDType.java | 5 +---- 6 files changed, 8 insertions(+), 11 deletions(-) rename photon-core/src/main/java/org/photonvision/common/hardware/statusLED/{GYStatusLED.java => GreenYellowStatusLED.java} (95%) diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json index 6d0b568756..5b4c12f1a2 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2+/hardwareConfig.json @@ -3,7 +3,7 @@ "ledPins" : [ 13, 18 ], "ledsCanDim" : true, "ledPWMFrequency" : 1000, - "statusLEDType": "Green-Yellow", + "statusLEDType": "GreenYellow", "statusLEDPins": [ 5, 4 ], "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 diff --git a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json index 62f40f888e..fd36c6a658 100644 --- a/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json +++ b/docs/source/docs/advanced-installation/sw_install/files/Limelight2/hardwareConfig.json @@ -2,7 +2,7 @@ "deviceName" : "Limelight 2", "ledPins" : [ 17, 18 ], "ledsCanDim" : false, - "statusLEDType": "Green-Yellow", + "statusLEDType": "GreenYellow", "statusLEDPins": [ 5, 4 ], "statusLEDActiveHigh": false, "vendorFOV" : 75.76079874010732 diff --git a/docs/source/docs/hardware/customhardware.md b/docs/source/docs/hardware/customhardware.md index 8e0f6449ae..7e41ae8235 100644 --- a/docs/source/docs/hardware/customhardware.md +++ b/docs/source/docs/hardware/customhardware.md @@ -28,7 +28,7 @@ When running on Linux, PhotonVision can use [diozero](https://www.diozero.com) t There are currently two types of status LEDs supported: * `RGB` (default): A singular LED mixing separate red, green, and blue inputs -* `GY`/`Green-Yellow`: A pair of independent green and yellow LEDs +* `GreenYellow`: A pair of independent green and yellow LEDs For an explanation of the colors used for status LEDs, see {ref}`Status LEDs` diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GreenYellowStatusLED.java similarity index 95% rename from photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java rename to photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GreenYellowStatusLED.java index 0c035ff982..f2ad793d76 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GYStatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/GreenYellowStatusLED.java @@ -27,8 +27,8 @@ import org.photonvision.common.util.TimedTaskManager; /** A pair of green and yellow LEDs, as used on the Limelight cameras */ -public class GYStatusLED implements StatusLED { - private final Logger logger = new Logger(GYStatusLED.class, LogGroup.General); +public class GreenYellowStatusLED implements StatusLED { + private final Logger logger = new Logger(GreenYellowStatusLED.class, LogGroup.General); public final LED greenLED; public final LED yellowLED; @@ -36,7 +36,7 @@ public class GYStatusLED implements StatusLED { protected PhotonStatus status = PhotonStatus.GENERIC_ERROR; - public GYStatusLED( + public GreenYellowStatusLED( NativeDeviceFactoryInterface deviceFactory, List statusLedPins, boolean activeHigh) { if (statusLedPins.size() != 2) { logger.warn( diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java index d82d3bfd86..3108452587 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLED.java @@ -35,7 +35,7 @@ static StatusLED ofType( boolean activeHigh) { return switch (type) { case RGB -> new RGBStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh); - case GY -> new GYStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh); + case GreenYellow -> new GreenYellowStatusLED(lazyDeviceFactory.get(), statusLedPins, activeHigh); }; } } diff --git a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java index b217e5587d..3142c09bd6 100644 --- a/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java +++ b/photon-core/src/main/java/org/photonvision/common/hardware/statusLED/StatusLEDType.java @@ -17,10 +17,7 @@ package org.photonvision.common.hardware.statusLED; -import com.fasterxml.jackson.annotation.JsonAlias; - public enum StatusLEDType { RGB, - @JsonAlias("Green-Yellow") - GY; + GreenYellow; }