Update log viewer, add uncalibrated modal (#108)

Adds a prettier log viewer, with the ability to filter logs. Also warns user and prevents switching into 3d mode if the resolution is uncalibrated.
This commit is contained in:
Matt
2020-09-04 18:18:44 -07:00
committed by GitHub
parent ec9e3dcf79
commit 8a7318f5dd
25 changed files with 1047 additions and 433 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,33 +8,33 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@femessage/log-viewer": "^1.4.1",
"@femessage/log-viewer": "^1.4.2",
"@vue/eslint-config-prettier": "^6.0.0",
"axios": "^0.19.2",
"core-js": "^2.6.11",
"downloadjs": "^1.4.7",
"material-design-icons-iconfont": "^5.0.1",
"msgpack5": "^4.2.1",
"vue": "^2.6.11",
"vue": "^2.6.12",
"vue-axios": "^2.1.5",
"vue-native-websocket": "git+https://github.com/PhotonVision/vue-native-websocket.git#7a32791",
"vue-router": "^3.3.2",
"vuetify": "^2.2.34",
"vuex": "^3.4.0"
"vue-router": "^3.4.3",
"vuetify": "^2.3.10",
"vuex": "^3.5.1"
},
"devDependencies": {
"@mdi/font": "^4.9.95",
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-eslint": "^4.4.1",
"@vue/cli-service": "^4.4.1",
"@vue/cli-plugin-eslint": "^4.5.4",
"@vue/cli-service": "^4.5.4",
"babel-eslint": "^10.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"papaparse": "^5.2.0",
"sass": "^1.26.5",
"papaparse": "^5.3.0",
"sass": "^1.26.10",
"sass-loader": "^7.1.0",
"vue-cli-plugin-vuetify": "^0.6.3",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.4.4"
"vue-template-compiler": "^2.6.12",
"vuetify-loader": "^1.6.0"
}
}

View File

@@ -40,6 +40,7 @@
<v-list-item
link
to="cameras"
ref="camerasTabOpener"
@click="switchToDriverMode()"
>
<v-list-item-icon>
@@ -71,7 +72,6 @@
<v-list-item-title>Documentation</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
v-if="this.$vuetify.breakpoint.mdAndUp"
link
@@ -111,63 +111,43 @@
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-content>
<v-main>
<v-container
fluid
fill-height
>
<v-layout>
<v-flex>
<router-view @save="startTimer" />
<v-snackbar
v-model="saveSnackbar"
:timeout="1000"
top
color="accent"
>
<div style="text-align: center;width: 100%;">
<h4>Saved All changes</h4>
</div>
</v-snackbar>
<div v-if="isLogger">
<keep-alive>
<log-view
class="loggerClass"
:log="log"
/>
</keep-alive>
</div>
<router-view v-on:switch-to-cameras="switchToDriverMode" />
</v-flex>
</v-layout>
</v-container>
</v-content>
</v-main>
<v-dialog
v-model="$store.state.logsOverlay"
width="1500"
dark
>
<logs />
</v-dialog>
</v-app>
</template>
<script>
import logView from '@femessage/log-viewer';
import Logs from "./views/LogsView"
export default {
name: 'App',
components: {
logView
Logs
},
data: () => ({
// Used so that we can switch back to the previously selected pipeline after camera calibration
previouslySelectedIndex: undefined,
timer: undefined,
isLogger: false,
log: "",
}),
computed: {
saveSnackbar: {
get() {
return this.$store.state.saveBar;
},
set(value) {
this.$store.commit("saveBar", value);
}
},
compact: {
get() {
if (this.$store.state.compactMode === undefined) {
@@ -186,8 +166,8 @@
created() {
document.addEventListener("keydown", e => {
switch (e.key) {
case '`' :
this.isLogger = !this.isLogger;
case "`":
this.$store.state.logsOverlay = !this.$store.state.logsOverlay;
break;
case "z":
if (e.ctrlKey && this.$store.getters.canUndo) {
@@ -250,22 +230,12 @@
toggleCompactMode() {
this.compact = !this.compact;
},
saveSettings() {
clearInterval(this.timer);
this.saveSnackbar = true;
this.handleInput("command", "save");
},
startTimer() {
if (this.timer !== undefined) {
clearInterval(this.timer);
}
this.timer = setInterval(this.saveSettings, 4000);
},
logMessage(message, level) {
const colors = ["\u001B[30m", "\u001B[31m", "\u001B[33m", "\u001B[32m", "\u001B[37m", "\u001B[36m"]
const reset = "\u001b[0m"
this.log += `${colors[level]}${message}${reset}\n`
console.log(message)
// eslint-disable-next-line no-unused-vars
logMessage(message, levelInt) {
this.$store.commit('logString', {
['level']: levelInt,
['message']: message
})
},
switchToDriverMode() {
this.previouslySelectedIndex = this.$store.getters.currentPipelineIndex;
@@ -307,16 +277,6 @@
object-fit: contain;
}
.loggerClass {
position: absolute;
bottom: 0;
height: 25% !important;
left: 0;
right: 0;
box-shadow: #282828 0 0 5px 1px;
background-color: #2b2b2b;
}
::-webkit-scrollbar {
width: 0.5em;
border-radius: 5px;
@@ -341,21 +301,23 @@
#title {
color: #ffd843;
}
span {
color: white;
}
</style>
<style>
/* Hack */
/* Hacks */
.v-divider {
border-color: white !important;
}
.v-input {
font-size: 1rem !important;
}
.v-input {
font-size: 1rem !important;
}
/* This is unfortunately the only way to override table background color */
.theme--dark.v-data-table > .v-data-table__wrapper > table > tbody > tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281 !important;
}
</style>
<style lang="scss">

View File

@@ -29,7 +29,6 @@
height: `${this.scale}%`,
cursor: (this.colorPicking ? `url(${require("../../assets/eyedropper.svg")}),` : "") + "default",
};
console.log(ret);
if (this.$vuetify.breakpoint.xl) {
ret["max-height"] = this.maxHeightXl;

View File

@@ -8,6 +8,7 @@
<template v-slot:activator="{ on, attrs }">
<span
style="cursor: text !important;"
class="white--text"
v-bind="attrs"
v-on="on"
>{{ text }}</span>

View File

@@ -5,7 +5,7 @@
align="center"
cols="12"
>
<span class="text--white">Target Location</span>
<span class="white--text">Target Location</span>
<canvas
id="canvasId"
class="mt-2"

View File

@@ -78,6 +78,7 @@
<v-menu
offset-y
auto
v-if="!$store.getters.isDriverMode"
>
<template v-slot:activator="{ on }">
<v-icon

View File

@@ -4,6 +4,7 @@ import Dashboard from "./views/PipelineView";
import Cameras from "./views/CamerasView";
import Settings from "./views/SettingsView";
import Docs from "./views/DocsView";
Vue.use(Router);
export default new Router({

View File

@@ -11,18 +11,14 @@ const set = key => (state, val) => {
export default new Vuex.Store({
modules: {
reflectivePipelineSettings: {
state: {
currentResolutionIndex: 0,
},
},
undoRedo: undoRedo
},
state: {
backendConnected: false,
colorPicking: false,
saveBar: false,
logsOverlay: false,
compactMode: localStorage.getItem("compactMode") === undefined ? undefined : localStorage.getItem("compactMode") === "true", // Compact mode is initially unset on purpose
logMessages: [],
currentCameraIndex: 0,
selectedOutputs: [0, 1], // 0 indicates normal, 1 indicates threshold
cameraSettings: [ // This is a list of objects representing the settings of all cameras
@@ -132,13 +128,17 @@ export default new Vuex.Store({
},
},
mutations: {
saveBar: set('saveBar'),
compactMode: set('compactMode'),
cameraSettings: set('cameraSettings'),
currentCameraIndex: set('currentCameraIndex'),
selectedOutputs: set('selectedOutputs'),
settings: set('settings'),
calibrationData: set('calibrationData'),
logString: (state, newStr) => {
const str = state.logMessages;
str.push(newStr)
Vue.set(state, 'logString', str)
},
solvePNPEnabled: (state, val) => {
state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.solvePNPEnabled = val;
@@ -205,6 +205,11 @@ export default new Vuex.Store({
currentPipelineResults: state => {
return state.pipelineResults;
},
isCalibrated: state => {
let resolution = state.cameraSettings[state.currentCameraIndex].videoFormatList[state.cameraSettings[state.currentCameraIndex].currentPipelineSettings.cameraVideoModeIndex];
return state.cameraSettings[state.currentCameraIndex].calibrations
.some(e => e.width === resolution.width && e.height === resolution.height);
},
cameraList: state => state.cameraSettings.map(it => it.nickname),
currentCameraSettings: state => state.cameraSettings[state.currentCameraIndex],
currentCameraIndex: state => state.currentCameraIndex,

View File

@@ -232,7 +232,7 @@
<v-icon left>
mdi-download
</v-icon>
Download Checkerboard
Download Chessboard
</v-btn>
<a
ref="calibrationFile"
@@ -415,8 +415,8 @@ export default {
return this.filteredVideomodeIndex
},
set(i) {
console.log(`Setting filtered index to ${i}`)
this.filteredVideomodeIndex = i
console.log(`Setting filtered index to ${i}`);
this.filteredVideomodeIndex = i;
this.$store.commit('mutateCalibrationState', {['videoModeIndex']: this.filteredResolutionList[i].index});
}
},
@@ -430,13 +430,13 @@ export default {
if(cal.width === resolution.width && cal.height === resolution.height) {
ret = cal
}
})
});
return ret;
},
downloadBoard() {
this.axios.get("http://" + this.$address + require('../assets/chessboard.png'), {responseType: 'blob'}).then((response) => {
require('downloadjs')(response.data, "Calibration Board", "image/png")
})
require('downloadjs')(response.data, "Calibration Board", "image/png");
});
},
sendCameraSettings() {
this.axios.post("http://" + this.$address + "/api/settings/camera", {
@@ -453,7 +453,7 @@ export default {
isCalibrated(resolution) {
return this.$store.getters.currentCameraSettings.calibrations
.some(e => e.width === resolution.width && e.height === resolution.height)
.some(e => e.width === resolution.width && e.height === resolution.height);
},
sendCalibrationMode() {
@@ -464,20 +464,20 @@ export default {
if (this.isCalibrating === true) {
data['takeCalibrationSnapshot'] = true
} else {
const calData = this.calibrationData
calData.isCalibrating = true
data['startPnpCalibration'] = calData
const calData = this.calibrationData;
calData.isCalibrating = true;
data['startPnpCalibration'] = calData;
console.log("starting calibration with index " + calData.videoModeIndex)
console.log("starting calibration with index " + calData.videoModeIndex);
}
this.$socket.send(this.$msgPack.encode(data));
},
sendCalibrationFinish() {
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex)
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex);
this.snackbar.text = "Calibrating...";
this.snackbar.color = "secondary"
this.snackbar.color = "secondary";
this.snack = true;
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", this.$store.getters.currentCameraIndex)
@@ -525,9 +525,4 @@ export default {
.v-data-table th,td {
font-size: 1rem !important;
}
/** This is unfortunately the only way to override table background color **/
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281;
}
</style>

View File

@@ -0,0 +1,130 @@
<template>
<v-card
dark
class="pt-3"
color="primary"
flat
>
<v-card-title>
View Program Logs
<v-btn
color="secondary"
style="margin-left: auto;"
depressed
@click="download('photonlog.log', rawLogs.map(it => it.message).join('\n'))"
>
<v-icon left>
mdi-download
</v-icon>
Download Log
</v-btn>
</v-card-title>
<div class="pr-6 pl-6">
<v-btn-toggle
v-model="logLevel"
dark
multiple
class="fill mb-4"
>
<v-btn
v-for="(level) in possibleLevelArray"
:key="level"
color="secondary"
class="fill"
>
{{ level }}
</v-btn>
</v-btn-toggle>
<!-- Logs -->
<v-virtual-scroll
:items="logMessageArray"
item-height="50"
height="600"
>
<template v-slot="{ item }">
<div :class="[getColor(item) + '--text', 'log-item']">{{ item.message }}</div>
</template>
</v-virtual-scroll>
</div>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
color="white"
text
@click="$store.state.logsOverlay = false"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "Logs",
components: {
},
data() {
return {
selectedLevel: [0, 1, 2],
possibleLevelArray: ['ERROR', 'WARN', 'INFO', 'DEBUG'],
colorArray: ['red', 'yellow','green', 'white'],
}
},
computed: {
rawLogs() {
return this.$store.state.logMessages;
},
logMessageArray() {
const logArray = this.$store.state.logMessages;
return logArray.filter(it => this.selectedLevel.includes(it.level));
},
logLevel: {
get() {
return this.selectedLevel
},
set(value) {
this.selectedLevel = value;
}
}
},
methods: {
getColor(message) {
return this.colorArray[message.level];
},
download(filename, text) {
const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
}
</script>
<style scoped>
.v-btn-toggle.fill {
width: 100%;
height: 100%;
}
.v-btn-toggle.fill > .v-btn {
width: 25%;
height: 100%;
}
</style>

View File

@@ -82,6 +82,7 @@
align="center"
class="pl-3 pr-3"
>
<!-- -->
<v-col lg="12">
<p style="color: white;">
Processing mode:
@@ -100,6 +101,7 @@
</v-btn>
<v-btn
color="secondary"
@click="on3DClick"
>
<v-icon>mdi-cube-outline</v-icon>
<span>3D</span>
@@ -180,7 +182,7 @@
</v-col>
</v-row>
</v-container>
<!-- snack bar -->
<!-- snack bar and modal -->
<v-snackbar
v-model="snackbar"
:timeout="3000"
@@ -196,6 +198,44 @@
Close
</v-btn>
</v-snackbar>
<v-dialog
v-model="dialog"
width="500"
>
<v-card
color="primary"
dark
>
<v-card-title>
Current resolution not calibrated
</v-card-title>
<v-card-text>
Because the current resolution {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].width }}
x {{ this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex].height }}
is not yet calibrated, 3D mode cannot be enabled. Please
<a
href="/#/cameras"
class="white--text"
@click="$emit('switch-to-cameras')"
> visit the Cameras tab</a> to calibrate this resolution. For now, SolvePNP will do nothing.
</v-card-text>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn
color="white"
text
@click="closeUncalibratedDialog"
>
OK
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
@@ -226,6 +266,8 @@
selectedTabsData: [0, 0, 0, 0],
snackbar: false,
counterData: 0,
dialog: false,
processingModeOverride: false
}
},
computed: {
@@ -293,11 +335,13 @@
},
processingMode: {
get() {
return this.$store.getters.currentPipelineSettings.solvePNPEnabled ? 1 : 0;
return (this.$store.getters.currentPipelineSettings.solvePNPEnabled || this.processingModeOverride) ? 1 : 0;
},
set(value) {
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
if (this.$store.getters.isCalibrated) {
this.$store.getters.currentPipelineSettings.solvePNPEnabled = value === 1;
this.handlePipelineUpdate("solvePNPEnabled", value === 1);
}
}
},
driverMode: {
@@ -350,6 +394,11 @@
}
},
methods: {
isCalibrated() {
const resolution = this.$store.getters.videoFormatList[this.$store.getters.currentPipelineSettings.cameraVideoModeIndex];
return this.$store.getters.currentCameraSettings.calibrations
.some(e => e.width === resolution.width && e.height === resolution.height)
},
onImageClick(event) {
// Only run on the input stream
if (event.target.alt !== "Stream0") return;
@@ -358,6 +407,18 @@
if (ref && ref[0])
ref[0].onClick(event)
},
on3DClick() {
if (!this.$store.getters.isCalibrated) {
this.dialog = true;
this.processingModeOverride = true;
}
},
closeUncalibratedDialog() {
this.dialog = false;
this.processingModeOverride = false;
// this.$store.getters.currentPipelineSettings.solvePNPEnabled = false;
this.handlePipelineUpdate("solvePNPEnabled", false);
}
}
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<span>Target Manipulation</span>
<span class="white--text">Target Manipulation</span>
<v-divider class="mt-2" />
<CVselect
@@ -31,7 +31,7 @@
@rollback="e=> rollback('outputShowMultipleTargets', e)"
/>
<span>Robot Offset</span>
<span class="white--text">Robot Offset</span>
<v-divider class="mt-2" />
<CVselect
v-model="offsetRobotOffsetMode"

View File

@@ -96,11 +96,6 @@
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
}
},
allow3D: {
get() {
return this.$store.getters.currentCameraSettings.calibrated;
}
}
},
mounted() {
let tmp = [];

View File

@@ -97,9 +97,4 @@
.v-data-table td {
font-family: monospace !important;
}
/** This is unfortunately the only way to override table background color **/
.theme--dark.v-data-table tbody tr:hover:not(.v-data-table__expanded__content):not(.v-data-table__empty-wrapper) {
background: #005281;
}
</style>

View File

@@ -67,6 +67,7 @@
v-model="snack"
top
:color="snackbar.color"
timeout="0"
>
<span>{{ snackbar.text }}</span>
</v-snackbar>
@@ -115,15 +116,16 @@ export default {
{headers: {"Content-Type": "multipart/form-data"}}).then(() => {
this.snackbar = {
color: "success",
text: "Settings imported successfully",
text: "Settings imported successfully! Program will now exit...",
};
this.snack = true;
}).catch(() => {
this.snackbar = {
color: "error",
text: "Couldn't import settings",
}
color: "success",
text: "Settings imported successfully! Program will now exit...",
};
this.snack = true;
});
this.snack = true;
},
}
}

View File

@@ -57,17 +57,17 @@ public class ConfigManager {
}
public static void saveUploadedSettingsZip(File uploadPath) {
logger.info(uploadPath.getAbsolutePath());
var folderPath = Path.of(System.getProperty("java.io.tmpdir"), "photonvision").toFile();
folderPath.mkdirs();
ZipUtil.unpack(uploadPath, folderPath);
FileUtils.deleteDirectory(getRootFolder());
try {
org.apache.commons.io.FileUtils.copyDirectory(folderPath, getRootFolder().toFile());
logger.info("Copied settings successfully!");
} catch (IOException e) {
e.printStackTrace();
logger.error("Exception copying uploaded settings!", e);
return;
}
System.exit(666);
}
public PhotonConfiguration getConfig() {
@@ -159,8 +159,6 @@ public class ConfigManager {
}
public void saveToDisk() {
logger.info("Saving settings...");
// Delete old configs
FileUtils.deleteDirectory(camerasFolder.toPath());
@@ -214,6 +212,7 @@ public class ConfigManager {
}
}
}
logger.info("Settings saved!");
}
private HashMap<String, CameraConfiguration> loadCameraConfigs() {
@@ -341,7 +340,7 @@ public class ConfigManager {
}
public void requestSave() {
logger.debug("Requesting save...");
logger.trace("Requesting save...");
saveRequestTimestamp = System.currentTimeMillis();
}

View File

@@ -18,12 +18,11 @@
package org.photonvision.common.logging;
public enum LogLevel {
OFF(0, Logger.ANSI_BLACK),
ERROR(1, Logger.ANSI_RED),
WARN(2, Logger.ANSI_YELLOW),
INFO(3, Logger.ANSI_GREEN),
DEBUG(4, Logger.ANSI_WHITE),
TRACE(5, Logger.ANSI_CYAN);
ERROR(0, Logger.ANSI_RED),
WARN(1, Logger.ANSI_YELLOW),
INFO(2, Logger.ANSI_GREEN),
DEBUG(3, Logger.ANSI_WHITE),
TRACE(4, Logger.ANSI_CYAN);
public final String colorCode;
public final int code;

View File

@@ -25,6 +25,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.lang3.tuple.Pair;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.DataChangeService;
import org.photonvision.common.dataflow.events.OutgoingUIEvent;
@@ -47,6 +48,11 @@ public class Logger {
private static final SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final List<Pair<String, LogLevel>> uiBacklog = new ArrayList<>();
private static boolean connected = false;
private static UILogAppender uiLogAppender = new UILogAppender();
private final String className;
private final LogGroup group;
@@ -97,7 +103,7 @@ public class Logger {
static {
currentAppenders.add(new ConsoleLogAppender());
currentAppenders.add(new UILogAppender());
currentAppenders.add(uiLogAppender);
addFileAppender(ConfigManager.getInstance().getLogPath());
}
@@ -126,6 +132,15 @@ public class Logger {
var formattedMessage = format(message, level, group, clazz, shouldColor);
a.log(formattedMessage, level);
}
if (!connected) uiBacklog.add(Pair.of(format(message, level, group, clazz, false), level));
}
public static void sendConnectedBacklog() {
for (var message : uiBacklog) {
uiLogAppender.log(message.getLeft(), message.getRight());
}
connected = true;
uiBacklog.clear();
}
public boolean shouldLog(LogLevel logLevel) {
@@ -244,6 +259,7 @@ public class Logger {
private static class FileLogAppender implements LogAppender {
private OutputStream out;
private boolean wantsFlush;
public FileLogAppender(Path logFilePath) {
try {
@@ -253,7 +269,10 @@ public class Logger {
"FileLogAppender",
() -> {
try {
out.flush();
if (wantsFlush) {
out.flush();
wantsFlush = false;
}
} catch (IOException ignored) {
}
},
@@ -269,6 +288,7 @@ public class Logger {
message += "\n";
try {
out.write(message.getBytes());
wantsFlush = true;
} catch (IOException e) {
e.printStackTrace();
}

View File

@@ -32,6 +32,7 @@ import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.configuration.NetworkConfig;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
import org.photonvision.common.hardware.HardwareManager;
import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.networking.NetworkManager;
@@ -56,9 +57,17 @@ public class RequestHandler {
e.printStackTrace();
}
ConfigManager.saveUploadedSettingsZip(tempZipPath);
// restartDevice();
ctx.status(200);
logger.info("Settings uploaded, going down for restart.");
if (!Platform.isRaspberryPi()) {
logger.info("(On non-PI platforms, the program may not restart manually...)");
}
System.exit(0);
} else {
logger.error("Couldn't read uploaded settings ZIP! Ignoring.");
ctx.status(500);
}
}

View File

@@ -53,7 +53,7 @@ public class Server {
ws ->
ws.onBinaryMessage(
ctx ->
logger.debug(
logger.trace(
() -> {
var insa = ctx.session.getRemote().getInetSocketAddress();
var host = insa.getAddress().toString() + ":" + insa.getPort();

View File

@@ -105,14 +105,15 @@ public class SocketHandler {
var entryValue = entry.getValue();
var socketMessageType = SocketMessageType.fromEntryKey(entryKey);
logger.debug(
"Got WS message: ["
+ socketMessageType
+ "] ==> ["
+ entryKey
+ "], ["
+ entryValue
+ "]");
logger.trace(
() ->
"Got WS message: ["
+ socketMessageType
+ "] ==> ["
+ entryKey
+ "], ["
+ entryValue
+ "]");
if (socketMessageType == null) {
logger.warn("Got unknown socket message type: " + entryKey);

View File

@@ -26,6 +26,7 @@ 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.logging.Logger;
public class UIInboundSubscriber extends DataChangeSubscriber {
@@ -46,6 +47,7 @@ public class UIInboundSubscriber extends DataChangeSubscriber {
var message =
new OutgoingUIEvent<>("fullsettings", settings, incomingWSEvent.originContext);
DataChangeService.getInstance().publishEvent(message);
Logger.sendConnectedBacklog();
}
}
}

View File

@@ -41,8 +41,21 @@ public class SolvePNPPipe
private final MatOfPoint2f imagePoints = new MatOfPoint2f();
private boolean hasWarned = false;
@Override
protected List<TrackedTarget> process(List<TrackedTarget> targetList) {
if (params.cameraCoefficients == null
|| params.cameraCoefficients.getCameraIntrinsicsMat() == null
|| params.cameraCoefficients.getCameraExtrinsicsMat() == null) {
if (!hasWarned) {
logger.warn(
"Cannot perform solvePNP an uncalibrated camera! Please calibrate this resolution...");
hasWarned = true;
}
return targetList;
}
for (var target : targetList) {
calculateTargetPose(target);
}
@@ -55,6 +68,7 @@ public class SolvePNPPipe
var corners = target.getTargetCorners();
if (corners == null
|| corners.isEmpty()
|| params.cameraCoefficients == null
|| params.cameraCoefficients.getCameraIntrinsicsMat() == null
|| params.cameraCoefficients.getCameraExtrinsicsMat() == null) {
return;

View File

@@ -40,6 +40,7 @@ public class VisionSourceManager {
final List<UsbCameraInfo> knownUsbCameras = new CopyOnWriteArrayList<>();
final List<CameraConfiguration> unmatchedLoadedConfigs = new CopyOnWriteArrayList<>();
private boolean hasWarned;
private static class SingletonHolder {
private static final VisionSourceManager INSTANCE = new VisionSourceManager();
@@ -132,12 +133,14 @@ public class VisionSourceManager {
// Match camera configs to physical cameras
var matchedCameras = matchUSBCameras(notYetLoadedCams, unmatchedLoadedConfigs);
unmatchedLoadedConfigs.removeAll(matchedCameras);
if (!unmatchedLoadedConfigs.isEmpty())
if (!unmatchedLoadedConfigs.isEmpty() && !hasWarned) {
logger.warn(
() ->
"After matching, "
+ unmatchedLoadedConfigs.size()
+ " configs remained unmatched. Is your camera disconnected?");
hasWarned = true;
}
// We add the matched cameras to the known camera list
for (var cam : notYetLoadedCams) {