diff --git a/deps/tools/Makefile b/deps/tools/Makefile index 6a76f86..9be0b4c 100644 --- a/deps/tools/Makefile +++ b/deps/tools/Makefile @@ -39,6 +39,7 @@ _cscore.so: ../robotpy-cscore/src/_cscore.cpp ../robotpy-cscore/src/ndarray_conv RPICONFIGSERVER_OBJS= \ rpiConfigServer_src/main.o \ + rpiConfigServer_src/Application.o \ rpiConfigServer_src/MyHttpConnection.o \ rpiConfigServer_src/NetworkSettings.o \ rpiConfigServer_src/SystemStatus.o \ diff --git a/deps/tools/rpiConfigServer_src/Application.cpp b/deps/tools/rpiConfigServer_src/Application.cpp new file mode 100644 index 0000000..681f48a --- /dev/null +++ b/deps/tools/rpiConfigServer_src/Application.cpp @@ -0,0 +1,148 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "Application.h" + +#include +#include +#include +#include + +#include "VisionStatus.h" + +#define TYPE_TAG "### TYPE:" + +std::shared_ptr Application::GetInstance() { + static auto inst = std::make_shared(private_init{}); + return inst; +} + +void Application::Set(wpi::StringRef appType, + std::function onFail) { + wpi::StringRef appDir; + wpi::StringRef appCommand; + + if (appType == "builtin") { + appCommand = "./multiCameraServer"; + } else if (appType == "example-java") { + appDir = "java-multiCameraServer"; + appCommand = + "env LD_LIBRARY_PATH=/usr/local/frc/lib java -jar " + "build/libs/java-multiCameraServer-all.jar"; + } else if (appType == "example-cpp") { + appDir = "cpp-multiCameraServer"; + appCommand = "./multiCameraServerExample"; + } else if (appType == "example-python") { + appDir = "python-multiCameraServer"; + appCommand = "./multiCameraServer.py"; + } else if (appType == "upload-java") { + appCommand = + "env LD_LIBRARY_PATH=/usr/local/frc/lib java -jar uploaded.jar"; + } else if (appType == "upload-cpp") { + appCommand = "./uploaded"; + } else if (appType == "upload-python") { + appCommand = "./uploaded.py"; + } else if (appType == "custom") { + return; + } else { + wpi::SmallString<64> msg; + msg = "unrecognized application type '"; + msg += appType; + msg += "'"; + onFail(msg); + return; + } + + { + // write file + std::error_code ec; + wpi::raw_fd_ostream os(EXEC_HOME "/runCamera", ec, wpi::sys::fs::F_Text); + if (ec) { + onFail("could not write " EXEC_HOME "/runCamera"); + return; + } + os << "#!/bin/sh\n"; + os << TYPE_TAG << ' ' << appType << '\n'; + os << "echo \"Waiting 5 seconds...\"\n"; + os << "sleep 5\n"; + if (!appDir.empty()) os << "cd " << appDir << '\n'; + os << "exec " << appCommand << '\n'; + } + + m_appType = appType; + + // terminate vision process so it reloads + VisionStatus::GetInstance()->Terminate(onFail); + + UpdateStatus(); +} + +void Application::Upload(wpi::ArrayRef contents, + std::function onFail) { + wpi::StringRef filename; + if (m_appType == "upload-java") { + filename = "/uploaded.jar"; + } else if (m_appType == "upload-cpp") { + filename = "/uploaded"; + } else if (m_appType == "upload-python") { + filename = "/uploaded.py"; + } else { + wpi::SmallString<64> msg; + msg = "cannot upload application type '"; + msg += m_appType; + msg += "'"; + onFail(msg); + return; + } + + wpi::SmallString<64> pathname; + pathname = EXEC_HOME; + pathname += filename; + + { + // write file + std::error_code ec; + wpi::raw_fd_ostream os(pathname, ec, wpi::sys::fs::F_None); + if (ec) { + wpi::SmallString<64> msg; + msg = "could not write "; + msg += pathname; + onFail(msg); + return; + } + os << contents; + } + + // terminate vision process so it reloads + VisionStatus::GetInstance()->Terminate(onFail); +} + +void Application::UpdateStatus() { status(GetStatusJson()); } + +wpi::json Application::GetStatusJson() { + wpi::json j = {{"type", "applicationSettings"}, + {"applicationType", "custom"}}; + + std::error_code ec; + wpi::raw_fd_istream is(EXEC_HOME "/runCamera", ec); + if (ec) { + wpi::errs() << "could not read " EXEC_HOME "/runCamera\n"; + return j; + } + + // scan file + wpi::SmallString<256> lineBuf; + while (!is.has_error()) { + wpi::StringRef line = is.getline(lineBuf, 256).trim(); + if (line.startswith(TYPE_TAG)) { + j["applicationType"] = line.substr(strlen(TYPE_TAG)).trim(); + break; + } + } + + return j; +} diff --git a/deps/tools/rpiConfigServer_src/Application.h b/deps/tools/rpiConfigServer_src/Application.h new file mode 100644 index 0000000..12d0651 --- /dev/null +++ b/deps/tools/rpiConfigServer_src/Application.h @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef RPICONFIGSERVER_APPLICATION_H_ +#define RPICONFIGSERVER_APPLICATION_H_ + +#include +#include +#include + +#include +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +class Application { + struct private_init {}; + + public: + explicit Application(const private_init&) {} + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + void Set(wpi::StringRef appType, std::function onFail); + + void Upload(wpi::ArrayRef contents, + std::function onFail); + + void UpdateStatus(); + + wpi::json GetStatusJson(); + + wpi::sig::Signal status; + + static std::shared_ptr GetInstance(); + + private: + std::string m_appType; +}; + +#endif // RPICONFIGSERVER_APPLICATION_H_ diff --git a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp index 5d7c559..4410742 100644 --- a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp +++ b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp @@ -18,6 +18,7 @@ #include #include +#include "Application.h" #include "NetworkSettings.h" #include "SystemStatus.h" #include "VisionSettings.h" @@ -36,6 +37,7 @@ struct WebSocketData { wpi::sig::ScopedConnection visLogConn; wpi::sig::ScopedConnection netSettingsConn; wpi::sig::ScopedConnection visSettingsConn; + wpi::sig::ScopedConnection appSettingsConn; }; static void SendWsText(wpi::WebSocket& ws, const wpi::json& j) { @@ -136,6 +138,13 @@ void InitWs(wpi::WebSocket& ws) { visSettingsFunc(visSettings->GetStatusJson()); data->visSettingsConn = visSettings->status.connect_connection(visSettingsFunc); + + // send initial application settings + auto appSettings = Application::GetInstance(); + auto appSettingsFunc = [&ws](const wpi::json& j) { SendWsText(ws, j); }; + appSettingsFunc(appSettings->GetStatusJson()); + data->appSettingsConn = + appSettings->status.connect_connection(appSettingsFunc); } void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { @@ -251,8 +260,25 @@ void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { wpi::errs() << "could not read networkSave value: " << e.what() << '\n'; return; } + } else if (t == "applicationSave") { + auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) { + SendWsText(*s, {{"type", "status"}, {"message", msg}}); + }; + try { + Application::GetInstance()->Set( + j.at("applicationType").get_ref(), statusFunc); + } catch (const wpi::json::exception& e) { + wpi::errs() << "could not read applicationSave value: " << e.what() + << '\n'; + return; + } } } -void ProcessWsBinary(wpi::WebSocket& ws, wpi::ArrayRef msg) {} - +void ProcessWsBinary(wpi::WebSocket& ws, wpi::ArrayRef msg) { + auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) { + SendWsText(*s, {{"type", "status"}, {"message", msg}}); + }; + Application::GetInstance()->Upload(msg, statusFunc); + SendWsText(ws, {{"type", "applicationSaveComplete"}}); +} diff --git a/deps/tools/rpiConfigServer_src/resources/frcvision.js b/deps/tools/rpiConfigServer_src/resources/frcvision.js index 2cfb120..74b4ae2 100644 --- a/deps/tools/rpiConfigServer_src/resources/frcvision.js +++ b/deps/tools/rpiConfigServer_src/resources/frcvision.js @@ -484,6 +484,8 @@ $('#addCamera').click(function() { appendNewVisionCameraView({}, i); }); +var applicationFiles = []; + // Show details when appropriate for application type function updateApplicationView() { if ($('#applicationType').val().startsWith("upload")) { @@ -492,12 +494,17 @@ function updateApplicationView() { $('#applicationUpload').collapse('hide'); } $('#applicationFile').val(null); + applicationFiles = []; } $('#applicationType').change(function() { updateApplicationView(); }); +$('#applicationFile').change(function() { + applicationFiles = this.files; +}); + $('#applicationSave').click(function() { var msg = { type: 'applicationSave', @@ -506,8 +513,7 @@ $('#applicationSave').click(function() { connection.send(JSON.stringify(msg)); // upload the file if requested - var f = $('#applicationFile'); - if (f.files.length <= 0) { + if (applicationFiles.length <= 0) { return; } $('#applicationSave').button('loading'); @@ -515,7 +521,7 @@ $('#applicationSave').click(function() { fr.onload = function(e) { connection.send(e.target.result); }; - fr.readAsArrayBuffer(f.files.item(0)); + fr.readAsArrayBuffer(applicationFiles.item(0)); }); // Start with display disconnected and start initial connection attempt