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:
Banks T
2020-08-17 16:20:36 -04:00
committed by GitHub
parent b3436765e1
commit 832d8413e1
11 changed files with 264 additions and 278 deletions

View File

@@ -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() {

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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();

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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);
}
}
}
}