Implement vision settings and start adding app settings
This commit is contained in:
parent
ee8ffe0636
commit
c559efc53a
29
deps/tools/Makefile
vendored
29
deps/tools/Makefile
vendored
|
@ -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
|
.PHONY: all
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
|
@ -9,21 +13,21 @@ clean:
|
||||||
rm -f setuidgids
|
rm -f setuidgids
|
||||||
rm -f _cscore.so
|
rm -f _cscore.so
|
||||||
rm -f rpiConfigServer
|
rm -f rpiConfigServer
|
||||||
rm -f rpiConfigServer/*.o
|
rm -f rpiConfigServer_src/*.o
|
||||||
rm -f rpiConfigServer/resources/*.o
|
rm -f rpiConfigServer_src/resources/*.o
|
||||||
|
|
||||||
setuidgids: setuidgids.c
|
setuidgids: setuidgids.c
|
||||||
${COMPILER}gcc -O -Wall -D_GNU_SOURCE -o $@ $<
|
${COMPILER}gcc -O -Wall -D_GNU_SOURCE -o $@ $<
|
||||||
|
|
||||||
_cscore.so: ../robotpy-cscore/src/_cscore.cpp ../robotpy-cscore/src/ndarray_converter.cpp
|
_cscore.so: ../robotpy-cscore/src/_cscore.cpp ../robotpy-cscore/src/ndarray_converter.cpp
|
||||||
${COMPILER}g++ -O -Wall -fvisibility=hidden -shared -fPIC -o $@ \
|
${COMPILER}g++ -O -Wall -fvisibility=hidden -shared -fPIC -o $@ \
|
||||||
-I../03-build/opencv-build/install/include \
|
-I${OPENCV_INSTALL}/include \
|
||||||
-I../allwpilib/wpiutil/src/main/native/include \
|
-I${WPILIB}/wpiutil/src/main/native/include \
|
||||||
-I../allwpilib/cscore/src/main/native/include \
|
-I${WPILIB}/cscore/src/main/native/include \
|
||||||
-I../02-extract/raspbian9/arm-raspbian9-linux-gnueabihf/usr/include/python3.5 \
|
-I${SYSROOT}/usr/include/python3.5 \
|
||||||
-L../allwpilib/cscore/build/libs/cscore/shared/release \
|
-L${WPILIB}/cscore/build/libs/cscore/shared/${WPILIB_PLATFORM}/release \
|
||||||
-L../allwpilib/wpiutil/build/libs/wpiutil/shared/release \
|
-L${WPILIB}/wpiutil/build/libs/wpiutil/shared/${WPILIB_PLATFORM}/release \
|
||||||
-L../03-build/opencv-build/install/lib \
|
-L${OPENCV_INSTALL}/lib \
|
||||||
../robotpy-cscore/src/_cscore.cpp \
|
../robotpy-cscore/src/_cscore.cpp \
|
||||||
../robotpy-cscore/src/ndarray_converter.cpp \
|
../robotpy-cscore/src/ndarray_converter.cpp \
|
||||||
-lcscore \
|
-lcscore \
|
||||||
|
@ -35,6 +39,7 @@ RPICONFIGSERVER_OBJS= \
|
||||||
rpiConfigServer_src/MyHttpConnection.o \
|
rpiConfigServer_src/MyHttpConnection.o \
|
||||||
rpiConfigServer_src/NetworkSettings.o \
|
rpiConfigServer_src/NetworkSettings.o \
|
||||||
rpiConfigServer_src/SystemStatus.o \
|
rpiConfigServer_src/SystemStatus.o \
|
||||||
|
rpiConfigServer_src/VisionSettings.o \
|
||||||
rpiConfigServer_src/VisionStatus.o \
|
rpiConfigServer_src/VisionStatus.o \
|
||||||
rpiConfigServer_src/WebSocketHandlers.o \
|
rpiConfigServer_src/WebSocketHandlers.o \
|
||||||
rpiConfigServer_src/resources/index.html.o \
|
rpiConfigServer_src/resources/index.html.o \
|
||||||
|
@ -44,7 +49,7 @@ RPICONFIGSERVER_OBJS= \
|
||||||
rpiConfigServer: ${RPICONFIGSERVER_OBJS}
|
rpiConfigServer: ${RPICONFIGSERVER_OBJS}
|
||||||
${COMPILER}g++ -pthread -o $@ \
|
${COMPILER}g++ -pthread -o $@ \
|
||||||
${RPICONFIGSERVER_OBJS} \
|
${RPICONFIGSERVER_OBJS} \
|
||||||
-L../allwpilib/wpiutil/build/libs/wpiutil/static/release \
|
-L${WPILIB}/wpiutil/build/libs/wpiutil/static/${WPILIB_PLATFORM}/release \
|
||||||
-lwpiutil
|
-lwpiutil
|
||||||
${COMPILER}objcopy --only-keep-debug $@ $@.debug
|
${COMPILER}objcopy --only-keep-debug $@ $@.debug
|
||||||
${COMPILER}strip -g $@
|
${COMPILER}strip -g $@
|
||||||
|
@ -52,7 +57,7 @@ rpiConfigServer: ${RPICONFIGSERVER_OBJS}
|
||||||
|
|
||||||
%.o: %.cpp
|
%.o: %.cpp
|
||||||
${COMPILER}g++ -O -Wall -c -o $@ \
|
${COMPILER}g++ -O -Wall -c -o $@ \
|
||||||
-I../allwpilib/wpiutil/src/main/native/include \
|
-I${WPILIB}/wpiutil/src/main/native/include \
|
||||||
$<
|
$<
|
||||||
|
|
||||||
%.html.cpp: %.html
|
%.html.cpp: %.html
|
||||||
|
|
|
@ -7,11 +7,22 @@
|
||||||
|
|
||||||
#include "MyHttpConnection.h"
|
#include "MyHttpConnection.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
#include <wpi/FileSystem.h>
|
||||||
|
#include <wpi/SmallVector.h>
|
||||||
#include <wpi/UrlParser.h>
|
#include <wpi/UrlParser.h>
|
||||||
#include <wpi/raw_ostream.h>
|
#include <wpi/raw_ostream.h>
|
||||||
|
#include <wpi/raw_uv_ostream.h>
|
||||||
|
#include <wpi/uv/Request.h>
|
||||||
|
|
||||||
#include "WebSocketHandlers.h"
|
#include "WebSocketHandlers.h"
|
||||||
|
|
||||||
|
#define ZIPS_DIR "/home/pi/zips"
|
||||||
|
|
||||||
|
namespace uv = wpi::uv;
|
||||||
|
|
||||||
// static resources
|
// static resources
|
||||||
namespace wpi {
|
namespace wpi {
|
||||||
StringRef GetResource_bootstrap_4_1_min_js_gz();
|
StringRef GetResource_bootstrap_4_1_min_js_gz();
|
||||||
|
@ -52,6 +63,81 @@ MyHttpConnection::MyHttpConnection(std::shared_ptr<wpi::uv::Stream> stream)
|
||||||
ws->text.connect([s = ws.get()](wpi::StringRef msg, bool) {
|
ws->text.connect([s = ws.get()](wpi::StringRef msg, bool) {
|
||||||
ProcessWsText(*s, msg);
|
ProcessWsText(*s, msg);
|
||||||
});
|
});
|
||||||
|
ws->binary.connect([s = ws.get()](wpi::ArrayRef<uint8_t> msg, bool) {
|
||||||
|
ProcessWsBinary(*s, msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FsReq : public uv::RequestImpl<FsReq, uv_fs_t> {
|
||||||
|
public:
|
||||||
|
FsReq() {
|
||||||
|
error = [this](uv::Error err) { GetLoop().error(err); };
|
||||||
|
}
|
||||||
|
|
||||||
|
uv::Loop& GetLoop() const {
|
||||||
|
return *static_cast<uv::Loop*>(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<void()> complete) {
|
||||||
|
auto req = std::make_shared<FsReq>();
|
||||||
|
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<FsReq*>(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<uv::Buffer, 4> 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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +189,9 @@ void MyHttpConnection::ProcessRequest() {
|
||||||
} else if (isGET && path.equals("/wpilib.png")) {
|
} else if (isGET && path.equals("/wpilib.png")) {
|
||||||
SendStaticResponse(200, "OK", "image/png",
|
SendStaticResponse(200, "OK", "image/png",
|
||||||
wpi::GetResource_wpilib_128_png(), false);
|
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 {
|
} else {
|
||||||
SendError(404, "Resource not found");
|
SendError(404, "Resource not found");
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <wpi/HttpServerConnection.h>
|
#include <wpi/HttpServerConnection.h>
|
||||||
|
#include <wpi/Twine.h>
|
||||||
#include <wpi/WebSocketServer.h>
|
#include <wpi/WebSocketServer.h>
|
||||||
#include <wpi/uv/Stream.h>
|
#include <wpi/uv/Stream.h>
|
||||||
|
|
||||||
|
@ -21,6 +22,10 @@ class MyHttpConnection : public wpi::HttpServerConnection,
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void ProcessRequest() override;
|
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;
|
wpi::WebSocketServerHelper m_websocketHelper;
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,11 @@
|
||||||
|
|
||||||
namespace uv = wpi::uv;
|
namespace uv = wpi::uv;
|
||||||
|
|
||||||
|
#ifdef __RASPBIAN9__
|
||||||
#define DHCPCD_CONF "/boot/dhcpcd.conf"
|
#define DHCPCD_CONF "/boot/dhcpcd.conf"
|
||||||
|
#else
|
||||||
|
#define DHCPCD_CONF "dhcpcd.conf"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define GEN_MARKER "###### BELOW THIS LINE EDITED BY RPICONFIGSERVER ######"
|
#define GEN_MARKER "###### BELOW THIS LINE EDITED BY RPICONFIGSERVER ######"
|
||||||
|
|
||||||
|
|
65
deps/tools/rpiConfigServer_src/VisionSettings.cpp
vendored
Normal file
65
deps/tools/rpiConfigServer_src/VisionSettings.cpp
vendored
Normal file
|
@ -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 <wpi/FileSystem.h>
|
||||||
|
#include <wpi/json.h>
|
||||||
|
#include <wpi/raw_istream.h>
|
||||||
|
#include <wpi/raw_ostream.h>
|
||||||
|
|
||||||
|
#include "VisionStatus.h"
|
||||||
|
|
||||||
|
#ifdef __RASPBIAN9__
|
||||||
|
#define FRC_JSON "/boot/frc.json"
|
||||||
|
#else
|
||||||
|
#define FRC_JSON "frc.json"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::shared_ptr<VisionSettings> VisionSettings::GetInstance() {
|
||||||
|
static auto inst = std::make_shared<VisionSettings>(private_init{});
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisionSettings::Set(const wpi::json& data,
|
||||||
|
std::function<void(wpi::StringRef)> 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();
|
||||||
|
}
|
||||||
|
}
|
40
deps/tools/rpiConfigServer_src/VisionSettings.h
vendored
Normal file
40
deps/tools/rpiConfigServer_src/VisionSettings.h
vendored
Normal file
|
@ -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 <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <wpi/Signal.h>
|
||||||
|
#include <wpi/StringRef.h>
|
||||||
|
|
||||||
|
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<void(wpi::StringRef)> onFail);
|
||||||
|
|
||||||
|
void UpdateStatus();
|
||||||
|
|
||||||
|
wpi::json GetStatusJson();
|
||||||
|
|
||||||
|
wpi::sig::Signal<const wpi::json&> status;
|
||||||
|
|
||||||
|
static std::shared_ptr<VisionSettings> GetInstance();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RPICONFIGSERVER_VISIONSETTINGS_H_
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#include "NetworkSettings.h"
|
#include "NetworkSettings.h"
|
||||||
#include "SystemStatus.h"
|
#include "SystemStatus.h"
|
||||||
|
#include "VisionSettings.h"
|
||||||
#include "VisionStatus.h"
|
#include "VisionStatus.h"
|
||||||
|
|
||||||
namespace uv = wpi::uv;
|
namespace uv = wpi::uv;
|
||||||
|
@ -34,6 +35,7 @@ struct WebSocketData {
|
||||||
wpi::sig::ScopedConnection visStatusConn;
|
wpi::sig::ScopedConnection visStatusConn;
|
||||||
wpi::sig::ScopedConnection visLogConn;
|
wpi::sig::ScopedConnection visLogConn;
|
||||||
wpi::sig::ScopedConnection netSettingsConn;
|
wpi::sig::ScopedConnection netSettingsConn;
|
||||||
|
wpi::sig::ScopedConnection visSettingsConn;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void SendWsText(wpi::WebSocket& ws, const wpi::json& j) {
|
static void SendWsText(wpi::WebSocket& ws, const wpi::json& j) {
|
||||||
|
@ -127,6 +129,13 @@ void InitWs(wpi::WebSocket& ws) {
|
||||||
netSettingsFunc(netSettings->GetStatusJson());
|
netSettingsFunc(netSettings->GetStatusJson());
|
||||||
data->netSettingsConn =
|
data->netSettingsConn =
|
||||||
netSettings->status.connect_connection(netSettingsFunc);
|
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) {
|
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';
|
wpi::errs() << "could not read networkSave value: " << e.what() << '\n';
|
||||||
return;
|
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<uint8_t> msg) {}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#ifndef RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
#ifndef RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
||||||
#define RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
#define RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
||||||
|
|
||||||
|
#include <wpi/ArrayRef.h>
|
||||||
#include <wpi/StringRef.h>
|
#include <wpi/StringRef.h>
|
||||||
|
|
||||||
namespace wpi {
|
namespace wpi {
|
||||||
|
@ -16,5 +17,6 @@ class WebSocket;
|
||||||
|
|
||||||
void InitWs(wpi::WebSocket& ws);
|
void InitWs(wpi::WebSocket& ws);
|
||||||
void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg);
|
void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg);
|
||||||
|
void ProcessWsBinary(wpi::WebSocket& ws, wpi::ArrayRef<uint8_t> msg);
|
||||||
|
|
||||||
#endif // RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
#endif // RPICONFIGSERVER_WEBSOCKETHANDLERS_H_
|
||||||
|
|
|
@ -3,20 +3,19 @@ var connection = null;
|
||||||
|
|
||||||
var WebSocket = WebSocket || MozWebSocket;
|
var WebSocket = WebSocket || MozWebSocket;
|
||||||
|
|
||||||
/*
|
|
||||||
// Implement bootstrap 3 style button loading support
|
// Implement bootstrap 3 style button loading support
|
||||||
(function($) {
|
(function($) {
|
||||||
$.fn.button = function(action) {
|
$.fn.button = function(action) {
|
||||||
if (action === 'loading' && this.data('loading-text')) {
|
if (action === 'loading' && this.data('loading-text')) {
|
||||||
this.data('original-text', this.html()).html(this.data('loading-text')).prop('disabled', true);
|
this.data('original-text', this.html()).html(this.data('loading-text')).prop('disabled', true);
|
||||||
|
feather.replace();
|
||||||
}
|
}
|
||||||
if (action === 'reset' && this.data('original-text')) {
|
if (action === 'reset' && this.data('original-text')) {
|
||||||
this.html(this.data('original-text')).prop('disabled', false);
|
this.html(this.data('original-text')).prop('disabled', false);
|
||||||
}
|
|
||||||
feather.replace();
|
feather.replace();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}(jQuery));
|
}(jQuery));
|
||||||
*/
|
|
||||||
|
|
||||||
// HTML escaping
|
// HTML escaping
|
||||||
var entityMap = {
|
var entityMap = {
|
||||||
|
@ -41,8 +40,9 @@ function displayStatus(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable and disable buttons based on connection status
|
// Enable and disable buttons based on connection status
|
||||||
var connectedButtonIds = ['systemRestart', 'networkApproach', 'visionUp', 'visionDown', 'visionTerm', 'visionKill', 'systemReadOnly', 'systemWritable'];
|
var connectedButtonIds = ['systemRestart', 'networkApproach', 'networkAddress', 'networkMask', 'networkGateway', 'networkDNS', 'visionUp', 'visionDown', 'visionTerm', 'visionKill', 'systemReadOnly', 'systemWritable', 'visionClient', 'visionTeam', 'visionDiscard', 'addCamera', 'applicationType'];
|
||||||
var writableButtonIds = ['networkSave'];
|
var connectedButtonClasses = ['cameraName', 'cameraPath', 'cameraPixelFormat', 'cameraWidth', 'cameraHeight', 'cameraFps', 'cameraBrightness', 'cameraWhiteBalance', 'cameraExposure', 'cameraProperties', 'cameraRemove']
|
||||||
|
var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave'];
|
||||||
var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s',
|
var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s',
|
||||||
'systemMemoryAvail1s', 'systemMemoryAvail5s',
|
'systemMemoryAvail1s', 'systemMemoryAvail5s',
|
||||||
'systemCpuUser1s', 'systemCpuUser5s',
|
'systemCpuUser1s', 'systemCpuUser5s',
|
||||||
|
@ -57,6 +57,9 @@ function displayDisconnected() {
|
||||||
for (var i = 0; i < connectedButtonIds.length; i++) {
|
for (var i = 0; i < connectedButtonIds.length; i++) {
|
||||||
$('#' + connectedButtonIds[i]).prop('disabled', true);
|
$('#' + 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++) {
|
for (var i = 0; i < systemStatusIds.length; i++) {
|
||||||
$('#' + systemStatusIds[i]).text("");
|
$('#' + systemStatusIds[i]).text("");
|
||||||
}
|
}
|
||||||
|
@ -67,6 +70,9 @@ function displayConnected() {
|
||||||
for (var i = 0; i < connectedButtonIds.length; i++) {
|
for (var i = 0; i < connectedButtonIds.length; i++) {
|
||||||
$('#' + connectedButtonIds[i]).prop('disabled', false);
|
$('#' + 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
|
// Enable and disable buttons based on writable status
|
||||||
|
@ -105,6 +111,10 @@ $('#systemWritable').click(function() {
|
||||||
connection.send(JSON.stringify(msg));
|
connection.send(JSON.stringify(msg));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Vision settings
|
||||||
|
var visionSettingsServer = {};
|
||||||
|
var visionSettingsDisplay = {'cameras': []};
|
||||||
|
|
||||||
// WebSocket automatic reconnection timer
|
// WebSocket automatic reconnection timer
|
||||||
var reconnectTimerId = 0;
|
var reconnectTimerId = 0;
|
||||||
|
|
||||||
|
@ -132,6 +142,9 @@ function connect() {
|
||||||
// WebSocket incoming message handling
|
// WebSocket incoming message handling
|
||||||
connection.onmessage = function(evt) {
|
connection.onmessage = function(evt) {
|
||||||
var msg = JSON.parse(evt.data);
|
var msg = JSON.parse(evt.data);
|
||||||
|
if (msg === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case 'systemStatus':
|
case 'systemStatus':
|
||||||
for (var i = 0; i < systemStatusIds.length; i++) {
|
for (var i = 0; i < systemStatusIds.length; i++) {
|
||||||
|
@ -160,6 +173,19 @@ function connect() {
|
||||||
$('#networkDNS').val(msg.networkDNS);
|
$('#networkDNS').val(msg.networkDNS);
|
||||||
updateNetworkSettingsView();
|
updateNetworkSettingsView();
|
||||||
break;
|
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':
|
case 'systemReadOnly':
|
||||||
displayReadOnly();
|
displayReadOnly();
|
||||||
break;
|
break;
|
||||||
|
@ -303,47 +329,198 @@ $('#visionClient').change(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update view from data structure
|
function updateVisionCameraView(camera, value) {
|
||||||
var visionSettings = {
|
if ('name' in value) {
|
||||||
team: '294',
|
camera.find('.cameraTitle').text('Camera ' + value.name);
|
||||||
ntmode: 'client',
|
camera.find('.cameraName').val(value.name);
|
||||||
cameras: []
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateVisionSettingsCameraView(cardElem, data) {
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateVisionSettingsView() {
|
|
||||||
$('#visionClient').prop('checked', visionSettings.ntmode === 'client');
|
|
||||||
if (visionSettings.ntmode === 'client') {
|
|
||||||
$('#visionClientDetails').collapse('show');
|
|
||||||
} else {
|
|
||||||
$('#visionClientDetails').collapse('hide');
|
|
||||||
}
|
}
|
||||||
$('#visionTeam').val(visionSettings.team);
|
if ('path' in value) {
|
||||||
|
camera.find('.cameraPath').val(value.path);
|
||||||
var newCamera = $('#cameraNEW').clone();
|
}
|
||||||
newCamera.find('[id]').each(function() {
|
camera.find('.cameraPixelFormat').val(value['pixel format']);
|
||||||
$(this).attr('id', $(this).attr('id').replace('NEW', ''));
|
camera.find('.cameraWidth').val(value.width);
|
||||||
});
|
camera.find('.cameraHeight').val(value.height);
|
||||||
newCamera.find('[for]').each(function() {
|
camera.find('.cameraFps').val(value.fps);
|
||||||
$(this).attr('for', $(this).attr('for').replace('NEW', ''));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#cameraSettingsFile0').change(function() {
|
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) {
|
if (this.files.length <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var fr = new FileReader();
|
var fr = new FileReader();
|
||||||
fr.onload = function(e) {
|
fr.onload = function(e) {
|
||||||
var result = JSON.parse(e.target.result);
|
var result = JSON.parse(e.target.result);
|
||||||
console.log(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));
|
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() {
|
||||||
|
var isClient = !visionSettingsDisplay.ntmode || visionSettingsDisplay.ntmode === 'client';
|
||||||
|
$('#visionClient').prop('checked', isClient);
|
||||||
|
if (isClient) {
|
||||||
|
$('#visionClientDetails').collapse('show');
|
||||||
|
} else {
|
||||||
|
$('#visionClientDetails').collapse('hide');
|
||||||
|
}
|
||||||
|
$('#visionTeam').val(visionSettingsDisplay.team);
|
||||||
|
|
||||||
|
$('.cameraSetting').remove();
|
||||||
|
visionSettingsDisplay.cameras.forEach(function (value, i) {
|
||||||
|
appendNewVisionCameraView(value, i);
|
||||||
|
});
|
||||||
|
feather.replace();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#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) {
|
||||||
|
connection.send(e.target.result);
|
||||||
|
};
|
||||||
|
fr.readAsArrayBuffer(f.files.item(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start with display disconnected and start initial connection attempt
|
// Start with display disconnected and start initial connection attempt
|
||||||
displayDisconnected();
|
displayDisconnected();
|
||||||
|
updateNetworkSettingsView();
|
||||||
updateVisionSettingsView();
|
updateVisionSettingsView();
|
||||||
|
updateApplicationView();
|
||||||
connect();
|
connect();
|
||||||
|
|
188
deps/tools/rpiConfigServer_src/resources/index.html
vendored
188
deps/tools/rpiConfigServer_src/resources/index.html
vendored
|
@ -56,6 +56,12 @@
|
||||||
Vision Settings
|
Vision Settings
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="application-tab" data-toggle="tab" href="#application">
|
||||||
|
<i class="nav-icon" data-feather="cpu"></i>
|
||||||
|
Application
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,7 +214,7 @@
|
||||||
<div class="tab-pane" id="vision-settings" role="tabpanel" aria-labelledby="vision-settings-tab">
|
<div class="tab-pane" id="vision-settings" role="tabpanel" aria-labelledby="vision-settings-tab">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5>Network Tables</h5>
|
<h6>Network Tables</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form>
|
<form>
|
||||||
|
@ -231,105 +237,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="accordion" id="cameras">
|
<div class="accordion" id="cameras">
|
||||||
<div class="card" id="camera0">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col-auto mr-auto">
|
|
||||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#cameraBody0">
|
|
||||||
<h5>Camera 1</h5>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<a class="btn btn-sm btn-success" href="" target="_blank" role="button" id="cameraStream0">
|
|
||||||
<span data-feather="play"></span>
|
|
||||||
Open Stream
|
|
||||||
</a>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" id="cameraRemove0">
|
|
||||||
<span data-feather="x"></span>
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="collapse" id="cameraBody0" data-parent="#cameras">
|
|
||||||
<div class="card-body">
|
|
||||||
<form>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col">
|
|
||||||
<label for="cameraName0">Name</label>
|
|
||||||
<input type="text" class="form-control" id="cameraName0">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col">
|
|
||||||
<label for="cameraPath0">Path</label>
|
|
||||||
<input type="text" class="form-control" id="cameraPath0">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="custom-file">
|
|
||||||
<input type="file" class="custom-file-input" accept=".json,text/json,application/json" id="cameraSettingsFile0">
|
|
||||||
<label class="custom-file-label" for="cameraSettingsFile0">Load camera settings from JSON file</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraPixelFormat0">Pixel Format</label>
|
|
||||||
<select class="form-control" id="cameraPixelFormat0">
|
|
||||||
<option value="mjpeg">MJPEG</option>
|
|
||||||
<option value="yuyv">YUYV</option>
|
|
||||||
<option value="rgb565">RGB565</option>
|
|
||||||
<option value="bgr">BGR</option>
|
|
||||||
<option value="gray">Gray</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraWidth0">Width</label>
|
|
||||||
<input type="text" class="form-control" id="cameraWidth0" size="5">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraHeight0">Height</label>
|
|
||||||
<input type="text" class="form-control" id="cameraHeight0" size="5">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraFps0">FPS</label>
|
|
||||||
<input type="text" class="form-control" id="cameraFps0" size="4">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraBrightness0">Brightness</label>
|
|
||||||
<input type="text" class="form-control" id="cameraBrightness0" size="4">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraBrightness0">White Balance</label>
|
|
||||||
<input type="text" class="form-control" id="cameraWhiteBalance0" size="4">
|
|
||||||
</div>
|
|
||||||
<div class="form-group col-auto">
|
|
||||||
<label for="cameraBrightness0">Exposure</label>
|
|
||||||
<input type="text" class="form-control" id="cameraExposure0" size="4">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="cameraProperties0">Custom Properties JSON</label>
|
|
||||||
<textarea class="form-control" id="cameraProperties0" rows="3"></textarea>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card" style="display:none" id="cameraNEW">
|
<div class="card" style="display:none" id="cameraNEW">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-auto mr-auto">
|
<div class="col-auto mr-auto">
|
||||||
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#cameraBodyNEW">
|
<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#cameraBodyNEW">
|
||||||
<h5>Camera NEW</h5>
|
<h6 class="cameraTitle">Camera NEW</h6>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<a class="btn btn-sm btn-success" href="" target="_blank" role="button" id="cameraStreamNEW">
|
<a class="btn btn-sm btn-success cameraStream" href="" target="_blank" role="button">
|
||||||
<span data-feather="play"></span>
|
<span data-feather="play"></span>
|
||||||
Open Stream
|
Open Stream
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-sm btn-danger" id="cameraRemoveNEW">
|
<button type="button" class="btn btn-sm btn-danger cameraRemove">
|
||||||
<span data-feather="x"></span>
|
<span data-feather="x"></span>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
@ -342,23 +263,23 @@
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col">
|
<div class="form-group col">
|
||||||
<label for="cameraNameNEW">Name</label>
|
<label for="cameraNameNEW">Name</label>
|
||||||
<input type="text" class="form-control" id="cameraNameNEW">
|
<input type="text" class="form-control cameraName" id="cameraNameNEW">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col">
|
<div class="form-group col">
|
||||||
<label for="cameraPathNEW">Path</label>
|
<label for="cameraPathNEW">Path</label>
|
||||||
<input type="text" class="form-control" id="cameraPathNEW">
|
<input type="text" class="form-control cameraPath" id="cameraPathNEW">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="custom-file">
|
<div class="custom-file">
|
||||||
<input type="file" class="custom-file-input" accept=".json,text/json,application/json" id="cameraSettingsFileNEW">
|
<input type="file" class="custom-file-input cameraSettingsFile" accept=".json,text/json,application/json" id="cameraSettingsFileNEW">
|
||||||
<label class="custom-file-label" for="cameraSettingsFileNEW">Load camera settings from JSON file</label>
|
<label class="custom-file-label" for="cameraSettingsFileNEW">Load source config from JSON file</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraPixelFormatNEW">Pixel Format</label>
|
<label for="cameraPixelFormatNEW">Pixel Format</label>
|
||||||
<select class="form-control" id="cameraPixelFormatNEW">
|
<select class="form-control cameraPixelFormat" id="cameraPixelFormatNEW">
|
||||||
<option value="mjpeg">MJPEG</option>
|
<option value="mjpeg">MJPEG</option>
|
||||||
<option value="yuyv">YUYV</option>
|
<option value="yuyv">YUYV</option>
|
||||||
<option value="rgb565">RGB565</option>
|
<option value="rgb565">RGB565</option>
|
||||||
|
@ -368,34 +289,34 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraWidthNEW">Width</label>
|
<label for="cameraWidthNEW">Width</label>
|
||||||
<input type="text" class="form-control" id="cameraWidthNEW" size="5">
|
<input type="number" class="form-control cameraWidth" id="cameraWidthNEW" size="5">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraHeightNEW">Height</label>
|
<label for="cameraHeightNEW">Height</label>
|
||||||
<input type="text" class="form-control" id="cameraHeightNEW" size="5">
|
<input type="number" class="form-control cameraHeight" id="cameraHeightNEW" size="5">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraFpsNEW">FPS</label>
|
<label for="cameraFpsNEW">FPS</label>
|
||||||
<input type="text" class="form-control" id="cameraFpsNEW" size="4">
|
<input type="number" class="form-control cameraFps" id="cameraFpsNEW" size="4">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraBrightnessNEW">Brightness</label>
|
<label for="cameraBrightnessNEW">Brightness</label>
|
||||||
<input type="text" class="form-control" id="cameraBrightnessNEW" size="4">
|
<input type="text" class="form-control cameraBrightness" id="cameraBrightnessNEW" size="4">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraBrightnessNEW">White Balance</label>
|
<label for="cameraWhiteBalanceNEW">White Balance</label>
|
||||||
<input type="text" class="form-control" id="cameraWhiteBalanceNEW" size="4">
|
<input type="text" class="form-control cameraWhiteBalance" id="cameraWhiteBalanceNEW" size="4">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-auto">
|
<div class="form-group col-auto">
|
||||||
<label for="cameraExposureNEW">Exposure</label>
|
<label for="cameraExposureNEW">Exposure</label>
|
||||||
<input type="text" class="form-control" id="cameraExposureNEW" size="4">
|
<input type="text" class="form-control cameraExposure" id="cameraExposureNEW" size="4">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="cameraPropertiesNEW">Custom Properties JSON</label>
|
<label for="cameraPropertiesNEW">Custom Properties JSON</label>
|
||||||
<textarea class="form-control" id="cameraPropertiesNEW" rows="3"></textarea>
|
<textarea class="form-control cameraProperties" id="cameraPropertiesNEW" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -423,6 +344,69 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Application -->
|
||||||
|
<div class="tab-pane" id="application" role="tabpanel" aria-labelledby="application-tab">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6>Vision Application Configuration</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="applicationType">Application</label>
|
||||||
|
<select class="form-control" id="applicationType">
|
||||||
|
<option value="builtin">Built-in multi-camera streaming</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
<option value="example-java">Java example (must be built to work)</option>
|
||||||
|
<option value="example-cpp">C++ example (must be built to work)</option>
|
||||||
|
<option value="example-python">Python example</option>
|
||||||
|
<option value="upload-java">Uploaded Java jar</option>
|
||||||
|
<option value="upload-cpp">Uploaded C++ executable</option>
|
||||||
|
<option value="upload-python">Uploaded Python file</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="collapse" id="applicationUpload">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="applicationFile">Upload executable file</label>
|
||||||
|
<input type="file" class="form-control-file" id="applicationFile">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="applicationSave" type="button" class="btn btn-primary" data-loading-text="<i class='spin' data-feather='loader'></i> Saving" disabled>
|
||||||
|
<span data-feather="save"></span>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6>Example Programs</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-auto">
|
||||||
|
<a class="btn btn-sm btn-success" href="java-multiCameraServer.zip" target="_blank" role="button">
|
||||||
|
<span data-feather="download"></span>
|
||||||
|
Download Java Example
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a class="btn btn-sm btn-success" href="cpp-multiCameraServer.zip" target="_blank" role="button">
|
||||||
|
<span data-feather="download"></span>
|
||||||
|
Download C++ Example
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<a class="btn btn-sm btn-success" href="python-multiCameraServer.zip" target="_blank" role="button">
|
||||||
|
<span data-feather="download"></span>
|
||||||
|
Download Python Example
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -116,6 +116,7 @@ EOF
|
||||||
|
|
||||||
install -v -o 1000 -g 1000 -d "${ROOTFS_DIR}/home/pi/javalibs/"
|
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/"
|
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
|
on_chroot << EOF
|
||||||
ldconfig
|
ldconfig
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
### TYPE: builtin
|
||||||
echo "Waiting 5 seconds..."
|
echo "Waiting 5 seconds..."
|
||||||
sleep 5
|
sleep 5
|
||||||
exec ./multiCameraServer
|
exec ./multiCameraServer
|
||||||
|
|
Loading…
Reference in New Issue
Block a user