From c559efc53a7c5c4aa36db17956a61ae1283a6707 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 20 Dec 2018 13:26:14 -0800 Subject: [PATCH] Implement vision settings and start adding app settings --- deps/tools/Makefile | 29 ++- .../rpiConfigServer_src/MyHttpConnection.cpp | 89 +++++++ .../rpiConfigServer_src/MyHttpConnection.h | 5 + .../rpiConfigServer_src/NetworkSettings.cpp | 4 + .../rpiConfigServer_src/VisionSettings.cpp | 65 +++++ .../rpiConfigServer_src/VisionSettings.h | 40 +++ .../rpiConfigServer_src/WebSocketHandlers.cpp | 22 ++ .../rpiConfigServer_src/WebSocketHandlers.h | 2 + .../resources/frcvision.js | 231 ++++++++++++++++-- .../rpiConfigServer_src/resources/index.html | 188 +++++++------- stage2/01-sys-tweaks/01-run.sh | 1 + stage2/01-sys-tweaks/files/runCamera | 1 + 12 files changed, 536 insertions(+), 141 deletions(-) create mode 100644 deps/tools/rpiConfigServer_src/VisionSettings.cpp create mode 100644 deps/tools/rpiConfigServer_src/VisionSettings.h diff --git a/deps/tools/Makefile b/deps/tools/Makefile index 3b4c87c..bb7fb28 100644 --- a/deps/tools/Makefile +++ b/deps/tools/Makefile @@ -1,4 +1,8 @@ -COMPILER=../02-extract/raspbian9/bin/arm-raspbian9-linux-gnueabihf- +SYSROOT?=../02-extract/raspbian9/arm-raspbian9-linux-gnueabihf +COMPILER?=../02-extract/raspbian9/bin/arm-raspbian9-linux-gnueabihf- +WPILIB?=../allwpilib +WPILIB_PLATFORM?=. +OPENCV_INSTALL?=../03-build/opencv-build/install .PHONY: all .SUFFIXES: @@ -9,21 +13,21 @@ clean: rm -f setuidgids rm -f _cscore.so rm -f rpiConfigServer - rm -f rpiConfigServer/*.o - rm -f rpiConfigServer/resources/*.o + rm -f rpiConfigServer_src/*.o + rm -f rpiConfigServer_src/resources/*.o setuidgids: setuidgids.c ${COMPILER}gcc -O -Wall -D_GNU_SOURCE -o $@ $< _cscore.so: ../robotpy-cscore/src/_cscore.cpp ../robotpy-cscore/src/ndarray_converter.cpp ${COMPILER}g++ -O -Wall -fvisibility=hidden -shared -fPIC -o $@ \ - -I../03-build/opencv-build/install/include \ - -I../allwpilib/wpiutil/src/main/native/include \ - -I../allwpilib/cscore/src/main/native/include \ - -I../02-extract/raspbian9/arm-raspbian9-linux-gnueabihf/usr/include/python3.5 \ - -L../allwpilib/cscore/build/libs/cscore/shared/release \ - -L../allwpilib/wpiutil/build/libs/wpiutil/shared/release \ - -L../03-build/opencv-build/install/lib \ + -I${OPENCV_INSTALL}/include \ + -I${WPILIB}/wpiutil/src/main/native/include \ + -I${WPILIB}/cscore/src/main/native/include \ + -I${SYSROOT}/usr/include/python3.5 \ + -L${WPILIB}/cscore/build/libs/cscore/shared/${WPILIB_PLATFORM}/release \ + -L${WPILIB}/wpiutil/build/libs/wpiutil/shared/${WPILIB_PLATFORM}/release \ + -L${OPENCV_INSTALL}/lib \ ../robotpy-cscore/src/_cscore.cpp \ ../robotpy-cscore/src/ndarray_converter.cpp \ -lcscore \ @@ -35,6 +39,7 @@ RPICONFIGSERVER_OBJS= \ rpiConfigServer_src/MyHttpConnection.o \ rpiConfigServer_src/NetworkSettings.o \ rpiConfigServer_src/SystemStatus.o \ + rpiConfigServer_src/VisionSettings.o \ rpiConfigServer_src/VisionStatus.o \ rpiConfigServer_src/WebSocketHandlers.o \ rpiConfigServer_src/resources/index.html.o \ @@ -44,7 +49,7 @@ RPICONFIGSERVER_OBJS= \ rpiConfigServer: ${RPICONFIGSERVER_OBJS} ${COMPILER}g++ -pthread -o $@ \ ${RPICONFIGSERVER_OBJS} \ - -L../allwpilib/wpiutil/build/libs/wpiutil/static/release \ + -L${WPILIB}/wpiutil/build/libs/wpiutil/static/${WPILIB_PLATFORM}/release \ -lwpiutil ${COMPILER}objcopy --only-keep-debug $@ $@.debug ${COMPILER}strip -g $@ @@ -52,7 +57,7 @@ rpiConfigServer: ${RPICONFIGSERVER_OBJS} %.o: %.cpp ${COMPILER}g++ -O -Wall -c -o $@ \ - -I../allwpilib/wpiutil/src/main/native/include \ + -I${WPILIB}/wpiutil/src/main/native/include \ $< %.html.cpp: %.html diff --git a/deps/tools/rpiConfigServer_src/MyHttpConnection.cpp b/deps/tools/rpiConfigServer_src/MyHttpConnection.cpp index e4ca58e..5c232ed 100644 --- a/deps/tools/rpiConfigServer_src/MyHttpConnection.cpp +++ b/deps/tools/rpiConfigServer_src/MyHttpConnection.cpp @@ -7,11 +7,22 @@ #include "MyHttpConnection.h" +#include +#include + +#include +#include #include #include +#include +#include #include "WebSocketHandlers.h" +#define ZIPS_DIR "/home/pi/zips" + +namespace uv = wpi::uv; + // static resources namespace wpi { StringRef GetResource_bootstrap_4_1_min_js_gz(); @@ -52,9 +63,84 @@ MyHttpConnection::MyHttpConnection(std::shared_ptr stream) ws->text.connect([s = ws.get()](wpi::StringRef msg, bool) { ProcessWsText(*s, msg); }); + ws->binary.connect([s = ws.get()](wpi::ArrayRef msg, bool) { + ProcessWsBinary(*s, msg); + }); }); } +class FsReq : public uv::RequestImpl { + public: + FsReq() { + error = [this](uv::Error err) { GetLoop().error(err); }; + } + + uv::Loop& GetLoop() const { + return *static_cast(GetRaw()->loop->data); + } + + wpi::sig::Signal<> complete; +}; + +void Sendfile(uv::Loop& loop, uv_file out, uv_file in, int64_t inOffset, + size_t len, std::function complete) { + auto req = std::make_shared(); + if (complete) req->complete.connect(complete); + int err = uv_fs_sendfile(loop.GetRaw(), req->GetRaw(), out, in, inOffset, len, + [](uv_fs_t* req) { + auto& h = *static_cast(req->data); + h.complete(); + h.Release(); // this is always a one-shot + }); + if (err < 0) { + loop.ReportError(err); + complete(); + } else { + req->Keep(); + } +} + +void MyHttpConnection::SendFileResponse(int code, const wpi::Twine& codeText, + const wpi::Twine& contentType, + const wpi::Twine& filename, + const wpi::Twine& extraHeader) { + // open file + int infd; + if (wpi::sys::fs::openFileForRead(filename, infd)) { + SendError(404); + return; + } + + // get status (to get file size) + wpi::sys::fs::file_status status; + if (wpi::sys::fs::status(infd, status)) { + SendError(404); + ::close(infd); + return; + } + + uv_os_fd_t outfd; + int err = uv_fileno(m_stream.GetRawHandle(), &outfd); + if (err < 0) { + m_stream.GetLoopRef().ReportError(err); + SendError(404); + ::close(infd); + return; + } + + wpi::SmallVector toSend; + wpi::raw_uv_ostream os{toSend, 4096}; + BuildHeader(os, code, codeText, contentType, status.getSize(), extraHeader); + SendData(os.bufs(), false); + + // close after write completes if we aren't keeping alive + Sendfile(m_stream.GetLoopRef(), outfd, infd, 0, status.getSize(), + [ infd, closeAfter = !m_keepAlive, stream = &m_stream ] { + ::close(infd); + if (closeAfter) stream->Close(); + }); +} + void MyHttpConnection::ProcessRequest() { //wpi::errs() << "HTTP request: '" << m_request.GetUrl() << "'\n"; wpi::UrlParser url{m_request.GetUrl(), @@ -103,6 +189,9 @@ void MyHttpConnection::ProcessRequest() { } else if (isGET && path.equals("/wpilib.png")) { SendStaticResponse(200, "OK", "image/png", wpi::GetResource_wpilib_128_png(), false); + } else if (isGET && path.startswith("/") && path.endswith(".zip") && + !path.contains("..")) { + SendFileResponse(200, "OK", "application/zip", wpi::Twine(ZIPS_DIR) + path); } else { SendError(404, "Resource not found"); } diff --git a/deps/tools/rpiConfigServer_src/MyHttpConnection.h b/deps/tools/rpiConfigServer_src/MyHttpConnection.h index 5d2c9fd..44a0905 100644 --- a/deps/tools/rpiConfigServer_src/MyHttpConnection.h +++ b/deps/tools/rpiConfigServer_src/MyHttpConnection.h @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -21,6 +22,10 @@ class MyHttpConnection : public wpi::HttpServerConnection, protected: void ProcessRequest() override; + void SendFileResponse(int code, const wpi::Twine& codeText, + const wpi::Twine& contentType, + const wpi::Twine& filename, + const wpi::Twine& extraHeader = wpi::Twine{}); wpi::WebSocketServerHelper m_websocketHelper; }; diff --git a/deps/tools/rpiConfigServer_src/NetworkSettings.cpp b/deps/tools/rpiConfigServer_src/NetworkSettings.cpp index 2519d8b..5255564 100644 --- a/deps/tools/rpiConfigServer_src/NetworkSettings.cpp +++ b/deps/tools/rpiConfigServer_src/NetworkSettings.cpp @@ -21,7 +21,11 @@ namespace uv = wpi::uv; +#ifdef __RASPBIAN9__ #define DHCPCD_CONF "/boot/dhcpcd.conf" +#else +#define DHCPCD_CONF "dhcpcd.conf" +#endif #define GEN_MARKER "###### BELOW THIS LINE EDITED BY RPICONFIGSERVER ######" diff --git a/deps/tools/rpiConfigServer_src/VisionSettings.cpp b/deps/tools/rpiConfigServer_src/VisionSettings.cpp new file mode 100644 index 0000000..c6ae093 --- /dev/null +++ b/deps/tools/rpiConfigServer_src/VisionSettings.cpp @@ -0,0 +1,65 @@ +/*----------------------------------------------------------------------------*/ +/* 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 "VisionSettings.h" + +#include +#include +#include +#include + +#include "VisionStatus.h" + +#ifdef __RASPBIAN9__ +#define FRC_JSON "/boot/frc.json" +#else +#define FRC_JSON "frc.json" +#endif + +std::shared_ptr VisionSettings::GetInstance() { + static auto inst = std::make_shared(private_init{}); + return inst; +} + +void VisionSettings::Set(const wpi::json& data, + std::function onFail) { + { + // write file + std::error_code ec; + wpi::raw_fd_ostream os(FRC_JSON, ec, wpi::sys::fs::F_Text); + if (ec) { + onFail("could not write " FRC_JSON); + return; + } + data.dump(os, 4); + } + + // terminate vision process so it reloads the file + VisionStatus::GetInstance()->Terminate(onFail); + + UpdateStatus(); +} + +void VisionSettings::UpdateStatus() { status(GetStatusJson()); } + +wpi::json VisionSettings::GetStatusJson() { + std::error_code ec; + wpi::raw_fd_istream is(FRC_JSON, ec); + if (ec) { + wpi::errs() << "could not read " FRC_JSON "\n"; + return wpi::json(); + } + + try { + wpi::json j = {{"type", "visionSettings"}, + {"settings", wpi::json::parse(is)}}; + return j; + } catch (wpi::json::exception& e) { + wpi::errs() << "could not parse " FRC_JSON "\n"; + return wpi::json(); + } +} diff --git a/deps/tools/rpiConfigServer_src/VisionSettings.h b/deps/tools/rpiConfigServer_src/VisionSettings.h new file mode 100644 index 0000000..2437b6e --- /dev/null +++ b/deps/tools/rpiConfigServer_src/VisionSettings.h @@ -0,0 +1,40 @@ +/*----------------------------------------------------------------------------*/ +/* 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_VISIONSETTINGS_H_ +#define RPICONFIGSERVER_VISIONSETTINGS_H_ + +#include +#include + +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +class VisionSettings { + struct private_init {}; + + public: + explicit VisionSettings(const private_init&) {} + VisionSettings(const VisionSettings&) = delete; + VisionSettings& operator=(const VisionSettings&) = delete; + + void Set(const wpi::json& data, std::function onFail); + + void UpdateStatus(); + + wpi::json GetStatusJson(); + + wpi::sig::Signal status; + + static std::shared_ptr GetInstance(); +}; + +#endif // RPICONFIGSERVER_VISIONSETTINGS_H_ diff --git a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp index c672511..09aa9b8 100644 --- a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp +++ b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp @@ -20,6 +20,7 @@ #include "NetworkSettings.h" #include "SystemStatus.h" +#include "VisionSettings.h" #include "VisionStatus.h" namespace uv = wpi::uv; @@ -34,6 +35,7 @@ struct WebSocketData { wpi::sig::ScopedConnection visStatusConn; wpi::sig::ScopedConnection visLogConn; wpi::sig::ScopedConnection netSettingsConn; + wpi::sig::ScopedConnection visSettingsConn; }; static void SendWsText(wpi::WebSocket& ws, const wpi::json& j) { @@ -127,6 +129,13 @@ void InitWs(wpi::WebSocket& ws) { netSettingsFunc(netSettings->GetStatusJson()); data->netSettingsConn = netSettings->status.connect_connection(netSettingsFunc); + + // send initial vision settings + auto visSettings = VisionSettings::GetInstance(); + auto visSettingsFunc = [&ws](const wpi::json& j) { SendWsText(ws, j); }; + visSettingsFunc(visSettings->GetStatusJson()); + data->visSettingsConn = + visSettings->status.connect_connection(visSettingsFunc); } void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { @@ -235,5 +244,18 @@ void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { wpi::errs() << "could not read networkSave value: " << e.what() << '\n'; return; } + } else if (t == "visionSave") { + auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) { + SendWsText(*s, {{"type", "status"}, {"message", msg}}); + }; + try { + VisionSettings::GetInstance()->Set(j.at("settings"), statusFunc); + } catch (const wpi::json::exception& e) { + wpi::errs() << "could not read visionSave value: " << e.what() << '\n'; + return; + } } } + +void ProcessWsBinary(wpi::WebSocket& ws, wpi::ArrayRef msg) {} + diff --git a/deps/tools/rpiConfigServer_src/WebSocketHandlers.h b/deps/tools/rpiConfigServer_src/WebSocketHandlers.h index 9578468..4b438a3 100644 --- a/deps/tools/rpiConfigServer_src/WebSocketHandlers.h +++ b/deps/tools/rpiConfigServer_src/WebSocketHandlers.h @@ -8,6 +8,7 @@ #ifndef RPICONFIGSERVER_WEBSOCKETHANDLERS_H_ #define RPICONFIGSERVER_WEBSOCKETHANDLERS_H_ +#include #include namespace wpi { @@ -16,5 +17,6 @@ class WebSocket; void InitWs(wpi::WebSocket& ws); void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg); +void ProcessWsBinary(wpi::WebSocket& ws, wpi::ArrayRef msg); #endif // RPICONFIGSERVER_WEBSOCKETHANDLERS_H_ diff --git a/deps/tools/rpiConfigServer_src/resources/frcvision.js b/deps/tools/rpiConfigServer_src/resources/frcvision.js index 4c1712c..2cfb120 100644 --- a/deps/tools/rpiConfigServer_src/resources/frcvision.js +++ b/deps/tools/rpiConfigServer_src/resources/frcvision.js @@ -3,20 +3,19 @@ var connection = null; var WebSocket = WebSocket || MozWebSocket; -/* // Implement bootstrap 3 style button loading support (function($) { $.fn.button = function(action) { if (action === 'loading' && this.data('loading-text')) { this.data('original-text', this.html()).html(this.data('loading-text')).prop('disabled', true); + feather.replace(); } if (action === 'reset' && this.data('original-text')) { this.html(this.data('original-text')).prop('disabled', false); + feather.replace(); } - feather.replace(); }; }(jQuery)); -*/ // HTML escaping var entityMap = { @@ -41,8 +40,9 @@ function displayStatus(message) { } // Enable and disable buttons based on connection status -var connectedButtonIds = ['systemRestart', 'networkApproach', 'visionUp', 'visionDown', 'visionTerm', 'visionKill', 'systemReadOnly', 'systemWritable']; -var writableButtonIds = ['networkSave']; +var connectedButtonIds = ['systemRestart', 'networkApproach', 'networkAddress', 'networkMask', 'networkGateway', 'networkDNS', 'visionUp', 'visionDown', 'visionTerm', 'visionKill', 'systemReadOnly', 'systemWritable', 'visionClient', 'visionTeam', 'visionDiscard', 'addCamera', 'applicationType']; +var connectedButtonClasses = ['cameraName', 'cameraPath', 'cameraPixelFormat', 'cameraWidth', 'cameraHeight', 'cameraFps', 'cameraBrightness', 'cameraWhiteBalance', 'cameraExposure', 'cameraProperties', 'cameraRemove'] +var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave']; var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s', 'systemMemoryAvail1s', 'systemMemoryAvail5s', 'systemCpuUser1s', 'systemCpuUser5s', @@ -57,6 +57,9 @@ function displayDisconnected() { for (var i = 0; i < connectedButtonIds.length; i++) { $('#' + connectedButtonIds[i]).prop('disabled', true); } + for (var i = 0; i < connectedButtonClasses.length; i++) { + $('.' + connectedButtonClasses[i]).prop('disabled', true); + } for (var i = 0; i < systemStatusIds.length; i++) { $('#' + systemStatusIds[i]).text(""); } @@ -67,6 +70,9 @@ function displayConnected() { for (var i = 0; i < connectedButtonIds.length; i++) { $('#' + connectedButtonIds[i]).prop('disabled', false); } + for (var i = 0; i < connectedButtonClasses.length; i++) { + $('.' + connectedButtonClasses[i]).prop('disabled', false); + } } // Enable and disable buttons based on writable status @@ -105,6 +111,10 @@ $('#systemWritable').click(function() { connection.send(JSON.stringify(msg)); }); +// Vision settings +var visionSettingsServer = {}; +var visionSettingsDisplay = {'cameras': []}; + // WebSocket automatic reconnection timer var reconnectTimerId = 0; @@ -132,6 +142,9 @@ function connect() { // WebSocket incoming message handling connection.onmessage = function(evt) { var msg = JSON.parse(evt.data); + if (msg === null) { + return; + } switch (msg.type) { case 'systemStatus': for (var i = 0; i < systemStatusIds.length; i++) { @@ -160,6 +173,19 @@ function connect() { $('#networkDNS').val(msg.networkDNS); updateNetworkSettingsView(); break; + case 'visionSettings': + visionSettingsServer = msg.settings; + visionSettingsDisplay = $.extend(true, {}, visionSettingsServer); + updateVisionSettingsView(); + break; + case 'applicationSettings': + $('#applicationType').val(msg.applicationType); + updateApplicationView(); + break; + case 'applicationSaveComplete': + $('#applicationSave').button('reset'); + updateApplicationView(); + break; case 'systemReadOnly': displayReadOnly(); break; @@ -303,47 +329,198 @@ $('#visionClient').change(function() { } }); -// Update view from data structure -var visionSettings = { - team: '294', - ntmode: 'client', - cameras: [] -}; +function updateVisionCameraView(camera, value) { + if ('name' in value) { + camera.find('.cameraTitle').text('Camera ' + value.name); + camera.find('.cameraName').val(value.name); + } + if ('path' in value) { + camera.find('.cameraPath').val(value.path); + } + camera.find('.cameraPixelFormat').val(value['pixel format']); + camera.find('.cameraWidth').val(value.width); + camera.find('.cameraHeight').val(value.height); + camera.find('.cameraFps').val(value.fps); + camera.find('.cameraBrightness').val(value.brightness); + camera.find('.cameraWhiteBalance').val(value['white balance']); + camera.find('.cameraExposure').val(value.exposure); + camera.find('.cameraProperties').val(JSON.stringify(value.properties)); +} -function updateVisionSettingsCameraView(cardElem, data) { +function appendNewVisionCameraView(value, i) { + var camera = $('#cameraNEW').clone(); + camera.attr('id', 'camera' + i); + camera.addClass('cameraSetting'); + camera.removeAttr('style'); + + updateVisionCameraView(camera, value); + camera.find('.cameraStream').attr('href', 'http://' + window.location.hostname + ':' + (1181 + i) + '/'); + camera.find('.cameraRemove').click(function() { + visionSettingsDisplay.cameras.splice(i, 1); + camera.remove(); + }); + camera.find('.cameraSettingsFile').change(function() { + if (this.files.length <= 0) { + return false; + } + var fr = new FileReader(); + fr.onload = function(e) { + var result = JSON.parse(e.target.result); + if (!('name' in result)) { + result.name = visionSettingsDisplay.cameras[i].name; + } + if (!('path' in result)) { + result.path = visionSettingsDisplay.cameras[i].path; + } + visionSettingsDisplay.cameras[i] = result; + updateVisionCameraView(camera, result); + }; + fr.readAsText(this.files.item(0)); + }); + + camera.find('[id]').each(function() { + $(this).attr('id', $(this).attr('id').replace('NEW', i)); + }); + camera.find('[for]').each(function() { + $(this).attr('for', $(this).attr('for').replace('NEW', i)); + }); + camera.find('[data-target]').each(function() { + $(this).attr('data-target', $(this).attr('data-target').replace('NEW', i)); + }); + + $('#cameras').append(camera); } function updateVisionSettingsView() { - $('#visionClient').prop('checked', visionSettings.ntmode === 'client'); - if (visionSettings.ntmode === 'client') { + var isClient = !visionSettingsDisplay.ntmode || visionSettingsDisplay.ntmode === 'client'; + $('#visionClient').prop('checked', isClient); + if (isClient) { $('#visionClientDetails').collapse('show'); } else { $('#visionClientDetails').collapse('hide'); } - $('#visionTeam').val(visionSettings.team); + $('#visionTeam').val(visionSettingsDisplay.team); - var newCamera = $('#cameraNEW').clone(); - newCamera.find('[id]').each(function() { - $(this).attr('id', $(this).attr('id').replace('NEW', '')); - }); - newCamera.find('[for]').each(function() { - $(this).attr('for', $(this).attr('for').replace('NEW', '')); + $('.cameraSetting').remove(); + visionSettingsDisplay.cameras.forEach(function (value, i) { + appendNewVisionCameraView(value, i); }); + feather.replace(); } -$('#cameraSettingsFile0').change(function() { - if (this.files.length <= 0) { - return false; +$('#visionSave').click(function() { + // update json from view + visionSettingsDisplay.ntmode = $('#visionClient').prop('checked') ? 'client' : 'server'; + visionSettingsDisplay.team = parseInt($('#visionTeam').val(), 10); + visionSettingsDisplay.cameras.forEach(function (value, i) { + var camera = $('#camera' + i); + value.name = camera.find('.cameraName').val(); + value.path = camera.find('.cameraPath').val(); + value['pixel format'] = camera.find('.cameraPixelFormat').val(); + value.width = parseInt(camera.find('.cameraWidth').val(), 10); + if (isNaN(value.width)) { + delete value["width"]; + } + value.height = parseInt(camera.find('.cameraHeight').val(), 10); + if (isNaN(value.height)) { + delete value["height"]; + } + value.fps = parseInt(camera.find('.cameraFps').val(), 10); + if (isNaN(value.fps)) { + delete value["fps"]; + } + + var brightness = camera.find('.cameraBrightness').val(); + if (brightness !== '') { + value.brightness = parseInt(brightness); + if (isNaN(value.brightness)) { + value.brightness = brightness; + } + } else { + delete value['brightness']; + } + + var whiteBalance = camera.find('.cameraWhiteBalance').val(); + if (whiteBalance !== '') { + value['white balance'] = parseInt(whiteBalance); + if (isNaN(value['white balance'])) { + value['white balance'] = whiteBalance; + } + } else { + delete value['white balance']; + } + + var exposure = camera.find('.cameraExposure').val(); + if (exposure !== '') { + value.exposure = parseInt(exposure); + if (isNaN(value.exposure)) { + value.exposure = exposure; + } + } else { + delete value['exposure']; + } + + try { + value.properties = JSON.parse(camera.find('.cameraProperties').val()); + } catch (err) { + delete value['properties']; + } + }); + var msg = { + type: 'visionSave', + settings: visionSettingsDisplay + }; + connection.send(JSON.stringify(msg)); +}); + +$('#visionDiscard').click(function() { + visionSettingsDisplay = $.extend(true, {}, visionSettingsServer); + updateVisionSettingsView(); +}); + +$('#addCamera').click(function() { + var i = visionSettingsDisplay.cameras.length; + visionSettingsDisplay.cameras.push({}); + appendNewVisionCameraView({}, i); +}); + +// Show details when appropriate for application type +function updateApplicationView() { + if ($('#applicationType').val().startsWith("upload")) { + $('#applicationUpload').collapse('show'); + } else { + $('#applicationUpload').collapse('hide'); } + $('#applicationFile').val(null); +} + +$('#applicationType').change(function() { + updateApplicationView(); +}); + +$('#applicationSave').click(function() { + var msg = { + type: 'applicationSave', + applicationType: $('#applicationType').val() + }; + connection.send(JSON.stringify(msg)); + + // upload the file if requested + var f = $('#applicationFile'); + if (f.files.length <= 0) { + return; + } + $('#applicationSave').button('loading'); var fr = new FileReader(); fr.onload = function(e) { - var result = JSON.parse(e.target.result); - console.log(result); + connection.send(e.target.result); }; - fr.readAsText(this.files.item(0)); + fr.readAsArrayBuffer(f.files.item(0)); }); // Start with display disconnected and start initial connection attempt displayDisconnected(); +updateNetworkSettingsView(); updateVisionSettingsView(); +updateApplicationView(); connect(); diff --git a/deps/tools/rpiConfigServer_src/resources/index.html b/deps/tools/rpiConfigServer_src/resources/index.html index 3984a54..a9d9a1a 100644 --- a/deps/tools/rpiConfigServer_src/resources/index.html +++ b/deps/tools/rpiConfigServer_src/resources/index.html @@ -56,6 +56,12 @@ Vision Settings + @@ -208,7 +214,7 @@
-
Network Tables
+
Network Tables
@@ -231,105 +237,20 @@
-
-
-
-
- -
-
- - - Open Stream - - -
-
-
-
-
- -
-
- - -
-
- - -
-
-
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
- - -
- -
-
-
+ + +
+
+
+
Vision Application Configuration
+
+
+
+
+ + +
+
+
+ + +
+
+ +
+
+
+ +
diff --git a/stage2/01-sys-tweaks/01-run.sh b/stage2/01-sys-tweaks/01-run.sh index af33f1c..0066d8d 100755 --- a/stage2/01-sys-tweaks/01-run.sh +++ b/stage2/01-sys-tweaks/01-run.sh @@ -116,6 +116,7 @@ EOF install -v -o 1000 -g 1000 -d "${ROOTFS_DIR}/home/pi/javalibs/" install -m 644 -o 1000 -g 1000 extfiles/*.jar "${ROOTFS_DIR}/home/pi/javalibs/" +sh -c "cd ${ROOTFS_DIR}/home/pi/javalibs && zip ../zips/java-multiCameraServer.zip *.jar" on_chroot << EOF ldconfig diff --git a/stage2/01-sys-tweaks/files/runCamera b/stage2/01-sys-tweaks/files/runCamera index 236e79d..937499e 100755 --- a/stage2/01-sys-tweaks/files/runCamera +++ b/stage2/01-sys-tweaks/files/runCamera @@ -1,4 +1,5 @@ #!/bin/sh +### TYPE: builtin echo "Waiting 5 seconds..." sleep 5 exec ./multiCameraServer