mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-20 00:51:41 +00:00
Fix selective send (#90)
* Move VisionSettingsChangeSubscriber to own class * Use selective sending on simple PSCs * Fix origin context skip logic
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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<T> extends DataChangeEvent<T> {
|
||||
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<HashMap<String, Object>> wrappedOf(
|
||||
UIUpdateType uiUpdateType,
|
||||
String commandName,
|
||||
String propertyName,
|
||||
Object value,
|
||||
WsContext originContext) {
|
||||
String commandName, String propertyName, Object value, WsContext originContext) {
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
data.put(propertyName, value);
|
||||
|
||||
return new OutgoingUIEvent<>(uiUpdateType, commandName, data, originContext);
|
||||
return new OutgoingUIEvent<>(commandName, data, originContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Double> 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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.photonvision.server;
|
||||
|
||||
public enum UIUpdateType {
|
||||
BROADCAST,
|
||||
SINGLEUSER
|
||||
}
|
||||
@@ -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<CVPipelineResultConsumer> resultConsumers = new LinkedList<>();
|
||||
private final LinkedList<FrameConsumer> 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<String, PipelineType>) 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<Integer, Integer>) 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<String, Object>) 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<Number>) newPropValue;
|
||||
var actual = new DoubleCouple(orig.get(0), orig.get(1));
|
||||
propField.set(currentSettings, actual);
|
||||
} else if (propType.isAssignableFrom(IntegerCouple.class)) {
|
||||
var orig = (ArrayList<Integer>) 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);
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, PipelineType>) 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<Integer, Integer>) 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<String, Object>) 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<Number>) newPropValue;
|
||||
var actual = new DoubleCouple(orig.get(0), orig.get(1));
|
||||
propField.set(currentSettings, actual);
|
||||
} else if (propType.isAssignableFrom(IntegerCouple.class)) {
|
||||
var orig = (ArrayList<Integer>) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user