Bootup sprint (#18)

* Did some stuff

* Fix gradle, start implementing mjpeg frame consumer

* Did some stuff

* bade changes

* rename camera config to USBCameraConfiguration, add name

* unrename cameraconfiguration

* Add pub/sub framework

* Add setResolution to mjpeg frame consumer

* add NTDataConsumer

* Add some totally broken hsv hacks

* Start refactoring UI data

* Update index.js

* Commit and push, he says

* Fix up some errors

* Fix input tab

* Fix fps

* Update index.js

* Add pipeline field setting, update PipelineManager, fix nullpointers and USBCameraSettables

* Change v-model to point to data()

* update hsv to use mutations

* Work on saving, fix hsv

* Rename shouldErode/shouldDilate to erode and dilate

* Hook all the tabs up to the Store

* Change handleData to handlePipelineData

* camera quirk redo, add ICCSub to SocketHandler

* Fix some property names

* Fixed tons of naming in UI, fix backend for multi-val PSCs, fix PSC enums

* change pipeline type to an int in store

* Fix mutation naming

* Attempt threshold fix

* Update SocketHandler.java

* Add truthy data sending

* Start adding logging support

* [UI] Add delay to slider input boxes (#1)

* [UI] [Backend] potentially fix camera settings, various logging tweaks

* Don't release raw input mat

* add setVideoModeIndex to vision settables

* Implement pipeline index in socket handler, add framework for renaming/changing pipes

* (ish) get pipeline change working

* Create index.html

* Cleanups, fix pipeline index bug, fix stream res for MJPG, add dashboard stream (unused)

* Refactor UI to use mutatePipeline, send pipeline results

* Update NetworkConfig.java

* Change double to number

* Run spotless

* Fix reversal of large/small comparators

* Fix left/right

* Fix pitch/yaw calculation bug, fix area bug

* Use Vue.set instead of assignment

This fixes {{ }}

* Update App.vue

* run spotless

* Actually add pipelines and reassign indecies

* Delete old pipeline configs

Fixes duplication on renaming pipeline

* Start working on deleting pipes

* Fix camera nickname change

* run spotless

* Fix some test stuff

* Update VisionModuleManagerTest.java

* vision source manager test is still broken

* Fix VisionSourceManager test

* Apply spotless 2 electric boogaloo

Co-authored-by: Banks Troutman <btrout.dhrs@gmail.com>
Co-authored-by: Declan Freeman-Gleason <declanfreemangleason@gmail.com>
Co-authored-by: Aaryan Agrawal <54345060+13Ducks@users.noreply.github.com>
This commit is contained in:
Matt
2020-07-07 01:01:58 -07:00
committed by GitHub
parent 01712a7396
commit 4cd2262acc
106 changed files with 3666 additions and 623 deletions

View File

@@ -18,24 +18,54 @@
package org.photonvision.server;
import io.javalin.Javalin;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
public class Server {
private static final Logger logger = new Logger(Server.class, LogGroup.Server);
public static void main(int port) {
Javalin app =
Javalin.create(
javalinConfig -> {
javalinConfig.showJavalinBanner = false;
javalinConfig.addStaticFiles("web");
javalinConfig.enableCorsForAllOrigins();
config -> {
config.showJavalinBanner = false;
config.addStaticFiles("web");
config.enableCorsForAllOrigins();
config.requestLogger(
(ctx, ms) ->
logger.debug(
"Handled HTTP "
+ ctx.req.getMethod()
+ " request from "
+ ctx.req.getRemoteHost()
+ " in "
+ ms.toString()
+ "ms"));
config.wsLogger(
ws ->
ws.onMessage(
ctx -> logger.debug("Got WebSockets message: " + ctx.message())));
config.wsLogger(
ws ->
ws.onBinaryMessage(
ctx ->
logger.debug(
"Got WebSockets binary message from host " + ctx.host())));
});
var socketHandler = SocketHandler.getInstance();
/*Web Socket Events */
app.ws(
"/websocket",
ws -> {
ws.onConnect(SocketHandler::onConnect);
ws.onClose(SocketHandler::onClose);
ws.onBinaryMessage(SocketHandler::onBinaryMessage);
ws.onConnect(socketHandler::onConnect);
ws.onClose(socketHandler::onClose);
ws.onBinaryMessage(socketHandler::onBinaryMessage);
});
/*API Events*/
app.post("/api/settings/general", RequestHandler::onGeneralSettings);

View File

@@ -23,49 +23,273 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import io.javalin.websocket.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.photonvision.common.dataflow.DataChangeDestination;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.camera.IncomingCameraCommandSubscriber;
import org.photonvision.common.dataflow.events.IncomingWebSocketEvent;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.pipeline.PipelineType;
import org.photonvision.vision.processes.PipelineManager;
import org.photonvision.vision.processes.VisionModuleManager;
@SuppressWarnings("rawtypes")
public class SocketHandler {
static List<WsContext> users = new ArrayList<>();
static ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
public static void onConnect(WsConnectContext context) {
users.add(context);
private final Logger logger = new Logger(SocketHandler.class, LogGroup.Server);
private final List<WsContext> users = new ArrayList<>();
private final ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
private final DataChangeService dcService = DataChangeService.getInstance();
@SuppressWarnings("FieldCanBeLocal")
private final UIOutboundSubscriber uiOutboundSubscriber = new UIOutboundSubscriber(this);
private final IncomingCameraCommandSubscriber cameraChangeSubscriber =
new IncomingCameraCommandSubscriber(VisionModuleManager.getInstance());
public static class UIMap extends HashMap<String, Object> {}
abstract static class SelectiveBroadcastPair extends Pair<UIMap, WsContext> {}
private static class ThreadSafeSingleton {
private static final SocketHandler INSTANCE = new SocketHandler();
}
static void onClose(WsCloseContext context) {
public static SocketHandler getInstance() {
return SocketHandler.ThreadSafeSingleton.INSTANCE;
}
private SocketHandler() {
dcService.addSubscribers(
uiOutboundSubscriber,
cameraChangeSubscriber,
new UIInboundSubscriber()); // Subscribe outgoing messages to the data change service
}
public void onConnect(WsConnectContext context) {
context.session.setIdleTimeout(Long.MAX_VALUE); // TODO: determine better value
var host = context.session.getRemote().getInetSocketAddress().getHostName();
logger.info("New websocket connection from " + host);
users.add(context);
dcService.publishEvent(
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_GENSETTINGS, "userConnected", context));
}
protected void onClose(WsCloseContext context) {
var host = context.session.getRemote().getInetSocketAddress().getHostName();
var reason = context.reason() != null ? context.reason() : "Connection closed by client";
logger.info("Closing websocket connection from " + host + " for reason: " + reason);
users.remove(context);
}
public static void onBinaryMessage(WsBinaryMessageContext context) {
@SuppressWarnings({"unchecked"})
public void onBinaryMessage(WsBinaryMessageContext context) {
try {
Map<String, Object> data =
objectMapper.readValue(context.data(), new TypeReference<Map<String, Object>>() {});
// TODO pass data to ui data provider
Map<String, Object> deserializedData =
objectMapper.readValue(context.data(), new TypeReference<>() {});
// Special case the current camera index
var camIndexValue = deserializedData.get("cameraIndex");
Integer cameraIndex = null;
if (camIndexValue instanceof Integer) {
cameraIndex = (Integer) camIndexValue;
deserializedData.remove("cameraIndex");
}
for (Map.Entry<String, Object> entry : deserializedData.entrySet()) {
try {
var entryKey = entry.getKey();
var entryValue = entry.getValue();
var socketMessageType = SocketMessageType.fromEntryKey(entryKey);
logger.debug(
"Got WS message: ["
+ socketMessageType
+ "] ==> ["
+ entryKey
+ "], ["
+ entryValue
+ "]");
if (socketMessageType == null) {
logger.warn("Got unknown socket message type: " + entryKey);
continue;
}
switch (socketMessageType) {
case SMT_DRIVERMODE:
{
// TODO: what is this event?
var data = (HashMap<String, Object>) entryValue;
var dmExpEvent =
new IncomingWebSocketEvent<Integer>(
DataChangeDestination.DCD_ACTIVEMODULE, "driverExposure", data);
var dmBrightEvent =
new IncomingWebSocketEvent<Integer>(
DataChangeDestination.DCD_ACTIVEMODULE, "driverBrightness", data);
var dmIsDriverEvent =
new IncomingWebSocketEvent<Boolean>(
DataChangeDestination.DCD_ACTIVEMODULE, "isDriver", data);
dcService.publishEvents(dmExpEvent, dmBrightEvent, dmIsDriverEvent);
break;
}
case SMT_CHANGECAMERANAME:
{
var ccnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"cameraNickname",
(String) entryValue,
cameraIndex);
dcService.publishEvent(ccnEvent);
break;
}
case SMT_CHANGEPIPELINENAME:
{
var cpnEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"pipelineName",
(String) entryValue,
cameraIndex);
dcService.publishEvent(cpnEvent);
break;
}
case SMT_ADDNEWPIPELINE:
{
// HashMap<String, Object> data = (HashMap<String,
// Object>) entryValue;
// var type = (PipelineType)
// data.get("pipelineType");
// var name = (String) data.get("pipelineName");
var type = PipelineType.Reflective;
var name = (String) entryValue;
var newPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"newPipelineInfo",
Pair.of(name, type),
cameraIndex);
dcService.publishEvent(newPipelineEvent);
break;
}
case SMT_COMMAND:
{
var cmd = SocketMessageCommandType.fromEntryKey((String) entryValue);
switch (cmd) {
case SMCT_DELETECURRENTPIPELINE:
{
var deleteCurrentPipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"deleteCurrPipeline",
0,
cameraIndex);
dcService.publishEvent(deleteCurrentPipelineEvent);
break;
}
case SMCT_SAVE:
{
var saveEvent =
new IncomingWebSocketEvent<>(DataChangeDestination.DCD_OTHER, "save", 0);
dcService.publishEvent(saveEvent);
break;
}
}
break;
}
case SMT_CURRENTCAMERA:
{
var changeCurrentCameraEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_OTHER, "changeUICamera", (Integer) entryValue);
dcService.publishEvent(changeCurrentCameraEvent);
break;
}
case SMT_CURRENTPIPELINE:
{
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipeline",
(Integer) entryValue,
cameraIndex);
dcService.publishEvent(changePipelineEvent);
break;
}
case SMT_ISPNPCALIBRATION:
{
var changePipelineEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE,
"changePipeline",
PipelineManager.CAL_3D_INDEX,
cameraIndex);
dcService.publishEvent(changePipelineEvent);
break;
}
case SMT_TAKECALIBRATIONSNAPSHOT:
{
var takeCalSnapshotEvent =
new IncomingWebSocketEvent<>(
DataChangeDestination.DCD_ACTIVEMODULE, "takeCalSnapshot", 0, cameraIndex);
dcService.publishEvent(takeCalSnapshotEvent);
break;
}
case SMT_PIPELINESETTINGCHANGE:
{
HashMap<String, Object> data = (HashMap<String, Object>) entryValue;
if (data.size() >= 2) {
var cameraIndex2 = (int) data.get("cameraIndex");
for (var dataEntry : data.entrySet()) {
if (dataEntry.getKey().equals("cameraIndex")) {
continue;
}
var pipelineSettingChangeEvent =
new IncomingWebSocketEvent(
DataChangeDestination.DCD_ACTIVEPIPELINESETTINGS,
dataEntry.getKey(),
dataEntry.getValue(),
cameraIndex2);
dcService.publishEvent(pipelineSettingChangeEvent);
}
} else {
logger.warn("Unknown message for PSC: " + data.keySet().iterator().next());
}
break;
}
}
} catch (Exception ex) {
logger.error("unknown booboo");
ex.printStackTrace();
}
}
} catch (IOException e) {
// TODO: log
e.printStackTrace();
}
}
public static void sendMessage(Object message, WsContext user) throws JsonProcessingException {
// 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);
}
public static void broadcastMessage(Object message, WsContext userToSkip)
// TODO: change to use the DataFlow system
public void broadcastMessage(Object message, WsContext userToSkip)
throws JsonProcessingException {
for (WsContext user : users) {
if (user != userToSkip) {
sendMessage(message, user);
}
}
return;
}
public static void broadcastMessage(Object message) throws JsonProcessingException {
broadcastMessage(message, null);
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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 SocketMessageCommandType {
SMCT_DELETECURRENTPIPELINE("deleteCurrentPipeline"),
SMCT_SAVE("save");
public final String entryValue;
SocketMessageCommandType(String entryValue) {
this.entryValue = entryValue;
}
public static SocketMessageCommandType fromEntryKey(String entryValue) {
if (entryValue.equalsIgnoreCase(SMCT_SAVE.entryValue)) return SMCT_SAVE;
else return SMCT_DELETECURRENTPIPELINE;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
public enum SocketMessageType {
SMT_DRIVERMODE("driverMode"),
SMT_CHANGECAMERANAME("changeCameraName"),
SMT_CHANGEPIPELINENAME("changePipelineName"),
SMT_ADDNEWPIPELINE("addNewPipeline"),
SMT_COMMAND("command"),
SMT_CURRENTCAMERA("currentCamera"),
SMT_PIPELINESETTINGCHANGE("changePipelineSetting"),
SMT_CURRENTPIPELINE("currentPipeline"),
SMT_ISPNPCALIBRATION("isPNPCalibration"),
SMT_TAKECALIBRATIONSNAPSHOT("takeCalibrationSnapshot");
public final String entryKey;
SocketMessageType(String entryKey) {
this.entryKey = entryKey;
}
private static final Map<String, SocketMessageType> entryKeyToValueMap = new HashMap<>();
static {
for (var value : EnumSet.allOf(SocketMessageType.class)) {
entryKeyToValueMap.put(value.entryKey, value);
}
}
public static SocketMessageType fromEntryKey(String entryKey) {
return entryKeyToValueMap.get(entryKey);
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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;
import java.util.Collections;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.DataChangeDestination;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.DataChangeSource;
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;
public class UIInboundSubscriber extends DataChangeSubscriber {
public UIInboundSubscriber() {
super(
Collections.singletonList(DataChangeSource.DCS_WEBSOCKET),
Collections.singletonList(DataChangeDestination.DCD_GENSETTINGS));
}
@Override
public void onDataChangeEvent(DataChangeEvent event) {
if (event instanceof IncomingWebSocketEvent) {
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);
DataChangeService.getInstance().publishEvent(message);
}
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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;
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;
import org.photonvision.common.dataflow.events.DataChangeEvent;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
@SuppressWarnings("rawtypes")
/*
* DO NOT use logging in this class. If you do, the logs will recuse forever!
*/
class UIOutboundSubscriber extends DataChangeSubscriber {
Logger logger = new Logger(UIOutboundSubscriber.class, LogGroup.Server);
private final SocketHandler socketHandler;
public UIOutboundSubscriber(SocketHandler socketHandler) {
super(DataChangeSource.AllSources, Collections.singletonList(DataChangeDestination.DCD_UI));
this.socketHandler = socketHandler;
}
@Override
public void onDataChangeEvent(DataChangeEvent event) {
if (event instanceof OutgoingUIEvent) {
var thisEvent = (OutgoingUIEvent) event;
try {
switch (thisEvent.updateType) {
case BROADCAST:
{
// logger.debug("Broadcasting message");
if (event.data instanceof HashMap) {
var data = (HashMap) event.data;
socketHandler.broadcastMessage(data, null);
} else {
socketHandler.broadcastMessage(event.data, null);
}
break;
}
case SINGLEUSER:
{
// logger.debug("Sending single user message");
if (event.data instanceof Pair) {
var pair = (SocketHandler.SelectiveBroadcastPair) event.data;
socketHandler.broadcastMessage(pair.getLeft(), pair.getRight());
}
break;
}
}
} catch (JsonProcessingException e) {
// TODO: Log
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.NetworkConfig;
public class UISettings {
public NetworkConfig networkConfig;
public CameraConfiguration currentCameraConfiguration;
}

View File

@@ -0,0 +1,23 @@
/*
* 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
}