Skip to content

Commit

Permalink
Add support for offline updates of Romi webservice (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhiquanyeo authored Dec 10, 2020
1 parent bec26e9 commit b720ec9
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 12 deletions.
6 changes: 5 additions & 1 deletion deps/tools/configServer/Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
IMG_VERSION?=$(shell git describe)
DEPS_CFLAGS?=$(shell pkg-config --cflags cscore wpiutil) -std=c++17
DEPS_LIBS?=$(shell pkg-config --libs --static cscore wpiutil)
EXEC_HOME?=/home/${FIRST_USER_NAME}
EXEC_HOME?=/home/pi
FRC_JSON?=/boot/frc.json
DHCPCD_CONF?=/boot/dhcpcd.conf
WPA_SUPPLICANT_CONF?=/etc/wpa_supplicant/wpa_supplicant.conf
DNSMASQ_CONF?=/etc/dnsmasq.d/wpilib.conf
HOSTAPD_CONF?=/etc/hostapd/hostapd.conf
APP_UID?=1000
APP_GID?=1000
ROMI_JSON?=/boot/romi.json
NODE_HOME?=/home/pi/.nvm/versions/node/v14.15.0/bin

.PHONY: all clean install
.SUFFIXES:
Expand Down Expand Up @@ -56,6 +58,8 @@ configServer: ${OBJS}
'-DHOSTAPD_CONF="${HOSTAPD_CONF}"' \
'-DAPP_UID=${APP_UID}' \
'-DAPP_GID=${APP_GID}' \
'-DROMI_JSON="${ROMI_JSON}"' \
'-DNODE_HOME="${NODE_HOME}"' \
$<

%.html.cpp: %.html
Expand Down
14 changes: 4 additions & 10 deletions deps/tools/configServer/src/RomiStatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@ 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 @@ -243,11 +237,11 @@ void RomiStatus::UpdateIoConfig(std::function<void(wpi::StringRef)> 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);
wpi::raw_fd_istream is(ROMI_JSON, ec);

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

Expand All @@ -256,7 +250,7 @@ wpi::json RomiStatus::GetIoConfigJson(std::function<void(wpi::StringRef)> onFail
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 "
wpi::errs() << "Parse error in " << ROMI_JSON << ": byte "
<< e.byte <<": " << e.what() << "\n";
return wpi::json();
}
Expand All @@ -275,7 +269,7 @@ void RomiStatus::SaveConfig(const wpi::json& data, std::function<void(wpi::Strin
{
// write file
std::error_code ec;
wpi::raw_fd_ostream os(configFile, ec, wpi::sys::fs::F_Text);
wpi::raw_fd_ostream os(ROMI_JSON, ec, wpi::sys::fs::F_Text);
if (ec) {
onFail("could not write to romi config");
return;
Expand Down
62 changes: 62 additions & 0 deletions deps/tools/configServer/src/WebSocketHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,68 @@ void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) {
wpi::errs() << "could not read romiConfig value: " << e.what() << "\n";
return;
}
} else if (subType == "ServiceStartUpload") {
auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) {
SendWsText(*s, {{"type", "status"}, {"message", msg}});
};
auto d = ws.GetData<WebSocketData>();
d->upload.Open(EXEC_HOME "/romiUploadXXXXXX", false, statusFunc);
}
else if (subType == "ServiceFinishUpload") {
auto d = ws.GetData<WebSocketData>();

if (fchown(d->upload.GetFD(), APP_UID, APP_GID) == -1) {
wpi::errs() << "could not change file ownership: "
<< std::strerror(errno) << "\n";
}
d->upload.Close();

std::string filename;
try {
filename = j.at("fileName").get<std::string>();
} catch (const wpi::json::exception& e) {
wpi::errs() << "could not read fileName value: " << e.what() << "\n";
unlink(d->upload.GetFilename());
return;
}

wpi::SmallString<64> pathname;
pathname = EXEC_HOME;
pathname += '/';
pathname += filename;
if (unlink(pathname.c_str()) == -1) {
wpi::errs() << "could not remove file: " << std::strerror(errno)
<< "\n";
}

// Rename temporary file to new file
if (rename(d->upload.GetFilename(), pathname.c_str()) == -1) {
wpi::errs() << "could not rename: " << std::strerror(errno) << "\n";
}

auto installSuccess = [sf = statusFunc](wpi::WebSocket& s) {
auto d = s.GetData<WebSocketData>();
unlink(d->upload.GetFilename());
SendWsText(s, {{"type", "romiServiceUploadComplete"}, {"success", true}});
RomiStatus::GetInstance()->Up(sf);
};

auto installFailure = [sf = statusFunc](wpi::WebSocket& s) {
auto d = s.GetData<WebSocketData>();
unlink(d->upload.GetFilename());
wpi::errs() << "could not install service\n";
SendWsText(s, {{"type", "romiServiceUploadComplete"}});
RomiStatus::GetInstance()->Up(sf);
};

RomiStatus::GetInstance()->Down(statusFunc);
RunProcess(ws, installSuccess, installFailure, NODE_HOME "/npm",
uv::Process::Uid(APP_UID), uv::Process::Gid(APP_GID),
uv::Process::Cwd(EXEC_HOME),
uv::Process::Env("PATH=$PATH:" NODE_HOME),
NODE_HOME "/npm",
"install", "-g", pathname);

}
} else if (t == "networkSave") {
auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) {
Expand Down
62 changes: 61 additions & 1 deletion deps/tools/configServer/src/resources/frcvision.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ var connectedButtonClasses = [
'cameraCopyConfig',
'cameraKey'
];
var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave', 'fileUploadButton', 'romiSaveExternalIOConfig'];
var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave', 'fileUploadButton', 'romiSaveExternalIOConfig', 'romiServiceUploadButton'];
var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s',
'systemMemoryAvail1s', 'systemMemoryAvail5s',
'systemCpuUser1s', 'systemCpuUser5s',
Expand Down Expand Up @@ -313,6 +313,13 @@ function connect() {
updateRomiRobotPorts();
}
break;
case 'romiServiceUploadComplete':
$('#romiServiceUploadButton').button('reset');
updateRomiServiceUploadView();
if (msg.success) {
displaySuccess('Romi WebService successfully uploaded!');
}
break;
case 'networkSettings':
$('#networkApproach').val(msg.networkApproach);
$('#networkAddress').val(msg.networkAddress);
Expand Down Expand Up @@ -1232,6 +1239,58 @@ $('#fileUploadButton').click(function() {
uploadFile(0);
});

// Romi Service upload
var romiServiceUploadFiles = [];

function updateRomiServiceUploadView() {
$('#romiServiceUploadFile').val(null);
fileUploadFiles = [];
}

$('#romiServiceUploadFile').change(function() {
romiServiceUploadFiles = this.files;
dismissStatus();
});

$('#romiServiceUploadButton').click(function() {
if (romiServiceUploadFiles.length <= 0) {
return;
}

$('#romiServiceUploadButton').button('loading');

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

var reader = new FileReader();
var file = romiServiceUploadFiles.item(0);

function uploadFile(start) {
var nextSlice = start + (64 * 1024) + 1;
reader.onloadend = function(e) {
if (e.target.readyState !== FileReader.DONE) {
return;
}
connection.send(e.target.result);
if (nextSlice < file.size) {
// more to go
uploadFile(nextSlice);
} else {
// done
var msg = {
type: 'romiServiceFinishUpload',
fileName: file.name
};
connection.send(JSON.stringify(msg));
}
}
reader.readAsArrayBuffer(file.slice(start, nextSlice));
}
uploadFile(0);
});


// Start with display disconnected and start initial connection attempt
displayDisconnected();
Expand All @@ -1241,4 +1300,5 @@ updateWifiModeView();
updateVisionSettingsView();
updateApplicationView();
updateFileUploadView();
updateRomiServiceUploadView();
connect();
25 changes: 25 additions & 0 deletions deps/tools/configServer/src/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,31 @@ <h6>Romi Status</h6>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="row align-items-center">
<div class="col-auto mr-auto">
<h6>Web Service Update</h6>
</div>
</div>
</div>
<div class="card-body">
<p class="card-text">
To perform an offline update of the Romi webservice, obtain an appropriate
version from the GitHub release page, and upload the .tgz file here.
</p>
<form>
<div class="form-group">
<label for="romiServiceUploadFile">Upload Romi Webservice Package</label>
<input type="file" class="form-control-file" id="romiServiceUploadFile" accept=".tgz">
</div>
<button id="romiServiceUploadButton" type="button" class="btn btn-primary" data-loading-text="<i class='spin' data-feather='loader'></i> Saving" disabled>
<span data-feather="save"></span>
<span id="romiServiceUploadButtonText">Save</span>
</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<div class="row align-items-center">
Expand Down

0 comments on commit b720ec9

Please sign in to comment.