Skip to content

Commit

Permalink
Add ability to read/write Romi IO Config (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiquanyeo authored Dec 4, 2020
1 parent 967c026 commit c0ffad5
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 2 deletions.
5 changes: 4 additions & 1 deletion deps/tools/configServer/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ OBJS= \
src/WebSocketHandlers.o \
src/resources/index.html.o \
src/resources/frcvision.css.o \
src/resources/frcvision.js.o
src/resources/frcvision.js.o \
src/resources/romi_ext_io.png.o

configServer: ${OBJS}
${CXX} -pthread -g -o $@ ${CXXFLAGS} $^ ${DEPS_LIBS}
Expand Down Expand Up @@ -66,3 +67,5 @@ configServer: ${OBJS}
%.js.cpp: %.js
env IMG_VERSION=${IMG_VERSION} ./gen_resource.py $@ $<

%.png.cpp: %.png
env IMG_VERSION=${IMG_VERSION} ./gen_resource.py $@ $<
4 changes: 4 additions & 0 deletions deps/tools/configServer/src/MyHttpConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ StringRef GetResource_wpilib_128_png();
wpi::StringRef GetResource_frcvision_css();
wpi::StringRef GetResource_frcvision_js();
wpi::StringRef GetResource_index_html();
wpi::StringRef GetResource_romi_ext_io_png();

MyHttpConnection::MyHttpConnection(std::shared_ptr<wpi::uv::Stream> stream)
: HttpServerConnection(stream), m_websocketHelper(m_request) {
Expand Down Expand Up @@ -221,6 +222,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.equals("/romi_ext_io.png")) {
SendStaticResponse(200, "OK", "image/png",
GetResource_romi_ext_io_png(), false);
} else if (isGET && path.startswith("/") && path.endswith(".zip") &&
!path.contains("..")) {
SendFileResponse(200, "OK", "application/zip", wpi::Twine(ZIPS_DIR) + path);
Expand Down
62 changes: 62 additions & 0 deletions deps/tools/configServer/src/RomiStatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

#include <cstring>

#include <wpi/FileSystem.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpi/SmallString.h>
#include <wpi/StringRef.h>
#include <wpi/json.h>
Expand All @@ -30,6 +33,12 @@ namespace uv = wpi::uv;

#define SERVICE "/service/wpilibws-romi"

#ifdef __RASPBIAN__
static const char* configFile = "/boot/romi.json";
#else
static const char* configFile = "romi.json";
#endif

std::shared_ptr<RomiStatus> RomiStatus::GetInstance() {
static auto status = std::make_shared<RomiStatus>(private_init{});
return status;
Expand Down Expand Up @@ -226,3 +235,56 @@ void RomiStatus::FirmwareUpdate(std::function<void(wpi::StringRef)> onFail) {
onFail("could not spawn process");
}
}

void RomiStatus::UpdateIoConfig(std::function<void(wpi::StringRef)> onFail) {
ioConfig(GetIoConfigJson(onFail));
}

wpi::json RomiStatus::GetIoConfigJson(std::function<void(wpi::StringRef)> onFail) {
// Read config file
std::error_code ec;
wpi::raw_fd_istream is(configFile, ec);

if (ec) {
onFail("Could not read romi config file");
wpi::errs() << "could not read " << configFile << "\n";
wpi::json();
}

wpi::json j;
try {
j = wpi::json::parse(is);
} catch(const wpi::json::parse_error& e) {
onFail("Parse error in config file");
wpi::errs() << "Parse error in " << configFile << ": byte "
<< e.byte <<": " << e.what() << "\n";
return wpi::json();
}

if (!j.is_object()) {
onFail("Top level must be a JSON object");
wpi::errs() << "must be a JSON object\n";
return wpi::json();
}

return {{"type", "romiExternalIOConfig"},
{"romiConfig", j}};
}

void RomiStatus::SaveConfig(const wpi::json& data, std::function<void(wpi::StringRef)> onFail) {
{
// write file
std::error_code ec;
wpi::raw_fd_ostream os(configFile, ec, wpi::sys::fs::F_Text);
if (ec) {
onFail("could not write to romi config");
return;
}
data.dump(os, 4);
os << "\n";
}

// Terminate Romi process so it reloads the file
Terminate(onFail);
UpdateIoConfig(onFail);
}
6 changes: 6 additions & 0 deletions deps/tools/configServer/src/RomiStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ class RomiStatus {

void FirmwareUpdate(std::function<void(wpi::StringRef)> onFail);

void UpdateIoConfig(std::function<void(wpi::StringRef)> onFail);

void SaveConfig(const wpi::json& data, std::function<void(wpi::StringRef)> onFail);

wpi::sig::Signal<const wpi::json&> update;
wpi::sig::Signal<const wpi::json&> log;
wpi::sig::Signal<const wpi::json&> ioConfig;

static std::shared_ptr<RomiStatus> GetInstance();

private:
void RunSvc(const char* cmd, std::function<void(wpi::StringRef)> onFail);
wpi::json GetIoConfigJson(std::function<void(wpi::StringRef)> onFail);

std::shared_ptr<wpi::uv::Loop> m_loop;
};
Expand Down
11 changes: 11 additions & 0 deletions deps/tools/configServer/src/WebSocketHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct WebSocketData {
wpi::sig::ScopedConnection visLogConn;
wpi::sig::ScopedConnection romiStatusConn;
wpi::sig::ScopedConnection romiLogConn;
wpi::sig::ScopedConnection romiIoConfigConn;
wpi::sig::ScopedConnection cameraListConn;
wpi::sig::ScopedConnection netSettingsConn;
wpi::sig::ScopedConnection visSettingsConn;
Expand Down Expand Up @@ -150,7 +151,10 @@ void InitWs(wpi::WebSocket& ws) {
auto d = ws.GetData<WebSocketData>();
if (d->romiLogEnabled) SendWsText(ws, j);
});
data->romiIoConfigConn = romiStatus->ioConfig.connect_connection(
[&ws](const wpi::json& j) { SendWsText(ws, j); });
romiStatus->UpdateStatus();
romiStatus->UpdateIoConfig(statusFunc);
}

// send initial network settings
Expand Down Expand Up @@ -286,6 +290,13 @@ void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) {
<< '\n';
return;
}
} else if (subType == "SaveExternalIOConfig") {
try {
RomiStatus::GetInstance()->SaveConfig(j.at("romiConfig"), statusFunc);
} catch (const wpi::json::exception& e) {
wpi::errs() << "could not read romiConfig value: " << e.what() << "\n";
return;
}
}
} else if (t == "networkSave") {
auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) {
Expand Down
96 changes: 95 additions & 1 deletion deps/tools/configServer/src/resources/frcvision.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ var connectedButtonIds = [
'romiDown',
'romiTerm',
'romiKill',
'romiExtIO0',
'romiExtIO1',
'romiExtIO2',
'romiExtIO3',
'romiExtIO4',
'visionUp',
'visionDown',
'visionTerm',
Expand Down Expand Up @@ -102,7 +107,7 @@ var connectedButtonClasses = [
'cameraCopyConfig',
'cameraKey'
];
var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave', 'fileUploadButton'];
var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave', 'fileUploadButton', 'romiSaveExternalIOConfig'];
var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s',
'systemMemoryAvail1s', 'systemMemoryAvail5s',
'systemCpuUser1s', 'systemCpuUser5s',
Expand Down Expand Up @@ -194,6 +199,31 @@ function pushRomiLogEnabled() {
connection.send(JSON.stringify(msg));
}

function updateRomiRobotPorts() {
// Starting channel numbers
var digitalChannel = 8;
var analogChannel = 0;
var pwmChannel = 2;

for (var i = 0; i < 5; i++) {
var chType = $("#romiExtIO" + i).val();
switch (chType) {
case "dio":
$("#romiRobotPort" + i).html("Digital " + digitalChannel);
digitalChannel++;
break;
case "ain":
$("#romiRobotPort" + i).html("Analog In " + analogChannel);
analogChannel++;
break;
case "pwm":
$("#romiRobotPort" + i).html("PWM " + pwmChannel);
pwmChannel++;
break;
}
}
}

// WebSocket automatic reconnection timer
var reconnectTimerId = 0;

Expand Down Expand Up @@ -226,6 +256,7 @@ function connect() {
if (msg === null) {
return;
}

switch (msg.type) {
case 'romiEnable':
$('#romi-nav-item').removeAttr('style');
Expand Down Expand Up @@ -273,6 +304,15 @@ function connect() {
case 'romiFirmwareLog':
romiFirmwareLog(msg.data);
break;
case 'romiExternalIOConfig':
// Pre-fill the IO config dropdowns
if (msg.romiConfig && msg.romiConfig.ioConfig) {
for (var i = 0; i < msg.romiConfig.ioConfig.length; i++) {
$('#romiExtIO' + i).val(msg.romiConfig.ioConfig[i]);
}
updateRomiRobotPorts();
}
break;
case 'networkSettings':
$('#networkApproach').val(msg.networkApproach);
$('#networkAddress').val(msg.networkAddress);
Expand Down Expand Up @@ -412,6 +452,60 @@ $('#romiLogEnabled').change(function() {
pushRomiLogEnabled();
});

$('#romiSaveExternalIOConfig').click(function() {
var ioConfig = [];

for (var i = 0; i < 5; i++) {
ioConfig.push($('#romiExtIO' + i).val());
}

var msg = {
type: 'romiSaveExternalIOConfig',
romiConfig: {
ioConfig: ioConfig
}
};
connection.send(JSON.stringify(msg));
});

// Set up the Romi status query
setInterval(function() {
var baseUrl = "http://" + window.location.hostname + ":9001";
fetch(baseUrl + "/status")
.then(function(response) { return response.json(); })
.then(function(status) {
if (status["service-version"]) {
$("#romiServiceVersion").html(status["service-version"].serviceVersion);
}
else {
$("#romiServiceVersion").html("---");
}

if (status["firmware-status"]) {
var firmwareCompatString = status["firmware-status"].firmwareMatch ?
"<span class='font-weight-bold text-success'>Yes</span>" :
"<span class='font-weight-bold text-danger'>No</span>";
$("#romiFirmwareCompatible").html(firmwareCompatString);
}
else {
$("#romiFirmwareCompatible").html("---");
}

if (status["battery-status"]) {
$("#romiBatteryVoltage").html(status["battery-status"].voltage.toFixed(2));
}
else {
$("#romiBatteryVoltage").html("---");
}
})
.catch(function (err) {
$("#romiServiceVersion").html("---");
$("#romiFirmwareCompatible").html("---");
$("#romiBatteryVoltage").html("---");
});

}, 2000);

//
// Vision console output
//
Expand Down
Loading

0 comments on commit c0ffad5

Please sign in to comment.