diff --git a/photon-server/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java b/photon-server/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java index 701c6bfd6..30ed78ad3 100644 --- a/photon-server/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java +++ b/photon-server/src/main/java/org/photonvision/common/dataflow/DataChangeSubscriber.java @@ -47,7 +47,7 @@ public abstract class DataChangeSubscriber { this(DataChangeSource.AllSources, wantedDestinations); } - public abstract void onDataChangeEvent(DataChangeEvent event); + public abstract void onDataChangeEvent(DataChangeEvent event); @Override public int hashCode() { diff --git a/photon-server/src/main/java/org/photonvision/common/dataflow/events/OutgoingUIEvent.java b/photon-server/src/main/java/org/photonvision/common/dataflow/events/OutgoingUIEvent.java index 71e325d9f..2fa279137 100644 --- a/photon-server/src/main/java/org/photonvision/common/dataflow/events/OutgoingUIEvent.java +++ b/photon-server/src/main/java/org/photonvision/common/dataflow/events/OutgoingUIEvent.java @@ -21,28 +21,24 @@ import io.javalin.websocket.WsContext; import java.util.HashMap; import org.photonvision.common.dataflow.DataChangeDestination; import org.photonvision.common.dataflow.DataChangeSource; -import org.photonvision.server.UIUpdateType; public class OutgoingUIEvent extends DataChangeEvent { - public final UIUpdateType updateType; public final WsContext originContext; - public OutgoingUIEvent( - UIUpdateType updateType, String propertyName, T newValue, WsContext originContext) { + public OutgoingUIEvent(String propertyName, T newValue) { + this(propertyName, newValue, null); + } + + public OutgoingUIEvent(String propertyName, T newValue, WsContext originContext) { super(DataChangeSource.DCS_WEBSOCKET, DataChangeDestination.DCD_UI, propertyName, newValue); - this.updateType = updateType; this.originContext = originContext; } public static OutgoingUIEvent> wrappedOf( - UIUpdateType uiUpdateType, - String commandName, - String propertyName, - Object value, - WsContext originContext) { + String commandName, String propertyName, Object value, WsContext originContext) { HashMap data = new HashMap<>(); data.put(propertyName, value); - return new OutgoingUIEvent<>(uiUpdateType, commandName, data, originContext); + return new OutgoingUIEvent<>(commandName, data, originContext); } } diff --git a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java index e3ca2654d..bc8b6b083 100644 --- a/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java +++ b/photon-server/src/main/java/org/photonvision/common/hardware/metrics/MetricsPublisher.java @@ -23,7 +23,6 @@ import org.photonvision.common.dataflow.events.OutgoingUIEvent; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.TimedTaskManager; -import org.photonvision.server.UIUpdateType; public class MetricsPublisher { private final HashMap metrics; @@ -57,8 +56,7 @@ public class MetricsPublisher { metrics.put("ramUtil", ramMetrics.getUsedRam()); DataChangeService.getInstance() - .publishEvent( - new OutgoingUIEvent<>(UIUpdateType.BROADCAST, "metrics", metrics, null)); + .publishEvent(new OutgoingUIEvent<>("metrics", metrics)); }, 1000); } diff --git a/photon-server/src/main/java/org/photonvision/common/logging/Logger.java b/photon-server/src/main/java/org/photonvision/common/logging/Logger.java index 22bb238a3..e5eee1aae 100644 --- a/photon-server/src/main/java/org/photonvision/common/logging/Logger.java +++ b/photon-server/src/main/java/org/photonvision/common/logging/Logger.java @@ -30,7 +30,6 @@ import org.photonvision.common.dataflow.DataChangeService; import org.photonvision.common.dataflow.events.OutgoingUIEvent; import org.photonvision.common.util.TimedTaskManager; import org.photonvision.server.SocketHandler; -import org.photonvision.server.UIUpdateType; @SuppressWarnings("unused") public class Logger { @@ -239,8 +238,7 @@ public class Logger { messageMap.put("logLevel", level.code); var superMap = new SocketHandler.UIMap(); superMap.put("logMessage", messageMap); - DataChangeService.getInstance() - .publishEvent(new OutgoingUIEvent<>(UIUpdateType.BROADCAST, "log", superMap, null)); + DataChangeService.getInstance().publishEvent(new OutgoingUIEvent<>("log", superMap)); } } diff --git a/photon-server/src/main/java/org/photonvision/server/Server.java b/photon-server/src/main/java/org/photonvision/server/Server.java index b664a74e6..d6b3bcbab 100644 --- a/photon-server/src/main/java/org/photonvision/server/Server.java +++ b/photon-server/src/main/java/org/photonvision/server/Server.java @@ -54,7 +54,11 @@ public class Server { ws.onBinaryMessage( ctx -> logger.debug( - "Got WebSockets binary message from host " + ctx.host()))); + () -> { + var insa = ctx.session.getRemote().getInetSocketAddress(); + var host = insa.getAddress().toString() + ":" + insa.getPort(); + return "Got WebSockets binary message from host " + host; + }))); }); var socketHandler = SocketHandler.getInstance(); diff --git a/photon-server/src/main/java/org/photonvision/server/SocketHandler.java b/photon-server/src/main/java/org/photonvision/server/SocketHandler.java index fa02fe8b4..336a63cc5 100644 --- a/photon-server/src/main/java/org/photonvision/server/SocketHandler.java +++ b/photon-server/src/main/java/org/photonvision/server/SocketHandler.java @@ -70,7 +70,8 @@ public class SocketHandler { public void onConnect(WsConnectContext context) { context.session.setIdleTimeout(Long.MAX_VALUE); // TODO: determine better value - var host = context.session.getRemote().getInetSocketAddress().getHostName(); + var insa = context.session.getRemote().getInetSocketAddress(); + var host = insa.getAddress().toString() + ":" + insa.getPort(); logger.info("New websocket connection from " + host); users.add(context); dcService.publishEvent( @@ -79,7 +80,8 @@ public class SocketHandler { } protected void onClose(WsCloseContext context) { - var host = context.session.getRemote().getInetSocketAddress().getHostName(); + var insa = context.session.getRemote().getInetSocketAddress(); + var host = insa.getAddress().toString() + ":" + insa.getPort(); var reason = context.reason() != null ? context.reason() : "Connection closed by client"; logger.info("Closing websocket connection from " + host + " for reason: " + reason); users.remove(context); @@ -301,19 +303,25 @@ public class SocketHandler { } } - // TODO: change to use the DataFlow system private void sendMessage(Object message, WsContext user) throws JsonProcessingException { ByteBuffer b = ByteBuffer.wrap(objectMapper.writeValueAsBytes(message)); user.send(b); } - // TODO: change to use the DataFlow system public void broadcastMessage(Object message, WsContext userToSkip) throws JsonProcessingException { - for (WsContext user : users) { - if (user != userToSkip) { + if (userToSkip == null) { + for (WsContext user : users) { sendMessage(message, user); } + } else { + var skipUserPort = userToSkip.session.getRemote().getInetSocketAddress().getPort(); + for (WsContext user : users) { + var userPort = user.session.getRemote().getInetSocketAddress().getPort(); + if (userPort != skipUserPort) { + sendMessage(message, user); + } + } } } } diff --git a/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java b/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java index a35083158..e281c99db 100644 --- a/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java +++ b/photon-server/src/main/java/org/photonvision/server/UIInboundSubscriber.java @@ -36,16 +36,15 @@ public class UIInboundSubscriber extends DataChangeSubscriber { } @Override - public void onDataChangeEvent(DataChangeEvent event) { + public void onDataChangeEvent(DataChangeEvent event) { if (event instanceof IncomingWebSocketEvent) { - var incomingWSEvent = (IncomingWebSocketEvent) event; + var incomingWSEvent = (IncomingWebSocketEvent) event; if (incomingWSEvent.propertyName.equals("userConnected") || incomingWSEvent.propertyName.equals("sendFullSettings")) { // Send full settings var settings = ConfigManager.getInstance().getConfig().toHashMap(); var message = - new OutgoingUIEvent<>( - UIUpdateType.BROADCAST, "fullsettings", settings, incomingWSEvent.originContext); + new OutgoingUIEvent<>("fullsettings", settings, incomingWSEvent.originContext); DataChangeService.getInstance().publishEvent(message); } } diff --git a/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java b/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java index 8290153a4..c5bb2906e 100644 --- a/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java +++ b/photon-server/src/main/java/org/photonvision/server/UIOutboundSubscriber.java @@ -20,7 +20,6 @@ package org.photonvision.server; import com.fasterxml.jackson.core.JsonProcessingException; import java.util.Collections; import java.util.HashMap; -import org.apache.commons.lang3.tuple.Pair; import org.photonvision.common.dataflow.DataChangeDestination; import org.photonvision.common.dataflow.DataChangeSource; import org.photonvision.common.dataflow.DataChangeSubscriber; @@ -31,7 +30,7 @@ import org.photonvision.common.logging.Logger; @SuppressWarnings("rawtypes") /* -* DO NOT use logging in this class. If you do, the logs will recuse forever! +* DO NOT use logging in this class. If you do, the logs will recurse forever! */ class UIOutboundSubscriber extends DataChangeSubscriber { Logger logger = new Logger(UIOutboundSubscriber.class, LogGroup.WebServer); @@ -48,25 +47,11 @@ class UIOutboundSubscriber extends DataChangeSubscriber { if (event instanceof OutgoingUIEvent) { var thisEvent = (OutgoingUIEvent) event; try { - switch (thisEvent.updateType) { - case BROADCAST: - { - if (event.data instanceof HashMap) { - var data = (HashMap) event.data; - socketHandler.broadcastMessage(data, null); - } else { - socketHandler.broadcastMessage(event.data, null); - } - break; - } - case SINGLEUSER: - { - if (event.data instanceof Pair) { - var pair = (SocketHandler.SelectiveBroadcastPair) event.data; - socketHandler.broadcastMessage(pair.getLeft(), pair.getRight()); - } - break; - } + if (event.data instanceof HashMap) { + var data = (HashMap) event.data; + socketHandler.broadcastMessage(data, thisEvent.originContext); + } else { + socketHandler.broadcastMessage(event.data, thisEvent.originContext); } } catch (JsonProcessingException e) { logger.error("Failed to process outgoing message!", e); diff --git a/photon-server/src/main/java/org/photonvision/server/UIUpdateType.java b/photon-server/src/main/java/org/photonvision/server/UIUpdateType.java deleted file mode 100644 index fb234a77b..000000000 --- a/photon-server/src/main/java/org/photonvision/server/UIUpdateType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2020 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.server; - -public enum UIUpdateType { - BROADCAST, - SINGLEUSER -} diff --git a/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java index 2abfab960..e35484ca5 100644 --- a/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -21,15 +21,11 @@ import edu.wpi.first.wpilibj.geometry.Rotation2d; import edu.wpi.first.wpilibj.util.Units; import io.javalin.websocket.WsContext; import java.util.*; -import org.apache.commons.lang3.tuple.Pair; import org.photonvision.common.configuration.CameraConfiguration; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.PhotonConfiguration; import org.photonvision.common.dataflow.CVPipelineResultConsumer; import org.photonvision.common.dataflow.DataChangeService; -import org.photonvision.common.dataflow.DataChangeSubscriber; -import org.photonvision.common.dataflow.events.DataChangeEvent; -import org.photonvision.common.dataflow.events.IncomingWebSocketEvent; import org.photonvision.common.dataflow.events.OutgoingUIEvent; import org.photonvision.common.dataflow.networktables.NTDataPublisher; import org.photonvision.common.dataflow.websocket.UIDataPublisher; @@ -37,9 +33,6 @@ import org.photonvision.common.hardware.HardwareManager; import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.SerializationUtils; -import org.photonvision.common.util.numbers.DoubleCouple; -import org.photonvision.common.util.numbers.IntegerCouple; -import org.photonvision.server.UIUpdateType; import org.photonvision.vision.calibration.CameraCalibrationCoefficients; import org.photonvision.vision.camera.CameraQuirk; import org.photonvision.vision.camera.QuirkyCamera; @@ -47,7 +40,6 @@ import org.photonvision.vision.camera.USBCameraSource; import org.photonvision.vision.frame.Frame; import org.photonvision.vision.frame.FrameConsumer; import org.photonvision.vision.frame.consumer.MJPGFrameConsumer; -import org.photonvision.vision.pipeline.PipelineType; import org.photonvision.vision.pipeline.UICalibrationData; import org.photonvision.vision.pipeline.result.CVPipelineResult; @@ -62,21 +54,20 @@ public class VisionModule { private static final int StreamFPSCap = 30; private final Logger logger; - private final PipelineManager pipelineManager; - private final VisionSource visionSource; + protected final PipelineManager pipelineManager; + protected final VisionSource visionSource; private final VisionRunner visionRunner; private final LinkedList resultConsumers = new LinkedList<>(); private final LinkedList frameConsumers = new LinkedList<>(); private final NTDataPublisher ntConsumer; private final UIDataPublisher uiDataConsumer; - private final int moduleIndex; - private final QuirkyCamera cameraQuirks; + protected final int moduleIndex; + protected final QuirkyCamera cameraQuirks; - private long lastSettingChangeTimestamp = 0; private long lastFrameConsumeMillis; - private MJPGFrameConsumer dashboardInputStreamer; - private MJPGFrameConsumer dashboardOutputStreamer; + MJPGFrameConsumer dashboardInputStreamer; + MJPGFrameConsumer dashboardOutputStreamer; public VisionModule(PipelineManager pipelineManager, VisionSource visionSource, int index) { logger = @@ -100,7 +91,7 @@ public class VisionModule { cameraQuirks = QuirkyCamera.DefaultCamera; } - DataChangeService.getInstance().addSubscriber(new VisionSettingChangeSubscriber()); + DataChangeService.getInstance().addSubscriber(new VisionModuleChangeSubscriber(this)); dashboardOutputStreamer = new MJPGFrameConsumer( @@ -137,7 +128,7 @@ public class VisionModule { } } - private void setDriverMode(boolean isDriverMode) { + void setDriverMode(boolean isDriverMode) { pipelineManager.setDriverMode(isDriverMode); saveAndBroadcastAll(); } @@ -208,184 +199,7 @@ public class VisionModule { return ret; } - private class VisionSettingChangeSubscriber extends DataChangeSubscriber { - - private VisionSettingChangeSubscriber() { - super(); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public void onDataChangeEvent(DataChangeEvent event) { - if (event instanceof IncomingWebSocketEvent) { - var wsEvent = (IncomingWebSocketEvent) event; - - if (wsEvent.cameraIndex != null && wsEvent.cameraIndex == moduleIndex) { - logger.trace("Got PSC event - propName: " + wsEvent.propertyName); - - var propName = wsEvent.propertyName; - var newPropValue = wsEvent.data; - var currentSettings = pipelineManager.getCurrentUserPipeline().getSettings(); - - // special case for non-PipelineSetting changes - switch (propName) { - case "cameraNickname": // rename camera - var newNickname = (String) newPropValue; - logger.info("Changing nickname to " + newNickname); - setCameraNickname(newNickname); - saveAndBroadcastAll(); - return; - case "pipelineName": // rename current pipeline - logger.info("Changing nick to " + newPropValue); - pipelineManager.renameCurrentPipeline((String) newPropValue); - saveAndBroadcastAll(); - return; - case "newPipelineInfo": // add new pipeline - var typeName = (Pair) newPropValue; - var type = typeName.getRight(); - var name = typeName.getLeft(); - - logger.info("Adding a " + type + " pipeline with name " + name); - - var addedSettings = pipelineManager.addPipeline(type, name); - addedSettings.pipelineNickname = name; - - var newIndex = pipelineManager.userPipelineSettings.indexOf(addedSettings); - setPipeline(newIndex); - saveAndBroadcastAll(); - return; - case "deleteCurrPipeline": - var indexToDelete = pipelineManager.getCurrentPipelineIndex(); - logger.info("Deleting current pipe at index " + indexToDelete); - pipelineManager.removePipeline(indexToDelete); - saveAndBroadcastAll(); - return; - case "duplicatePipeline": - logger.info("Duplicating pipe " + newPropValue); - pipelineManager.duplicatePipeline((Integer) newPropValue); - saveAndBroadcastAll(); - return; - case "changePipeline": // change active pipeline - var index = (Integer) newPropValue; - if (index == pipelineManager.getCurrentPipelineIndex()) { - logger.debug("Skipping pipeline change, index " + index + " already active"); - return; - } - setPipeline(index); - saveAndBroadcastAll(); - return; - case "dimLED": - if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { - var dimPercentage = (int) newPropValue; - HardwareManager.getInstance().setBrightnessPercentage(dimPercentage); - } - return; - case "blinkLED": - if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { - var params = (Pair) newPropValue; - HardwareManager.getInstance().blinkLEDs(params.getLeft(), params.getRight()); - } - return; - case "setLED": - if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { - var state = (boolean) newPropValue; - if (state) HardwareManager.getInstance().turnLEDsOn(); - else HardwareManager.getInstance().turnLEDsOff(); - } - return; - case "toggleLED": - if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { - HardwareManager.getInstance().toggleLEDs(); - } - return; - case "shutdownLEDs": - if (cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { - HardwareManager.getInstance().shutdown(); - } - return; - case "startcalibration": - var data = UICalibrationData.fromMap((Map) newPropValue); - startCalibration(data); - saveAndBroadcastAll(); - return; - case "takeCalSnapshot": - takeCalibrationSnapshot(); - return; - } - - // special case for camera settables - if (propName.startsWith("camera")) { - var propMethodName = "set" + propName.replace("camera", ""); - var methods = visionSource.getSettables().getClass().getMethods(); - for (var method : methods) { - if (method.getName().equalsIgnoreCase(propMethodName)) { - try { - method.invoke(visionSource.getSettables(), newPropValue); - } catch (Exception e) { - logger.error("Failed to invoke camera settable method: " + method.getName(), e); - } - } - } - } - - try { - var propField = currentSettings.getClass().getField(propName); - var propType = propField.getType(); - - if (propType.isEnum()) { - var actual = propType.getEnumConstants()[(int) newPropValue]; - propField.set(currentSettings, actual); - } else if (propType.isAssignableFrom(DoubleCouple.class)) { - var orig = (ArrayList) newPropValue; - var actual = new DoubleCouple(orig.get(0), orig.get(1)); - propField.set(currentSettings, actual); - } else if (propType.isAssignableFrom(IntegerCouple.class)) { - var orig = (ArrayList) newPropValue; - var actual = new IntegerCouple(orig.get(0), orig.get(1)); - propField.set(currentSettings, actual); - } else if (propType.equals(Double.TYPE)) { - propField.setDouble(currentSettings, (Double) newPropValue); - } else if (propType.equals(Integer.TYPE)) { - propField.setInt(currentSettings, (Integer) newPropValue); - } else if (propType.equals(Boolean.TYPE)) { - if (newPropValue instanceof Integer) { - propField.setBoolean(currentSettings, (Integer) newPropValue != 0); - } else { - propField.setBoolean(currentSettings, (Boolean) newPropValue); - } - } else { - propField.set(newPropValue, newPropValue); - } - logger.trace("Set prop " + propName + " to value " + newPropValue); - - // special case for extra tasks to perform after setting PipelineSettings - if (propName.equals("streamingFrameDivisor")) { - dashboardInputStreamer.setFrameDivisor( - pipelineManager.getCurrentPipelineSettings().streamingFrameDivisor); - dashboardOutputStreamer.setFrameDivisor( - pipelineManager.getCurrentPipelineSettings().streamingFrameDivisor); - } - - } catch (NoSuchFieldException | IllegalAccessException e) { - logger.error( - "Could not set prop " - + propName - + " with value " - + newPropValue - + " on " - + currentSettings, - e); - } catch (Exception e) { - logger.error("Unknown exception when setting PSC prop!", e); - } - - saveModule(); - } - } - } - } - - private void setPipeline(int index) { + void setPipeline(int index) { logger.info("Setting pipeline to " + index); pipelineManager.setIndex(index); var config = pipelineManager.getPipelineSettings(index); @@ -415,28 +229,23 @@ public class VisionModule { getStateAsCameraConfig(), visionSource.getSettables().getConfiguration().uniqueName); } - private void saveAndBroadcastAll() { + void saveAndBroadcastAll() { saveModule(); DataChangeService.getInstance() .publishEvent( new OutgoingUIEvent<>( - UIUpdateType.BROADCAST, - "fullsettings", - ConfigManager.getInstance().getConfig().toHashMap(), - null)); + "fullsettings", ConfigManager.getInstance().getConfig().toHashMap())); } - private void saveAndBroadcastSelective( - WsContext originContext, String propertyName, Object value) { + void saveAndBroadcastSelective(WsContext originContext, String propertyName, Object value) { logger.trace("Broadcasting PSC mutation - " + propertyName + ": " + value); saveModule(); DataChangeService.getInstance() .publishEvent( - OutgoingUIEvent.wrappedOf( - UIUpdateType.BROADCAST, "mutatePipeline", propertyName, value, originContext)); + OutgoingUIEvent.wrappedOf("mutatePipeline", propertyName, value, originContext)); } - private void setCameraNickname(String newName) { + void setCameraNickname(String newName) { visionSource.getSettables().getConfiguration().nickname = newName; ntConsumer.updateCameraNickname(newName); diff --git a/photon-server/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java new file mode 100644 index 000000000..0bb56d440 --- /dev/null +++ b/photon-server/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2020 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.vision.processes; + +import java.util.ArrayList; +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; +import org.photonvision.common.dataflow.DataChangeSubscriber; +import org.photonvision.common.dataflow.events.DataChangeEvent; +import org.photonvision.common.dataflow.events.IncomingWebSocketEvent; +import org.photonvision.common.hardware.HardwareManager; +import org.photonvision.common.logging.LogGroup; +import org.photonvision.common.logging.Logger; +import org.photonvision.common.util.numbers.DoubleCouple; +import org.photonvision.common.util.numbers.IntegerCouple; +import org.photonvision.vision.camera.CameraQuirk; +import org.photonvision.vision.pipeline.PipelineType; +import org.photonvision.vision.pipeline.UICalibrationData; + +@SuppressWarnings("unchecked") +public class VisionModuleChangeSubscriber extends DataChangeSubscriber { + + private final VisionModule parentModule; + private final Logger logger; + + public VisionModuleChangeSubscriber(VisionModule parentModule) { + this.parentModule = parentModule; + logger = + new Logger( + VisionModuleChangeSubscriber.class, + parentModule.visionSource.getSettables().getConfiguration().nickname, + LogGroup.VisionModule); + } + + @Override + public void onDataChangeEvent(DataChangeEvent event) { + if (event instanceof IncomingWebSocketEvent) { + var wsEvent = (IncomingWebSocketEvent) event; + + if (wsEvent.cameraIndex != null && wsEvent.cameraIndex == parentModule.moduleIndex) { + logger.trace("Got PSC event - propName: " + wsEvent.propertyName); + + var propName = wsEvent.propertyName; + var newPropValue = wsEvent.data; + var currentSettings = parentModule.pipelineManager.getCurrentUserPipeline().getSettings(); + + // special case for non-PipelineSetting changes + switch (propName) { + case "cameraNickname": // rename camera + var newNickname = (String) newPropValue; + logger.info("Changing nickname to " + newNickname); + parentModule.setCameraNickname(newNickname); + parentModule.saveAndBroadcastAll(); + return; + case "pipelineName": // rename current pipeline + logger.info("Changing nick to " + newPropValue); + parentModule.pipelineManager.getCurrentPipelineSettings().pipelineNickname = + (String) newPropValue; + parentModule.saveAndBroadcastAll(); + return; + case "newPipelineInfo": // add new pipeline + var typeName = (Pair) newPropValue; + var type = typeName.getRight(); + var name = typeName.getLeft(); + + logger.info("Adding a " + type + " pipeline with name " + name); + + var addedSettings = parentModule.pipelineManager.addPipeline(type); + addedSettings.pipelineNickname = name; + parentModule.saveAndBroadcastAll(); + return; + case "deleteCurrPipeline": + var indexToDelete = parentModule.pipelineManager.getCurrentPipelineIndex(); + logger.info("Deleting current pipe at index " + indexToDelete); + parentModule.pipelineManager.removePipeline(indexToDelete); + parentModule.saveAndBroadcastAll(); + return; + case "changePipeline": // change active pipeline + var index = (Integer) newPropValue; + if (index == parentModule.pipelineManager.getCurrentPipelineIndex()) { + logger.debug("Skipping pipeline change, index " + index + " already active"); + return; + } + parentModule.setPipeline(index); + parentModule.saveAndBroadcastAll(); + return; + case "dimLED": + if (parentModule.cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var dimPercentage = (int) newPropValue; + HardwareManager.getInstance().setBrightnessPercentage(dimPercentage); + } + return; + case "blinkLED": + if (parentModule.cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var params = (Pair) newPropValue; + HardwareManager.getInstance().blinkLEDs(params.getLeft(), params.getRight()); + } + return; + case "setLED": + if (parentModule.cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + var state = (boolean) newPropValue; + if (state) HardwareManager.getInstance().turnLEDsOn(); + else HardwareManager.getInstance().turnLEDsOff(); + } + return; + case "toggleLED": + if (parentModule.cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + HardwareManager.getInstance().toggleLEDs(); + } + return; + case "shutdownLEDs": + if (parentModule.cameraQuirks.hasQuirk(CameraQuirk.PiCam)) { + HardwareManager.getInstance().shutdown(); + } + return; + case "startcalibration": + var data = UICalibrationData.fromMap((Map) newPropValue); + parentModule.startCalibration(data); + parentModule.saveAndBroadcastAll(); + return; + case "takeCalSnapshot": + parentModule.takeCalibrationSnapshot(); + return; + } + + // special case for camera settables + if (propName.startsWith("camera")) { + var propMethodName = "set" + propName.replace("camera", ""); + var methods = parentModule.visionSource.getSettables().getClass().getMethods(); + for (var method : methods) { + if (method.getName().equalsIgnoreCase(propMethodName)) { + try { + method.invoke(parentModule.visionSource.getSettables(), newPropValue); + } catch (Exception e) { + logger.error("Failed to invoke camera settable method: " + method.getName(), e); + } + } + } + } + + try { + var propField = currentSettings.getClass().getField(propName); + var propType = propField.getType(); + + if (propType.isEnum()) { + var actual = propType.getEnumConstants()[(int) newPropValue]; + propField.set(currentSettings, actual); + } else if (propType.isAssignableFrom(DoubleCouple.class)) { + var orig = (ArrayList) newPropValue; + var actual = new DoubleCouple(orig.get(0), orig.get(1)); + propField.set(currentSettings, actual); + } else if (propType.isAssignableFrom(IntegerCouple.class)) { + var orig = (ArrayList) newPropValue; + var actual = new IntegerCouple(orig.get(0), orig.get(1)); + propField.set(currentSettings, actual); + } else if (propType.equals(Double.TYPE)) { + propField.setDouble(currentSettings, (Double) newPropValue); + } else if (propType.equals(Integer.TYPE)) { + propField.setInt(currentSettings, (Integer) newPropValue); + } else if (propType.equals(Boolean.TYPE)) { + if (newPropValue instanceof Integer) { + propField.setBoolean(currentSettings, (Integer) newPropValue != 0); + } else { + propField.setBoolean(currentSettings, (Boolean) newPropValue); + } + } else { + propField.set(newPropValue, newPropValue); + } + logger.trace("Set prop " + propName + " to value " + newPropValue); + + // special case for extra tasks to perform after setting PipelineSettings + if (propName.equals("streamingFrameDivisor")) { + parentModule.dashboardInputStreamer.setFrameDivisor( + parentModule.pipelineManager.getCurrentPipelineSettings().streamingFrameDivisor); + parentModule.dashboardOutputStreamer.setFrameDivisor( + parentModule.pipelineManager.getCurrentPipelineSettings().streamingFrameDivisor); + } + + } catch (NoSuchFieldException | IllegalAccessException e) { + logger.error( + "Could not set prop " + + propName + + " with value " + + newPropValue + + " on " + + currentSettings, + e); + } catch (Exception e) { + logger.error("Unknown exception when setting PSC prop!", e); + } + + // parentModule.saveModule(); + parentModule.saveAndBroadcastSelective(wsEvent.originContext, propName, newPropValue); + } + } + } +}