mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-22 01:11:40 +00:00
Update backend to provide more useful info to frontend (#866)
This commit is contained in:
@@ -345,7 +345,7 @@ export default {
|
||||
this.previouslySelectedIndices = null;
|
||||
},
|
||||
switchToSettingsTab() {
|
||||
this.axios.post('http://' + this.$address + '/api/sendMetrics', {})
|
||||
this.axios.post('http://' + this.$address + '/api/utils/publishMetrics')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-snackbar
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
:timeout="2000"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
<v-row
|
||||
align="center"
|
||||
style="padding: 12px 12px 12px 24px"
|
||||
@@ -269,7 +277,12 @@ export default {
|
||||
duplicateDialog: false,
|
||||
showPipeTypeDialog: false,
|
||||
proposedPipelineType : 0,
|
||||
pipeIndexToDuplicate: undefined
|
||||
pipeIndexToDuplicate: undefined,
|
||||
snack: false,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: "",
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -347,16 +360,37 @@ export default {
|
||||
},
|
||||
saveCameraNameChange() {
|
||||
if (this.checkCameraName === "") {
|
||||
// this.handleInputWithIndex("changeCameraName", this.newCameraName);
|
||||
this.axios.post('http://' + this.$address + '/api/setCameraNickname',
|
||||
this.axios.post('http://' + this.$address + '/api/settings/camera/setNickname',
|
||||
{name: this.newCameraName, cameraIndex: this.$store.getters.currentCameraIndex})
|
||||
// eslint-disable-next-line
|
||||
.then(r => {
|
||||
this.$emit('camera-name-changed')
|
||||
.then(response => {
|
||||
this.$emit('camera-name-changed')
|
||||
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(e => {
|
||||
console.log("HTTP error while changing camera name " + e);
|
||||
this.$emit('camera-name-changed')
|
||||
.catch(error => {
|
||||
this.$emit('camera-name-changed')
|
||||
|
||||
if(error.response) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: error.response.data.text || error.response.data
|
||||
}
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
this.discardCameraNameChange();
|
||||
}
|
||||
@@ -404,7 +438,3 @@ export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
<v-col>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:disabled="isCalibrating"
|
||||
small
|
||||
style="width: 100%;"
|
||||
@click="$refs.importCalibrationFromCalibdb.click()"
|
||||
@@ -330,7 +331,7 @@
|
||||
style="border-radius: 5px;"
|
||||
/>
|
||||
<v-dialog
|
||||
v-model="snack"
|
||||
v-model="calibrationDialog"
|
||||
width="500px"
|
||||
:persistent="true"
|
||||
>
|
||||
@@ -399,12 +400,12 @@
|
||||
>
|
||||
|
||||
<v-snackbar
|
||||
v-model="uploadSnack"
|
||||
v-model="snack"
|
||||
top
|
||||
:color="uploadSnackData.color"
|
||||
timeout="-1"
|
||||
:color="snackbar.color"
|
||||
timeout="2000"
|
||||
>
|
||||
<span>{{ uploadSnackData.text }}</span>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
@@ -431,17 +432,17 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
snack: false,
|
||||
calibrationDialog: false,
|
||||
calibrationInProgress: false,
|
||||
calibrationFailed: false,
|
||||
filteredVideomodeIndex: 0,
|
||||
settingsValid: true,
|
||||
unfilteredStreamDivisors: [1, 2, 4],
|
||||
uploadSnackData: {
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: "",
|
||||
},
|
||||
uploadSnack: false,
|
||||
snack: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -617,43 +618,34 @@ export default {
|
||||
};
|
||||
|
||||
this.axios
|
||||
.post("http://" + this.$address + "/api/calibration/import", data, {
|
||||
.post("http://" + this.$address + "/api/calibration/importFromCalibDB", data, {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
})
|
||||
.then(() => {
|
||||
this.uploadSnackData = {
|
||||
color: "success",
|
||||
text:
|
||||
"Calibration imported successfully!",
|
||||
};
|
||||
this.uploadSnack = true;
|
||||
.then((response) => {
|
||||
this.snackbar = {
|
||||
color: response.status === 200 ? "success" : "error",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response) {
|
||||
this.uploadSnackData = {
|
||||
if (err.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading calibration file! Could not process provided file.",
|
||||
};
|
||||
} else if (err.request) {
|
||||
this.uploadSnackData = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading calibration file! No respond to upload attempt.",
|
||||
text: "Error while uploading calibration file! The backend didn't respond to the upload attempt.",
|
||||
};
|
||||
} else {
|
||||
this.uploadSnackData = {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while uploading calibration file!",
|
||||
};
|
||||
}
|
||||
this.uploadSnack = true;
|
||||
this.snack = true;
|
||||
});
|
||||
|
||||
})
|
||||
},
|
||||
closeDialog() {
|
||||
this.snack = false;
|
||||
this.calibrationDialog = false;
|
||||
this.calibrationInProgress = false;
|
||||
this.calibrationFailed = false;
|
||||
},
|
||||
@@ -747,15 +739,33 @@ export default {
|
||||
doc.save(`calibrationTarget-${config.type}.pdf`)
|
||||
},
|
||||
sendCameraSettings() {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", {
|
||||
"settings": this.cameraSettings,
|
||||
"index": this.$store.state.currentCameraIndex
|
||||
}).then(response => {
|
||||
if (response.status === 200) {
|
||||
this.$store.state.saveBar = true;
|
||||
this.axios.post("http://" + this.$address + "/api/settings/camera", {"settings": this.cameraSettings, "index": this.$store.state.currentCameraIndex})
|
||||
.then(response => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: error.response.data.text || error.response.data
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
},
|
||||
isCalibrated(resolution) {
|
||||
return this.$store.getters.currentCameraSettings.calibrations
|
||||
@@ -782,16 +792,13 @@ export default {
|
||||
sendCalibrationFinish() {
|
||||
console.log("finishing calibration for index " + this.$store.getters.currentCameraIndex);
|
||||
|
||||
this.snack = true;
|
||||
this.calibrationDialog = true;
|
||||
this.calibrationInProgress = true;
|
||||
|
||||
this.axios.post("http://" + this.$address + "/api/settings/endCalibration", {idx: this.$store.getters.currentCameraIndex})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
this.calibrationInProgress = false;
|
||||
} else {
|
||||
this.calibrationFailed = true;
|
||||
}
|
||||
this.axios.post("http://" + this.$address + "/api/calibration/end", {index: this.$store.getters.currentCameraIndex})
|
||||
.then(() => {
|
||||
// End calibration will always return a 200 code on success
|
||||
this.calibrationInProgress = false;
|
||||
}
|
||||
).catch(() => {
|
||||
this.calibrationFailed = true;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<a
|
||||
ref="exportLogFile"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="'http://' + this.$address + '/api/settings/photonvision-journalctl.txt'"
|
||||
:href="'http://' + this.$address + '/api/utils/logs/photonvision-journalctl.txt'"
|
||||
download="photonvision-journalctl.txt"
|
||||
/>
|
||||
</v-btn>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
/>
|
||||
<v-snackbar
|
||||
v-model="snackbar"
|
||||
:timeout="3000"
|
||||
:timeout="2000"
|
||||
top
|
||||
color="error"
|
||||
>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="$refs.importSettings.click()"
|
||||
@click="() => showImportDialog = true"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-import
|
||||
@@ -62,7 +62,6 @@
|
||||
Import Settings
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
@@ -77,7 +76,6 @@
|
||||
Export Settings
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
@@ -95,16 +93,11 @@
|
||||
<a
|
||||
ref="exportLogFile"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="
|
||||
'http://' +
|
||||
this.$address +
|
||||
'/api/settings/photonvision-journalctl.txt'
|
||||
"
|
||||
:href="'http://' + this.$address + '/api/utils/logs/photonvision-journalctl.txt'"
|
||||
download="photonvision-journalctl.txt"
|
||||
/>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
@@ -124,24 +117,71 @@
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
timeout="-1"
|
||||
:timeout="snackbarTimeout"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
|
||||
<!-- Special hidden upload input that gets 'clicked' when the user imports settings -->
|
||||
<input
|
||||
ref="importSettings"
|
||||
type="file"
|
||||
accept=".zip, .json"
|
||||
style="display: none;"
|
||||
@change="readImportedSettings"
|
||||
<v-dialog
|
||||
v-model="showImportDialog"
|
||||
width="600"
|
||||
@input="() => {
|
||||
importType = undefined;
|
||||
importFile = null;
|
||||
}"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card-title>Import Settings</v-card-title>
|
||||
<v-card-text>
|
||||
Upload and apply previously saved or exported PhotonVision settings to this device
|
||||
<v-row
|
||||
class="mt-6 ml-8"
|
||||
>
|
||||
<CVselect
|
||||
v-model="importType"
|
||||
name="Type"
|
||||
tooltip="Select the type of settings file you are trying to upload"
|
||||
:list="['All Settings', 'Hardware Config', 'Hardware Settings', 'Network Config']"
|
||||
:select-cols="10"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row
|
||||
class="mt-6 ml-8 mr-8"
|
||||
>
|
||||
<v-file-input
|
||||
:disabled="importType === undefined"
|
||||
:error-messages="importType === undefined ? 'Settings type not selected' : ''"
|
||||
:accept="importType === 0 ? '.zip' : '.json'"
|
||||
@change="(file) => importFile = file"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row
|
||||
class="mt-12 ml-8 mr-8 mb-1"
|
||||
style="display: flex; align-items: center; justify-content: center"
|
||||
align="center"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:disabled="importFile === null"
|
||||
@click="uploadSettings"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-import
|
||||
</v-icon>
|
||||
Import Settings
|
||||
</v-btn>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Special hidden link that gets 'clicked' when the user exports settings -->
|
||||
<a
|
||||
ref="exportSettings"
|
||||
style="color: black; text-decoration: none; display: none"
|
||||
:href="'http://' + this.$address + '/api/settings/photonvision_config.zip'"
|
||||
:href="`http://${this.$address}/api/settings/photonvision_config.zip`"
|
||||
download="photonvision-settings.zip"
|
||||
/>
|
||||
|
||||
@@ -156,18 +196,28 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import CVselect from "../../components/common/cv-select";
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line
|
||||
name: "DeviceControl",
|
||||
components: {
|
||||
CVselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
snack: false,
|
||||
snackbarTimeout: 2000,
|
||||
uploadPercentage: 0.0,
|
||||
showImportDialog: false,
|
||||
importType: undefined,
|
||||
importFile: null,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: "",
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -197,51 +247,117 @@ export default {
|
||||
metrics() {
|
||||
// console.log(this.$store.state.metrics);
|
||||
return this.$store.state.metrics;
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
restartProgram() {
|
||||
this.axios.post("http://" + this.$address + "/api/restartProgram", {});
|
||||
this.axios.post("http://" + this.$address + "/api/utils/restartProgram")
|
||||
.then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Successfully sent program restart request"
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
// This endpoint always return 204 regardless of outcome
|
||||
if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
},
|
||||
restartDevice() {
|
||||
this.axios.post("http://" + this.$address + "/api/restartDevice", {});
|
||||
this.axios.post("http://" + this.$address + "/api/utils/restartDevice")
|
||||
.then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Successfully dispatched the restart command. It isn't confirmed if a device restart will occur."
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "The backend is unable to fulfil the request to restart the device."
|
||||
}
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
},
|
||||
readImportedSettings(event) {
|
||||
uploadSettings() {
|
||||
let formData = new FormData();
|
||||
formData.append("zipData", event.target.files[0]);
|
||||
this.axios
|
||||
.post("http://" + this.$address + "/api/settings/import", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
})
|
||||
.then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text:
|
||||
"Settings imported successfully! PhotonVision will restart in the background...",
|
||||
};
|
||||
this.snack = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response) {
|
||||
formData.append("data", this.importFile);
|
||||
|
||||
let settingsType
|
||||
switch (this.importType) {
|
||||
case 0:
|
||||
settingsType = ""
|
||||
break;
|
||||
case 1:
|
||||
settingsType = "/hardwareConfig"
|
||||
break;
|
||||
case 2:
|
||||
settingsType = "/hardwareSettings"
|
||||
break;
|
||||
case 3:
|
||||
settingsType = "/networkConfig"
|
||||
break;
|
||||
}
|
||||
|
||||
const requestUrl = `http://${this.$address}/api/settings${settingsType}`;
|
||||
this.axios.post(requestUrl, formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
})
|
||||
.then(response => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading settings file! Could not process provided file.",
|
||||
};
|
||||
} else if (err.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading settings file! No respond to upload attempt.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while uploading settings file!",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
});
|
||||
color: "success",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: error.response.data.text || error.response.data
|
||||
}
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
|
||||
this.showImportDialog = false
|
||||
this.importType = undefined;
|
||||
this.importFile = null;
|
||||
},
|
||||
doOfflineUpdate(event) {
|
||||
this.snackbar = {
|
||||
@@ -249,12 +365,13 @@ export default {
|
||||
text: "New Software Upload in Process...",
|
||||
};
|
||||
this.snack = true;
|
||||
this.snackbarTimeout = -1
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("jarData", event.target.files[0]);
|
||||
this.axios
|
||||
.post(
|
||||
"http://" + this.$address + "/api/settings/offlineUpdate",
|
||||
"http://" + this.$address + "/api/utils/offlineUpdate",
|
||||
formData,
|
||||
{
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
@@ -273,38 +390,37 @@ export default {
|
||||
}.bind(this),
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text:
|
||||
"New .jar copied successfully! PhotonVision will restart in the background...",
|
||||
};
|
||||
this.snack = true;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response) {
|
||||
.then(response => {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading new .jar file! Could not process provided file.",
|
||||
};
|
||||
} else if (err.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text:
|
||||
"Error while uploading new .jar file! No respond to upload attempt.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while uploading new .jar file!",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
});
|
||||
color: "success",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: error.response.data.text || error.response.data
|
||||
};
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
|
||||
// Reset the timeout after the loading bar
|
||||
this.snackbarTimeout = 2000
|
||||
},
|
||||
showLogs(event) {
|
||||
event;
|
||||
showLogs() {
|
||||
this.$store.state.logsOverlay = true;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
timeout="5000"
|
||||
timeout="2000"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
@@ -230,31 +230,40 @@ export default {
|
||||
};
|
||||
this.snack = true;
|
||||
|
||||
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings).then(
|
||||
response => {
|
||||
if (response.status === 200) {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: "Settings updated successfully"
|
||||
};
|
||||
this.snack = true;
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (error.status === 504 || changingStaticIp) {
|
||||
this.snackbar = {
|
||||
this.axios.post("http://" + this.$address + "/api/settings/general", this.settings)
|
||||
.then(response => {
|
||||
this.snackbar = {
|
||||
color: "success",
|
||||
text: response.data.text || response.data
|
||||
}
|
||||
this.snack = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
if (error.status === 504 || changingStaticIp) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: (error.response || {data: `Connection lost! Try the new static IP at ${this.staticIp}:5800 or ${this.hostname}:5800 ?`}).data
|
||||
text: `Connection lost! Try the new static IP at ${this.staticIp}:5800 or ${this.hostname}:5800?`
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: error.response.data.text || error.response.data
|
||||
}
|
||||
}
|
||||
} else if(error.request) {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: "Error while trying to process the request! The backend didn't respond.",
|
||||
};
|
||||
} else {
|
||||
this.snackbar = {
|
||||
color: "error",
|
||||
text: (error.response || {data: "Couldn't save settings"}).data
|
||||
color: "error",
|
||||
text: "An error occurred while trying to process the request.",
|
||||
};
|
||||
}
|
||||
this.snack = true;
|
||||
}
|
||||
)
|
||||
this.snack = true;
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
@@ -117,31 +117,12 @@
|
||||
</tr>
|
||||
</table>
|
||||
</v-row>
|
||||
|
||||
<v-snackbar
|
||||
v-model="snack"
|
||||
top
|
||||
:color="snackbar.color"
|
||||
timeout="-1"
|
||||
>
|
||||
<span>{{ snackbar.text }}</span>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Stats',
|
||||
data() {
|
||||
return {
|
||||
snack: false,
|
||||
uploadPercentage: 0.0,
|
||||
snackbar: {
|
||||
color: "success",
|
||||
text: ""
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.state.settings.general;
|
||||
|
||||
@@ -128,7 +128,7 @@ public class ConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveUploadedSettingsZip(File uploadPath) {
|
||||
public static boolean saveUploadedSettingsZip(File uploadPath) {
|
||||
// Unpack to /tmp/something/photonvision
|
||||
var folderPath = Path.of(System.getProperty("java.io.tmpdir"), "photonvision").toFile();
|
||||
folderPath.mkdirs();
|
||||
@@ -147,14 +147,16 @@ public class ConfigManager {
|
||||
|
||||
var sql = new SqlConfigProvider(getRootFolder());
|
||||
sql.setConfig(loadedConfig);
|
||||
sql.saveToDisk();
|
||||
return sql.saveToDisk();
|
||||
} else {
|
||||
// new structure -- just copy and save like we used to
|
||||
try {
|
||||
org.apache.commons.io.FileUtils.copyDirectory(folderPath, getRootFolder().toFile());
|
||||
logger.info("Copied settings successfully!");
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception copying uploaded settings!", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,16 +243,16 @@ public class ConfigManager {
|
||||
return imgFilePath.toPath();
|
||||
}
|
||||
|
||||
public void saveUploadedHardwareConfig(Path uploadPath) {
|
||||
m_provider.saveUploadedHardwareConfig(uploadPath);
|
||||
public boolean saveUploadedHardwareConfig(Path uploadPath) {
|
||||
return m_provider.saveUploadedHardwareConfig(uploadPath);
|
||||
}
|
||||
|
||||
public void saveUploadedHardwareSettings(Path uploadPath) {
|
||||
m_provider.saveUploadedHardwareSettings(uploadPath);
|
||||
public boolean saveUploadedHardwareSettings(Path uploadPath) {
|
||||
return m_provider.saveUploadedHardwareSettings(uploadPath);
|
||||
}
|
||||
|
||||
public void saveUploadedNetworkConfig(Path uploadPath) {
|
||||
m_provider.saveUploadedNetworkConfig(uploadPath);
|
||||
public boolean saveUploadedNetworkConfig(Path uploadPath) {
|
||||
return m_provider.saveUploadedNetworkConfig(uploadPath);
|
||||
}
|
||||
|
||||
public void requestSave() {
|
||||
|
||||
@@ -24,15 +24,15 @@ public abstract class ConfigProvider {
|
||||
|
||||
abstract void load();
|
||||
|
||||
abstract void saveToDisk();
|
||||
abstract boolean saveToDisk();
|
||||
|
||||
PhotonConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public abstract void saveUploadedHardwareConfig(Path uploadPath);
|
||||
public abstract boolean saveUploadedHardwareConfig(Path uploadPath);
|
||||
|
||||
public abstract void saveUploadedHardwareSettings(Path uploadPath);
|
||||
public abstract boolean saveUploadedHardwareSettings(Path uploadPath);
|
||||
|
||||
public abstract void saveUploadedNetworkConfig(Path uploadPath);
|
||||
public abstract boolean saveUploadedNetworkConfig(Path uploadPath);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToDisk() {
|
||||
public boolean saveToDisk() {
|
||||
// Delete old configs
|
||||
FileUtils.deleteDirectory(camerasFolder.toPath());
|
||||
|
||||
@@ -239,6 +239,7 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
}
|
||||
}
|
||||
logger.info("Settings saved!");
|
||||
return false; // TODO, deal with this. Do I need to?
|
||||
}
|
||||
|
||||
private HashMap<String, CameraConfiguration> loadCameraConfigs() {
|
||||
@@ -400,19 +401,19 @@ class LegacyConfigProvider extends ConfigProvider {
|
||||
return this.networkConfigFile.toPath();
|
||||
}
|
||||
|
||||
public void saveUploadedHardwareConfig(Path uploadPath) {
|
||||
FileUtils.deleteFile(this.getHardwareConfigFile());
|
||||
FileUtils.copyFile(uploadPath, this.getHardwareConfigFile());
|
||||
@Override
|
||||
public boolean saveUploadedHardwareConfig(Path uploadPath) {
|
||||
return FileUtils.replaceFile(uploadPath, this.getHardwareConfigFile());
|
||||
}
|
||||
|
||||
public void saveUploadedHardwareSettings(Path uploadPath) {
|
||||
FileUtils.deleteFile(this.getHardwareSettingsFile());
|
||||
FileUtils.copyFile(uploadPath, this.getHardwareSettingsFile());
|
||||
@Override
|
||||
public boolean saveUploadedHardwareSettings(Path uploadPath) {
|
||||
return FileUtils.replaceFile(uploadPath, this.getHardwareSettingsFile());
|
||||
}
|
||||
|
||||
public void saveUploadedNetworkConfig(Path uploadPath) {
|
||||
FileUtils.deleteFile(this.getNetworkConfigFile());
|
||||
FileUtils.copyFile(uploadPath, this.getNetworkConfigFile());
|
||||
@Override
|
||||
public boolean saveUploadedNetworkConfig(Path uploadPath) {
|
||||
return FileUtils.replaceFile(uploadPath, this.getNetworkConfigFile());
|
||||
}
|
||||
|
||||
public void requestSave() {
|
||||
|
||||
@@ -79,15 +79,6 @@ public class NetworkConfig {
|
||||
setShouldManage(shouldManage);
|
||||
}
|
||||
|
||||
public static NetworkConfig fromHashMap(Map<String, Object> map) {
|
||||
try {
|
||||
return new ObjectMapper().convertValue(map, NetworkConfig.class);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new NetworkConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> toHashMap() {
|
||||
try {
|
||||
return new ObjectMapper().convertValue(this, JacksonUtils.UIMap.class);
|
||||
|
||||
@@ -168,14 +168,15 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveToDisk() {
|
||||
public boolean saveToDisk() {
|
||||
logger.debug("Saving to disk");
|
||||
var conn = createConn();
|
||||
if (conn == null) return;
|
||||
if (conn == null) return false;
|
||||
|
||||
synchronized (m_mutex) {
|
||||
if (config == null) {
|
||||
logger.error("Config null! Cannot save");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
saveCameras(conn);
|
||||
@@ -185,11 +186,14 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
// TODO, does the file still save if the SQL connection isn't closed correctly? If so,
|
||||
// return false here.
|
||||
logger.error("SQL Err closing connection while saving to disk: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Settings saved!");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -366,14 +370,16 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void saveOneFile(String fname, Path path) {
|
||||
private boolean saveOneFile(String fname, Path path) {
|
||||
Connection conn = null;
|
||||
PreparedStatement statement1 = null;
|
||||
|
||||
try {
|
||||
conn = createConn();
|
||||
if (conn == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace this camera's row with the new settings
|
||||
var sqlString = "REPLACE INTO global (filename, contents) VALUES " + "(?,?);";
|
||||
|
||||
@@ -382,36 +388,38 @@ public class SqlConfigProvider extends ConfigProvider {
|
||||
statement1.executeUpdate();
|
||||
|
||||
conn.commit();
|
||||
return true;
|
||||
} catch (SQLException | IOException e) {
|
||||
logger.error("Err saving global", e);
|
||||
logger.error("Error while saving file to global: ", e);
|
||||
try {
|
||||
conn.rollback();
|
||||
} catch (SQLException e1) {
|
||||
logger.error("Err rolling back changes: ", e);
|
||||
logger.error("Error rolling back changes: ", e);
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (statement1 != null) statement1.close();
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
logger.error("SQL Err saving file " + fname, e);
|
||||
logger.error("SQL Error saving file " + fname, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUploadedHardwareConfig(Path uploadPath) {
|
||||
saveOneFile(TableKeys.HARDWARE_CONFIG, uploadPath);
|
||||
public boolean saveUploadedHardwareConfig(Path uploadPath) {
|
||||
return saveOneFile(TableKeys.HARDWARE_CONFIG, uploadPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUploadedHardwareSettings(Path uploadPath) {
|
||||
saveOneFile(TableKeys.HARDWARE_SETTINGS, uploadPath);
|
||||
public boolean saveUploadedHardwareSettings(Path uploadPath) {
|
||||
return saveOneFile(TableKeys.HARDWARE_SETTINGS, uploadPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUploadedNetworkConfig(Path uploadPath) {
|
||||
saveOneFile(TableKeys.NETWORK_CONFIG, uploadPath);
|
||||
public boolean saveUploadedNetworkConfig(Path uploadPath) {
|
||||
return saveOneFile(TableKeys.NETWORK_CONFIG, uploadPath);
|
||||
}
|
||||
|
||||
private HashMap<String, CameraConfiguration> loadCameraConfigs(Connection conn) {
|
||||
|
||||
@@ -58,24 +58,55 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteFile(Path path) {
|
||||
/**
|
||||
* Delete the file at the path.
|
||||
*
|
||||
* @param path file path to delete.
|
||||
* @return whether the operation was successful.
|
||||
*/
|
||||
public static boolean deleteFile(Path path) {
|
||||
try {
|
||||
Files.delete(path);
|
||||
return true;
|
||||
} catch (FileNotFoundException | NoSuchFileException fe) {
|
||||
logger.warn("Tried to delete file \"" + path + "\" but it did not exist");
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception deleting file \"" + path + "\"!", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyFile(Path src, Path dst) {
|
||||
/**
|
||||
* Copy a file from a source to a new destination.
|
||||
*
|
||||
* @param src the file path to copy.
|
||||
* @param dst the file path to replace.
|
||||
* @return whether the operation was successful.
|
||||
*/
|
||||
public static boolean copyFile(Path src, Path dst) {
|
||||
try {
|
||||
Files.copy(src, dst);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception copying file " + src + " to " + dst + "!", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the destination file with a new source.
|
||||
*
|
||||
* @param src the file path to replace with.
|
||||
* @param dst the file path to replace.
|
||||
* @return whether the operation was successful.
|
||||
*/
|
||||
public static boolean replaceFile(Path src, Path dst) {
|
||||
boolean fileDeleted = deleteFile(dst);
|
||||
boolean fileCopied = copyFile(src, dst);
|
||||
return fileDeleted && fileCopied;
|
||||
}
|
||||
|
||||
public static void setFilePerms(Path path) throws IOException {
|
||||
if (Platform.isLinux()) {
|
||||
File thisFile = path.toFile();
|
||||
|
||||
@@ -370,6 +370,6 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
Server.main(DEFAULT_WEBPORT);
|
||||
Server.start(DEFAULT_WEBPORT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
package org.photonvision.server;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.javalin.http.Context;
|
||||
import io.javalin.http.UploadedFile;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -29,8 +29,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.photonvision.common.configuration.ConfigManager;
|
||||
import org.photonvision.common.configuration.NetworkConfig;
|
||||
@@ -48,170 +47,312 @@ import org.photonvision.common.util.TimedTaskManager;
|
||||
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
||||
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
||||
import org.photonvision.vision.processes.VisionModuleManager;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
|
||||
public class RequestHandler {
|
||||
// Treat all 2XX calls as "INFO"
|
||||
// Treat all 4XX calls as "ERROR"
|
||||
// Treat all 5XX calls as "ERROR"
|
||||
|
||||
private static final Logger logger = new Logger(RequestHandler.class, LogGroup.WebServer);
|
||||
|
||||
private static final ObjectMapper kObjectMapper = new ObjectMapper();
|
||||
|
||||
public static void onSettingUpload(Context ctx) {
|
||||
var file = ctx.uploadedFile("zipData");
|
||||
if (file != null) {
|
||||
// Copy the file from the client to a temporary location
|
||||
var tempFilePath =
|
||||
new File(Path.of(System.getProperty("java.io.tmpdir"), file.getFilename()).toString());
|
||||
tempFilePath.getParentFile().mkdirs();
|
||||
try {
|
||||
FileUtils.copyInputStreamToFile(file.getContent(), tempFilePath);
|
||||
} catch (IOException e) {
|
||||
logger.error("Exception while uploading settings file to temp folder!");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
public static void onSettingsImportRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("data");
|
||||
|
||||
// Process the file by its extension
|
||||
if (file.getExtension().contains("zip")) {
|
||||
// .zip files are assumed to be full packages of configuration files
|
||||
logger.debug("Processing uploaded settings zip " + file.getFilename());
|
||||
ConfigManager.saveUploadedSettingsZip(tempFilePath);
|
||||
if (file == null) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"No File was sent with the request. Make sure that the settings zip is sent at the key 'data'");
|
||||
logger.error(
|
||||
"No File was sent with the request. Make sure that the settings zip is sent at the key 'data'");
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (file.getFilename().equals(ConfigManager.HW_CFG_FNAME)) {
|
||||
// Filenames matching the hardware config .json file are assumed to be
|
||||
// hardware config .json's
|
||||
logger.debug("Processing uploaded hardware config " + file.getFilename());
|
||||
ConfigManager.getInstance().saveUploadedHardwareConfig(tempFilePath.toPath());
|
||||
if (!file.getExtension().contains("zip")) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The uploaded file was not of type 'zip'. The uploaded file should be a .zip file.");
|
||||
logger.error(
|
||||
"The uploaded file was not of type 'zip'. The uploaded file should be a .zip file.");
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (file.getFilename().equals(ConfigManager.HW_SET_FNAME)) {
|
||||
// Filenames matching the hardware settings .json file are assumed to be
|
||||
// hardware settings.json's
|
||||
logger.debug("Processing uploaded hardware settings" + file.getFilename());
|
||||
ConfigManager.getInstance().saveUploadedHardwareSettings(tempFilePath.toPath());
|
||||
// Create a temp file
|
||||
var tempFilePath = handleTempFileCreation(file);
|
||||
|
||||
} else if (file.getFilename().equals(ConfigManager.NET_SET_FNAME)) {
|
||||
// Filenames matching the network config .json file are assumed to be
|
||||
// network config .json's
|
||||
logger.debug("Processing uploaded network config " + file.getFilename());
|
||||
ConfigManager.getInstance().saveUploadedNetworkConfig(tempFilePath.toPath());
|
||||
|
||||
} else {
|
||||
logger.error(
|
||||
"Couldn't apply provided settings file - did not recognize "
|
||||
+ file.getFilename()
|
||||
+ " as a supported file.");
|
||||
ctx.status(500);
|
||||
return;
|
||||
}
|
||||
if (tempFilePath.isEmpty()) {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while creating a temporary copy of the file");
|
||||
logger.error("There was an error while creating a temporary copy of the file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigManager.saveUploadedSettingsZip(tempFilePath.get())) {
|
||||
ctx.status(200);
|
||||
logger.info("Settings uploaded, going down for restart.");
|
||||
restartProgram();
|
||||
ctx.result("Successfully saved the uploaded settings zip");
|
||||
logger.info("Successfully saved the uploaded settings zip");
|
||||
} else {
|
||||
logger.error("Couldn't read uploaded file! Ignoring.");
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while saving the uploaded zip file");
|
||||
logger.error("There was an error while saving the uploaded zip file");
|
||||
}
|
||||
}
|
||||
|
||||
public static void onOfflineUpdate(Context ctx) {
|
||||
logger.info("Handling offline update .jar upload...");
|
||||
var file = ctx.uploadedFile("jarData");
|
||||
logger.info("New .jar uploaded successfully.");
|
||||
public static void onSettingsExportRequest(Context ctx) {
|
||||
logger.info("Exporting Settings to ZIP Archive");
|
||||
|
||||
if (file != null) {
|
||||
try {
|
||||
Path filePath =
|
||||
Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar");
|
||||
File targetFile = new File(filePath.toString());
|
||||
var stream = new FileOutputStream(targetFile);
|
||||
|
||||
logger.info(
|
||||
"Streaming user-provided " + file.getFilename() + " into " + targetFile.toString());
|
||||
|
||||
file.getContent().transferTo(stream);
|
||||
stream.close();
|
||||
|
||||
ctx.status(200);
|
||||
logger.info("New .jar in place, going down for restart...");
|
||||
restartProgram();
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.error(
|
||||
".jar of this program could not be found. How the heck this program started in the first place is a mystery.");
|
||||
ctx.status(500);
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not overwrite the .jar for this instance of photonvision.");
|
||||
ctx.status(500);
|
||||
}
|
||||
} else {
|
||||
logger.error("Couldn't read provided file for new .jar! Ignoring.");
|
||||
ctx.status(500);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void onGeneralSettings(Context context) throws JsonProcessingException {
|
||||
Map<String, Object> map =
|
||||
(Map<String, Object>) kObjectMapper.readValue(context.body(), Map.class);
|
||||
|
||||
var networkConfig = NetworkConfig.fromHashMap(map);
|
||||
ConfigManager.getInstance().setNetworkSettings(networkConfig);
|
||||
ConfigManager.getInstance().requestSave();
|
||||
NetworkManager.getInstance().reinitialize();
|
||||
NetworkTablesManager.getInstance().setConfig(networkConfig);
|
||||
|
||||
context.status(200);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void onCameraSettingsSave(Context context) {
|
||||
try {
|
||||
var settingsAndIndex = kObjectMapper.readValue(context.body(), Map.class);
|
||||
logger.info("Got cam setting json from frontend!\n" + settingsAndIndex.toString());
|
||||
var settings = (HashMap<String, Object>) settingsAndIndex.get("settings");
|
||||
int index = (Integer) settingsAndIndex.get("index");
|
||||
|
||||
// The only settings we actually care about are FOV
|
||||
var fov = Double.parseDouble(settings.get("fov").toString());
|
||||
|
||||
logger.info(String.format("Setting camera %s's fov to %s", index, fov));
|
||||
var module = VisionModuleManager.getInstance().getModule(index);
|
||||
module.setFov(fov);
|
||||
module.saveModule();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Got invalid camera setting JSON from frontend!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onSettingsDownload(Context ctx) {
|
||||
logger.info("exporting settings to download...");
|
||||
try {
|
||||
var zip = ConfigManager.getInstance().getSettingsFolderAsZip();
|
||||
var stream = new FileInputStream(zip);
|
||||
logger.info("Uploading settings with size " + stream.available());
|
||||
ctx.result(stream);
|
||||
|
||||
ctx.contentType("application/zip");
|
||||
ctx.header("Content-Disposition: attachment; filename=\"photonvision-settings-export.zip\"");
|
||||
ctx.header(
|
||||
"Content-Disposition", "attachment; filename=\"photonvision-settings-export.zip\"");
|
||||
|
||||
ctx.result(stream);
|
||||
ctx.status(200);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ctx.status(501);
|
||||
logger.error("Got bad recode from zip to byte");
|
||||
logger.error("Unable to export settings archive, bad recode from zip to byte");
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while exporting the settings archive");
|
||||
}
|
||||
}
|
||||
|
||||
private static ShellExec shell = new ShellExec();
|
||||
public static void onHardwareConfigRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("data");
|
||||
|
||||
public static void onExportCurrentLogs(Context ctx) {
|
||||
if (!Platform.isLinux()) {
|
||||
logger.warn("Cannot export journalctl on non-Linux platforms! Ignoring");
|
||||
if (file == null) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"No File was sent with the request. Make sure that the hardware config json is sent at the key 'data'");
|
||||
logger.error(
|
||||
"No File was sent with the request. Make sure that the hardware config json is sent at the key 'data'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.getExtension().contains("json")) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
logger.error(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temp file
|
||||
var tempFilePath = handleTempFileCreation(file);
|
||||
|
||||
if (tempFilePath.isEmpty()) {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while creating a temporary copy of the file");
|
||||
logger.error("There was an error while creating a temporary copy of the file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigManager.getInstance().saveUploadedHardwareConfig(tempFilePath.get().toPath())) {
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully saved the uploaded hardware config");
|
||||
logger.info("Successfully saved the uploaded hardware config");
|
||||
} else {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while saving the uploaded hardware config");
|
||||
logger.error("There was an error while saving the uploaded hardware config");
|
||||
}
|
||||
}
|
||||
|
||||
public static void onHardwareSettingsRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("data");
|
||||
|
||||
if (file == null) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"No File was sent with the request. Make sure that the hardware settings json is sent at the key 'data'");
|
||||
logger.error(
|
||||
"No File was sent with the request. Make sure that the hardware settings json is sent at the key 'data'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.getExtension().contains("json")) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
logger.error(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temp file
|
||||
var tempFilePath = handleTempFileCreation(file);
|
||||
|
||||
if (tempFilePath.isEmpty()) {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while creating a temporary copy of the file");
|
||||
logger.error("There was an error while creating a temporary copy of the file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigManager.getInstance().saveUploadedHardwareSettings(tempFilePath.get().toPath())) {
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully saved the uploaded hardware settings");
|
||||
logger.info("Successfully saved the uploaded hardware settings");
|
||||
} else {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while saving the uploaded hardware settings");
|
||||
logger.error("There was an error while saving the uploaded hardware settings");
|
||||
}
|
||||
}
|
||||
|
||||
public static void onNetworkConfigRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("data");
|
||||
|
||||
if (file == null) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"No File was sent with the request. Make sure that the network config json is sent at the key 'data'");
|
||||
logger.error(
|
||||
"No File was sent with the request. Make sure that the network config json is sent at the key 'data'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.getExtension().contains("json")) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
logger.error(
|
||||
"The uploaded file was not of type 'json'. The uploaded file should be a .json file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temp file
|
||||
var tempFilePath = handleTempFileCreation(file);
|
||||
|
||||
if (tempFilePath.isEmpty()) {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while creating a temporary copy of the file");
|
||||
logger.error("There was an error while creating a temporary copy of the file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfigManager.getInstance().saveUploadedNetworkConfig(tempFilePath.get().toPath())) {
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully saved the uploaded network config");
|
||||
logger.info("Successfully saved the uploaded network config");
|
||||
} else {
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while saving the uploaded network config");
|
||||
logger.error("There was an error while saving the uploaded network config");
|
||||
}
|
||||
}
|
||||
|
||||
public static void onOfflineUpdateRequest(Context ctx) {
|
||||
var file = ctx.uploadedFile("jarData");
|
||||
|
||||
if (file == null) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"No File was sent with the request. Make sure that the new jar is sent at the key 'jarData'");
|
||||
logger.error(
|
||||
"No File was sent with the request. Make sure that the new jar is sent at the key 'jarData'");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.getExtension().contains("jar")) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The uploaded file was not of type 'jar'. The uploaded file should be a .jar file.");
|
||||
logger.error(
|
||||
"The uploaded file was not of type 'jar'. The uploaded file should be a .jar file.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Path filePath =
|
||||
Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "photonvision.jar");
|
||||
File targetFile = new File(filePath.toString());
|
||||
var stream = new FileOutputStream(targetFile);
|
||||
|
||||
file.getContent().transferTo(stream);
|
||||
stream.close();
|
||||
|
||||
ctx.status(200);
|
||||
ctx.result(
|
||||
"Offline update successfully complete. PhotonVision will restart in the background.");
|
||||
logger.info(
|
||||
"Offline update successfully complete. PhotonVision will restart in the background.");
|
||||
restartProgram();
|
||||
} catch (FileNotFoundException e) {
|
||||
ctx.result("The current program jar file couldn't be found.");
|
||||
ctx.status(500);
|
||||
logger.error("The current program jar file couldn't be found.", e);
|
||||
} catch (IOException e) {
|
||||
ctx.result("Unable to overwrite the existing program with the new program.");
|
||||
ctx.status(500);
|
||||
logger.error("Unable to overwrite the existing program with the new program.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onGeneralSettingsRequest(Context ctx) {
|
||||
NetworkConfig config;
|
||||
try {
|
||||
config = kObjectMapper.readValue(ctx.body(), NetworkConfig.class);
|
||||
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully saved general settings");
|
||||
logger.info("Successfully saved general settings");
|
||||
} catch (JsonProcessingException e) {
|
||||
// If the settings can't be parsed, use the default network settings
|
||||
config = new NetworkConfig();
|
||||
|
||||
ctx.status(400);
|
||||
ctx.result("The provided general settings were malformed");
|
||||
logger.error("The provided general settings were malformed", e);
|
||||
}
|
||||
|
||||
ConfigManager.getInstance().setNetworkSettings(config);
|
||||
ConfigManager.getInstance().requestSave();
|
||||
|
||||
NetworkManager.getInstance().reinitialize();
|
||||
|
||||
NetworkTablesManager.getInstance().setConfig(config);
|
||||
}
|
||||
|
||||
public static void onCameraSettingsRequest(Context ctx) {
|
||||
try {
|
||||
var data = kObjectMapper.readTree(ctx.body());
|
||||
|
||||
int index = data.get("index").asInt();
|
||||
double fov = kObjectMapper.readTree(data.get("settings").asText()).get("fov").asDouble();
|
||||
|
||||
var module = VisionModuleManager.getInstance().getModule(index);
|
||||
module.setFov(fov);
|
||||
|
||||
module.saveModule();
|
||||
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully saved camera settings");
|
||||
logger.info("Successfully saved camera settings");
|
||||
} catch (JsonProcessingException e) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided camera settings were malformed");
|
||||
logger.error("The provided camera settings were malformed", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onLogExportRequest(Context ctx) {
|
||||
if (!Platform.isLinux()) {
|
||||
ctx.status(405);
|
||||
ctx.result("Logs can only be exported on a Linux platform");
|
||||
// INFO only log because this isn't ERROR worthy
|
||||
logger.info("Logs can only be exported on a Linux platform");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShellExec shell = new ShellExec();
|
||||
var tempPath = Files.createTempFile("photonvision-journalctl", ".txt");
|
||||
shell.executeBashCommand(
|
||||
"journalctl -u photonvision.service > " + tempPath.toAbsolutePath().toString());
|
||||
shell.executeBashCommand("journalctl -u photonvision.service > " + tempPath.toAbsolutePath());
|
||||
|
||||
while (!shell.isOutputCompleted()) {
|
||||
// TODO: add timeout
|
||||
@@ -220,150 +361,170 @@ public class RequestHandler {
|
||||
if (shell.getExitCode() == 0) {
|
||||
// Wrote to the temp file! Add it to the ctx
|
||||
var stream = new FileInputStream(tempPath.toFile());
|
||||
logger.info("Uploading settings with size " + stream.available());
|
||||
ctx.result(stream);
|
||||
ctx.contentType("application/zip");
|
||||
ctx.header("Content-Disposition: attachment; filename=\"photonvision-journalctl.txt\"");
|
||||
ctx.contentType("text/plain");
|
||||
ctx.header("Content-Disposition", "attachment; filename=\"photonvision-journalctl.txt\"");
|
||||
ctx.status(200);
|
||||
ctx.result(stream);
|
||||
logger.info("Uploading settings with size " + stream.available());
|
||||
} else {
|
||||
logger.error("Could not export journactl logs! (exit code != 0)");
|
||||
ctx.status(500);
|
||||
ctx.result("The journalctl service was unable to export logs");
|
||||
logger.error("The journalctl service was unable to export logs");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
logger.error("Could not export journactl logs! (IOexception)", e);
|
||||
ctx.status(500);
|
||||
ctx.result("There was an error while exporting journactl logs");
|
||||
logger.error("There was an error while exporting journactl logs", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onCalibrationEnd(Context ctx) {
|
||||
public static void onCalibrationEndRequest(Context ctx) {
|
||||
logger.info("Calibrating camera! This will take a long time...");
|
||||
|
||||
int index;
|
||||
|
||||
try {
|
||||
index = (int) kObjectMapper.readValue(ctx.body(), HashMap.class).get("idx");
|
||||
index = kObjectMapper.readTree(ctx.body()).get("index").asInt();
|
||||
|
||||
var calData = VisionModuleManager.getInstance().getModule(index).endCalibration();
|
||||
if (calData == null) {
|
||||
ctx.result("The calibration process failed");
|
||||
ctx.status(500);
|
||||
logger.error(
|
||||
"The calibration process failed. Calibration data for module at index ("
|
||||
+ index
|
||||
+ ") was null");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.result("Camera calibration successfully completed!");
|
||||
ctx.status(200);
|
||||
logger.info("Camera calibration successfully completed!");
|
||||
} catch (JsonProcessingException e) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The 'index' field was not found in the request. Please make sure the index of the vision module is specified with the 'index' key.");
|
||||
logger.error(
|
||||
"The 'index' field was not found in the request. Please make sure the index of the vision module is specified with the 'index' key.",
|
||||
e);
|
||||
} catch (Exception e) {
|
||||
logger.error("Cannot parse calibration idx", e);
|
||||
ctx.status(500);
|
||||
return;
|
||||
ctx.result("There was an error while ending calibration");
|
||||
logger.error("There was an error while ending calibration", e);
|
||||
}
|
||||
|
||||
var calData = VisionModuleManager.getInstance().getModule(index).endCalibration();
|
||||
if (calData == null) {
|
||||
ctx.status(500);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.result(String.valueOf(calData.standardDeviation));
|
||||
ctx.status(200);
|
||||
logger.info("Camera calibrated!");
|
||||
}
|
||||
|
||||
public static void restartDevice(Context ctx) {
|
||||
ctx.status(HardwareManager.getInstance().restartDevice() ? 200 : 500);
|
||||
public static void onCalibrationImportRequest(Context ctx) {
|
||||
var data = ctx.body();
|
||||
|
||||
try {
|
||||
var actualObj = kObjectMapper.readTree(data);
|
||||
|
||||
int cameraIndex = actualObj.get("cameraIndex").asInt();
|
||||
var payload = kObjectMapper.readTree(actualObj.get("payload").asText());
|
||||
var coeffs = CameraCalibrationCoefficients.parseFromCalibdbJson(payload);
|
||||
|
||||
var uploadCalibrationEvent =
|
||||
new IncomingWebSocketEvent<>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"calibrationUploaded",
|
||||
coeffs,
|
||||
cameraIndex,
|
||||
null);
|
||||
DataChangeService.getInstance().publishEvent(uploadCalibrationEvent);
|
||||
|
||||
ctx.status(200);
|
||||
ctx.result("Calibration imported successfully from CalibDB data!");
|
||||
logger.info("Calibration imported successfully from CalibDB data!");
|
||||
} catch (JsonProcessingException e) {
|
||||
ctx.status(400);
|
||||
ctx.result(
|
||||
"The Provided CalibDB data is malformed and cannot be parsed for the required fields.");
|
||||
logger.error(
|
||||
"The Provided CalibDB data is malformed and cannot be parsed for the required fields.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void restartProgram(Context ctx) {
|
||||
public static void onProgramRestartRequest(Context ctx) {
|
||||
// TODO, check if this was successful or not
|
||||
ctx.status(204);
|
||||
restartProgram();
|
||||
}
|
||||
|
||||
public static void restartProgram() {
|
||||
TimedTaskManager.getInstance().addOneShotTask(RequestHandler::restartProgramInternal, 0);
|
||||
public static void onDeviceRestartRequest(Context ctx) {
|
||||
ctx.status(HardwareManager.getInstance().restartDevice() ? 204 : 500);
|
||||
}
|
||||
|
||||
public static void onCameraNicknameChangeRequest(Context ctx) {
|
||||
try {
|
||||
var data = kObjectMapper.readTree(ctx.body());
|
||||
|
||||
String name = data.get("name").asText();
|
||||
int idx = data.get("cameraIndex").asInt();
|
||||
|
||||
VisionModuleManager.getInstance().getModule(idx).setCameraNickname(name);
|
||||
ctx.status(200);
|
||||
ctx.result("Successfully changed the camera name to: " + name);
|
||||
logger.info("Successfully changed the camera name to: " + name);
|
||||
} catch (JsonProcessingException e) {
|
||||
ctx.status(400);
|
||||
ctx.result("The provided nickname data was malformed");
|
||||
logger.error("The provided nickname data was malformed", e);
|
||||
|
||||
} catch (Exception e) {
|
||||
ctx.status(500);
|
||||
ctx.result("An error occurred while changing the camera's nickname");
|
||||
logger.error("An error occurred while changing the camera's nickname", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onMetricsPublishRequest(Context ctx) {
|
||||
HardwareManager.getInstance().publishMetrics();
|
||||
ctx.status(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that this doesn't actually restart the program itself -- instead, it relies on systemd or
|
||||
* an equivalent.
|
||||
* Create a temporary file using the UploadedFile from Javalin.
|
||||
*
|
||||
* @param file the uploaded file.
|
||||
* @return Temporary file. Empty if the temporary file was unable to be created.
|
||||
*/
|
||||
public static void restartProgramInternal() {
|
||||
if (Platform.isLinux()) {
|
||||
try {
|
||||
new ShellExec().executeBashCommand("systemctl restart photonvision.service");
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not restart device!", e);
|
||||
System.exit(0);
|
||||
}
|
||||
} else {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
private static Optional<File> handleTempFileCreation(UploadedFile file) {
|
||||
var tempFilePath =
|
||||
new File(Path.of(System.getProperty("java.io.tmpdir"), file.getFilename()).toString());
|
||||
tempFilePath.getParentFile().mkdirs();
|
||||
|
||||
public static void importCalibrationFromCalibdb(Context ctx) {
|
||||
var file = ctx.body();
|
||||
|
||||
if (file != null) {
|
||||
// check if it's a JSON file
|
||||
// Load using Jackson
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode actualObj = mapper.readTree(file);
|
||||
|
||||
int cameraIndex = actualObj.get("cameraIndex").asInt();
|
||||
String filename = actualObj.get("filename").asText();
|
||||
var payload = mapper.readTree(actualObj.get("payload").asText());
|
||||
|
||||
var coeffs = CameraCalibrationCoefficients.parseFromCalibdbJson(payload);
|
||||
|
||||
var uploadCalibrationEvent =
|
||||
new IncomingWebSocketEvent<CameraCalibrationCoefficients>(
|
||||
DataChangeDestination.DCD_ACTIVEMODULE,
|
||||
"calibrationUploaded",
|
||||
coeffs,
|
||||
(Integer) cameraIndex,
|
||||
null);
|
||||
DataChangeService.getInstance().publishEvent(uploadCalibrationEvent);
|
||||
|
||||
ctx.status(200);
|
||||
logger.info("Calibration added!");
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not parse cal metaJSON!");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ctx.status(500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCameraNickname(Context ctx) {
|
||||
try {
|
||||
var data = kObjectMapper.readValue(ctx.body(), HashMap.class);
|
||||
String name = String.valueOf(data.get("name"));
|
||||
int idx = Integer.parseInt(String.valueOf(data.get("cameraIndex")));
|
||||
VisionModuleManager.getInstance().getModule(idx).setCameraNickname(name);
|
||||
ctx.status(200);
|
||||
return;
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ctx.status(500);
|
||||
}
|
||||
|
||||
public static void uploadPnpModel(Context ctx) {
|
||||
UITargetData data;
|
||||
try {
|
||||
data = kObjectMapper.readValue(ctx.body(), UITargetData.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
ctx.status(500);
|
||||
return;
|
||||
FileUtils.copyInputStreamToFile(file.getContent(), tempFilePath);
|
||||
} catch (IOException e) {
|
||||
logger.error(
|
||||
"There was an error while uploading " + file.getFilename() + " to the temp folder!");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
VisionModuleManager.getInstance().getModule(data.index).setTargetModel(data.targetModel);
|
||||
ctx.status(200);
|
||||
return Optional.of(tempFilePath);
|
||||
}
|
||||
|
||||
public static void sendMetrics(Context ctx) {
|
||||
HardwareManager.getInstance().publishMetrics();
|
||||
// TimedTaskManager.getInstance().addOneShotTask(() ->
|
||||
// RoborioFinder.getInstance().findRios(),
|
||||
// 0);
|
||||
ctx.status(200);
|
||||
}
|
||||
|
||||
public static class UITargetData {
|
||||
public int index;
|
||||
public TargetModel targetModel;
|
||||
/**
|
||||
* Restart the running program. Note that this doesn't actually restart the program itself,
|
||||
* instead, it relies on systemd or an equivalent.
|
||||
*/
|
||||
private static void restartProgram() {
|
||||
TimedTaskManager.getInstance()
|
||||
.addOneShotTask(
|
||||
() -> {
|
||||
if (Platform.isLinux()) {
|
||||
try {
|
||||
new ShellExec().executeBashCommand("systemctl restart photonvision.service");
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not restart device!", e);
|
||||
System.exit(0);
|
||||
}
|
||||
} else {
|
||||
System.exit(0);
|
||||
}
|
||||
},
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,14 @@ package org.photonvision.server;
|
||||
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.http.staticfiles.Location;
|
||||
import java.util.StringJoiner;
|
||||
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.WebServer);
|
||||
|
||||
public static void main(int port) {
|
||||
public static void start(int port) {
|
||||
Javalin app =
|
||||
Javalin.create(
|
||||
config -> {
|
||||
@@ -34,15 +35,21 @@ public class Server {
|
||||
config.enableCorsForAllOrigins();
|
||||
|
||||
config.requestLogger(
|
||||
(ctx, ms) ->
|
||||
logger.debug(
|
||||
"Handled HTTP "
|
||||
+ ctx.req.getMethod()
|
||||
+ " request from "
|
||||
+ ctx.req.getRemoteHost()
|
||||
+ " in "
|
||||
+ ms.toString()
|
||||
+ "ms"));
|
||||
(ctx, ms) -> {
|
||||
StringJoiner joiner =
|
||||
new StringJoiner(" ")
|
||||
.add("Handled HTTP request of type")
|
||||
.add(ctx.req.getMethod())
|
||||
.add("from endpoint")
|
||||
.add(ctx.path())
|
||||
.add("for host")
|
||||
.add(ctx.req.getRemoteHost())
|
||||
.add("in")
|
||||
.add(ms.toString())
|
||||
.add("ms");
|
||||
|
||||
logger.debug(joiner.toString());
|
||||
});
|
||||
|
||||
config.wsLogger(
|
||||
ws ->
|
||||
@@ -61,7 +68,7 @@ public class Server {
|
||||
})));
|
||||
});
|
||||
|
||||
/*Web Socket Events for Data Exchage */
|
||||
/*Web Socket Events for Data Exchange */
|
||||
var dsHandler = DataSocketHandler.getInstance();
|
||||
app.ws(
|
||||
"/websocket_data",
|
||||
@@ -70,6 +77,7 @@ public class Server {
|
||||
ws.onClose(dsHandler::onClose);
|
||||
ws.onBinaryMessage(dsHandler::onBinaryMessage);
|
||||
});
|
||||
|
||||
/*Web Socket Events for Camera Streaming */
|
||||
var camDsHandler = CameraSocketHandler.getInstance();
|
||||
app.ws(
|
||||
@@ -80,20 +88,28 @@ public class Server {
|
||||
ws.onBinaryMessage(camDsHandler::onBinaryMessage);
|
||||
ws.onMessage(camDsHandler::onMessage);
|
||||
});
|
||||
|
||||
/*API Events*/
|
||||
app.post("/api/settings/import", RequestHandler::onSettingUpload);
|
||||
app.post("/api/settings/offlineUpdate", RequestHandler::onOfflineUpdate);
|
||||
app.get("/api/settings/photonvision_config.zip", RequestHandler::onSettingsDownload);
|
||||
app.get("/api/settings/photonvision-journalctl.txt", RequestHandler::onExportCurrentLogs);
|
||||
app.post("/api/settings/camera", RequestHandler::onCameraSettingsSave);
|
||||
app.post("/api/settings/general", RequestHandler::onGeneralSettings);
|
||||
app.post("/api/settings/endCalibration", RequestHandler::onCalibrationEnd);
|
||||
app.post("/api/restartDevice", RequestHandler::restartDevice);
|
||||
app.post("api/restartProgram", RequestHandler::restartProgram);
|
||||
app.post("api/vision/pnpModel", RequestHandler::uploadPnpModel);
|
||||
app.post("api/sendMetrics", RequestHandler::sendMetrics);
|
||||
app.post("api/setCameraNickname", RequestHandler::setCameraNickname);
|
||||
app.post("api/calibration/import", RequestHandler::importCalibrationFromCalibdb);
|
||||
// Settings
|
||||
app.post("/api/settings", RequestHandler::onSettingsImportRequest);
|
||||
app.get("/api/settings/photonvision_config.zip", RequestHandler::onSettingsExportRequest);
|
||||
app.post("/api/settings/hardwareConfig", RequestHandler::onHardwareConfigRequest);
|
||||
app.post("/api/settings/hardwareSettings", RequestHandler::onHardwareSettingsRequest);
|
||||
app.post("/api/settings/networkConfig", RequestHandler::onNetworkConfigRequest);
|
||||
app.post("/api/settings/general", RequestHandler::onGeneralSettingsRequest);
|
||||
app.post("/api/settings/camera", RequestHandler::onCameraSettingsRequest);
|
||||
app.post("/api/settings/camera/setNickname", RequestHandler::onCameraNicknameChangeRequest);
|
||||
|
||||
// Utilities
|
||||
app.post("/api/utils/offlineUpdate", RequestHandler::onOfflineUpdateRequest);
|
||||
app.get("/api/utils/logs/photonvision-journalctl.txt", RequestHandler::onLogExportRequest);
|
||||
app.post("/api/utils/restartProgram", RequestHandler::onProgramRestartRequest);
|
||||
app.post("/api/utils/restartDevice", RequestHandler::onDeviceRestartRequest);
|
||||
app.post("/api/utils/publishMetrics", RequestHandler::onMetricsPublishRequest);
|
||||
|
||||
// Calibration
|
||||
app.post("/api/calibration/end", RequestHandler::onCalibrationEndRequest);
|
||||
app.post("/api/calibration/importFromCalibDB", RequestHandler::onCalibrationImportRequest);
|
||||
|
||||
app.start(port);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user