2020-07-02 22:02:21 -04:00
|
|
|
/*
|
2020-12-31 19:57:51 -08:00
|
|
|
* Copyright (C) Photon Vision.
|
2020-07-02 22:02:21 -04:00
|
|
|
*
|
|
|
|
|
* 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/>.
|
|
|
|
|
*/
|
2022-01-20 19:35:28 -08:00
|
|
|
|
2020-06-27 14:58:03 -07:00
|
|
|
package org.photonvision.server;
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
2020-06-27 14:58:03 -07:00
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import io.javalin.http.Context;
|
2020-08-14 12:39:21 -07:00
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileInputStream;
|
2021-12-03 22:08:51 -06:00
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.io.FileOutputStream;
|
2020-08-14 12:39:21 -07:00
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.nio.file.Path;
|
2021-12-03 22:08:51 -06:00
|
|
|
import java.nio.file.Paths;
|
2020-08-14 12:39:21 -07:00
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import org.apache.commons.io.FileUtils;
|
|
|
|
|
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;
|
2020-09-04 18:18:44 -07:00
|
|
|
import org.photonvision.common.hardware.Platform;
|
2020-09-15 11:19:36 -07:00
|
|
|
import org.photonvision.common.hardware.metrics.MetricsPublisher;
|
2020-08-14 12:39:21 -07:00
|
|
|
import org.photonvision.common.logging.LogGroup;
|
|
|
|
|
import org.photonvision.common.logging.Logger;
|
|
|
|
|
import org.photonvision.common.networking.NetworkManager;
|
2020-09-10 19:20:16 -07:00
|
|
|
import org.photonvision.common.util.ShellExec;
|
2022-01-09 16:02:32 -08:00
|
|
|
import org.photonvision.common.util.TimedTaskManager;
|
2021-12-03 22:08:51 -06:00
|
|
|
import org.photonvision.common.util.file.ProgramDirectoryUtilities;
|
2020-08-14 12:39:21 -07:00
|
|
|
import org.photonvision.vision.processes.VisionModuleManager;
|
2020-08-26 10:03:56 -07:00
|
|
|
import org.photonvision.vision.target.TargetModel;
|
2020-06-27 14:58:03 -07:00
|
|
|
|
|
|
|
|
public class RequestHandler {
|
2020-08-14 12:39:21 -07:00
|
|
|
private static final Logger logger = new Logger(RequestHandler.class, LogGroup.WebServer);
|
2020-06-27 14:58:03 -07:00
|
|
|
|
2020-06-28 04:40:43 -04:00
|
|
|
private static final ObjectMapper kObjectMapper = new ObjectMapper();
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
public static void onSettingUpload(Context ctx) {
|
|
|
|
|
var file = ctx.uploadedFile("zipData");
|
|
|
|
|
if (file != null) {
|
2020-11-07 19:21:07 -06:00
|
|
|
// Copy the file from the client to a temporary location
|
|
|
|
|
var tempFilePath =
|
2020-08-14 12:39:21 -07:00
|
|
|
new File(Path.of(System.getProperty("java.io.tmpdir"), file.getFilename()).toString());
|
2020-11-07 19:21:07 -06:00
|
|
|
tempFilePath.getParentFile().mkdirs();
|
2020-08-14 12:39:21 -07:00
|
|
|
try {
|
2020-11-07 19:21:07 -06:00
|
|
|
FileUtils.copyInputStreamToFile(file.getContent(), tempFilePath);
|
2020-08-14 12:39:21 -07:00
|
|
|
} catch (IOException e) {
|
2020-11-07 19:21:07 -06:00
|
|
|
logger.error("Exception while uploading settings file to temp folder!");
|
2020-08-14 12:39:21 -07:00
|
|
|
e.printStackTrace();
|
2020-11-07 19:21:07 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
} 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());
|
|
|
|
|
|
|
|
|
|
} 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());
|
|
|
|
|
|
|
|
|
|
} 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;
|
2020-08-14 12:39:21 -07:00
|
|
|
}
|
2020-11-07 19:21:07 -06:00
|
|
|
|
2020-09-04 18:18:44 -07:00
|
|
|
ctx.status(200);
|
|
|
|
|
logger.info("Settings uploaded, going down for restart.");
|
2022-01-09 16:02:32 -08:00
|
|
|
restartProgram();
|
2020-08-14 12:39:21 -07:00
|
|
|
} else {
|
2020-11-07 19:21:07 -06:00
|
|
|
logger.error("Couldn't read uploaded file! Ignoring.");
|
2020-09-04 18:18:44 -07:00
|
|
|
ctx.status(500);
|
2020-08-14 12:39:21 -07:00
|
|
|
}
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2021-12-03 22:08:51 -06:00
|
|
|
public static void onOfflineUpdate(Context ctx) {
|
|
|
|
|
logger.info("Handling offline update .jar upload...");
|
|
|
|
|
var file = ctx.uploadedFile("jarData");
|
|
|
|
|
logger.info("New .jar uploaded successfully.");
|
|
|
|
|
|
|
|
|
|
if (file != null) {
|
|
|
|
|
if (Platform.isRaspberryPi()) {
|
|
|
|
|
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...");
|
2022-01-09 16:02:32 -08:00
|
|
|
restartProgram();
|
2021-12-03 22:08:51 -06:00
|
|
|
|
|
|
|
|
} 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("Hot .jar replace currently only supported on Raspberry pi. Ignoring.");
|
|
|
|
|
ctx.status(500);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
logger.error("Couldn't read provided file for new .jar! Ignoring.");
|
|
|
|
|
ctx.status(500);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
public static void onGeneralSettings(Context context) throws JsonProcessingException {
|
|
|
|
|
Map<String, Object> map =
|
|
|
|
|
(Map<String, Object>) kObjectMapper.readValue(context.body(), Map.class);
|
|
|
|
|
|
2020-09-15 11:34:27 -07:00
|
|
|
var networkConfig = NetworkConfig.fromHashMap(map);
|
2020-08-14 12:39:21 -07:00
|
|
|
ConfigManager.getInstance().setNetworkSettings(networkConfig);
|
2020-08-17 13:25:28 -07:00
|
|
|
ConfigManager.getInstance().requestSave();
|
2020-08-14 12:39:21 -07:00
|
|
|
NetworkManager.getInstance().reinitialize();
|
2020-08-19 17:08:24 -07:00
|
|
|
NetworkTablesManager.getInstance().setConfig(networkConfig);
|
2020-06-28 04:40:43 -04:00
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
context.status(200);
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
@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");
|
|
|
|
|
|
2022-10-17 11:41:57 -05:00
|
|
|
// The only settings we actually care about are FOV
|
2020-08-14 12:39:21 -07:00
|
|
|
var fov = Double.parseDouble(settings.get("fov").toString());
|
|
|
|
|
|
2022-10-17 11:41:57 -05:00
|
|
|
logger.info(String.format("Setting camera %s's fov to %s", index, fov));
|
2020-08-14 12:39:21 -07:00
|
|
|
var module = VisionModuleManager.getInstance().getModule(index);
|
2022-10-17 11:41:57 -05:00
|
|
|
module.setFov(fov);
|
2020-08-14 12:39:21 -07:00
|
|
|
module.saveModule();
|
|
|
|
|
} catch (JsonProcessingException e) {
|
|
|
|
|
logger.error("Got invalid camera setting JSON from frontend!");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
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.status(200);
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
ctx.status(501);
|
|
|
|
|
logger.error("Got bad recode from zip to byte");
|
|
|
|
|
}
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
public static void onCalibrationEnd(Context ctx) {
|
2020-11-21 18:14:27 -08:00
|
|
|
logger.info("Calibrating camera! This will take a long time...");
|
2020-08-14 12:39:21 -07:00
|
|
|
var index = Integer.parseInt(ctx.body());
|
|
|
|
|
var calData = VisionModuleManager.getInstance().getModule(index).endCalibration();
|
|
|
|
|
if (calData == null) {
|
|
|
|
|
ctx.status(500);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx.result(String.valueOf(calData.standardDeviation));
|
|
|
|
|
ctx.status(200);
|
2020-11-21 18:14:27 -08:00
|
|
|
logger.info("Camera calibrated!");
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
public static void restartDevice(Context ctx) {
|
|
|
|
|
ctx.status(HardwareManager.getInstance().restartDevice() ? 200 : 500);
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-09 16:02:32 -08:00
|
|
|
public static void restartProgram(Context ctx) {
|
|
|
|
|
restartProgram();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void restartProgram() {
|
|
|
|
|
TimedTaskManager.getInstance().addOneShotTask(RequestHandler::restartProgramInternal, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-14 12:39:21 -07:00
|
|
|
/**
|
2022-01-10 11:56:45 -08:00
|
|
|
* Note that this doesn't actually restart the program itself -- instead, it relies on systemd or
|
|
|
|
|
* an equivalent.
|
|
|
|
|
*/
|
2022-01-09 16:02:32 -08:00
|
|
|
public static void restartProgramInternal() {
|
2020-09-10 19:20:16 -07:00
|
|
|
if (Platform.isRaspberryPi()) {
|
|
|
|
|
try {
|
2020-12-13 16:32:00 -08:00
|
|
|
new ShellExec().executeBashCommand("systemctl restart photonvision.service");
|
2020-09-10 19:20:16 -07:00
|
|
|
} catch (IOException e) {
|
|
|
|
|
logger.error("Could not restart device!", e);
|
|
|
|
|
System.exit(0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
System.exit(0);
|
|
|
|
|
}
|
2020-06-28 04:40:43 -04:00
|
|
|
}
|
2020-08-26 10:03:56 -07:00
|
|
|
|
2020-10-16 16:48:24 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 10:03:56 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VisionModuleManager.getInstance().getModule(data.index).setTargetModel(data.targetModel);
|
|
|
|
|
ctx.status(200);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 11:19:36 -07:00
|
|
|
public static void sendMetrics(Context ctx) {
|
|
|
|
|
MetricsPublisher.getInstance().publish();
|
2022-03-31 22:55:51 -04:00
|
|
|
// TimedTaskManager.getInstance().addOneShotTask(() -> RoborioFinder.getInstance().findRios(),
|
|
|
|
|
// 0);
|
2020-09-15 11:19:36 -07:00
|
|
|
ctx.status(200);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 10:03:56 -07:00
|
|
|
public static class UITargetData {
|
|
|
|
|
public int index;
|
|
|
|
|
public TargetModel targetModel;
|
|
|
|
|
}
|
2020-06-27 14:58:03 -07:00
|
|
|
}
|