Add roboRIO Team Number Setter tool (#3744)

This commit is contained in:
Thad House
2021-11-30 11:17:30 -08:00
committed by GitHub
parent fa1ceca83a
commit aced2e7da6
22 changed files with 1080 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
cppHeaderFileInclude {
\.h$
\.inc$
\.inl$
}
cppSrcFileInclude {
\.cpp$
}
generatedFileExclude {
src/main/native/resources/
src/main/native/win/roborioteamnumbersetter.ico
src/main/native/mac/rtns.icns
}
repoRootNameOverride {
roborioteamnumbersetter
}
includeOtherLibs {
^GLFW
^fmt/
^imgui
^ntcore
^wpi/
^wpigui
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>roboRIOTeamNumberSetter</string>
<key>CFBundleExecutable</key>
<string>roborioteamnumbersetter</string>
<key>CFBundleDisplayName</key>
<string>roboRIOTeamNumberSetter</string>
<key>CFBundleIdentifier</key>
<string>edu.wpi.first.tools.roboRIOTeamNumberSetter</string>
<key>CFBundleIconFile</key>
<string>ov.icns</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>2021</string>
<key>CFBundleVersion</key>
<string>2021</string>
<key>LSMinimumSystemVersion</key>
<string>10.11</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,134 @@
import org.gradle.internal.os.OperatingSystem
if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxraspbian') && !project.hasProperty('onlylinuxaarch64bionic')) {
description = "roboRIO Team Number Setter"
apply plugin: 'cpp'
apply plugin: 'c'
apply plugin: 'google-test-test-suite'
apply plugin: 'visual-studio'
apply plugin: 'edu.wpi.first.NativeUtils'
if (OperatingSystem.current().isWindows()) {
apply plugin: 'windows-resources'
}
ext {
nativeName = 'roborioteamnumbersetter'
}
apply from: "${rootDir}/shared/resources.gradle"
apply from: "${rootDir}/shared/config.gradle"
def wpilibVersionFileInput = file("src/main/generate/WPILibVersion.cpp.in")
def wpilibVersionFileOutput = file("$buildDir/generated/main/cpp/WPILibVersion.cpp")
nativeUtils {
nativeDependencyContainer {
libssh(getNativeDependencyTypeClass('WPIStaticMavenDependency')) {
groupId = "edu.wpi.first.thirdparty.frc2022"
artifactId = "libssh"
headerClassifier = "headers"
sourceClassifier = "sources"
ext = "zip"
version = '0.95-1'
targetPlatforms.addAll(nativeUtils.wpi.platforms.desktopPlatforms)
}
}
}
task generateCppVersion() {
description = 'Generates the wpilib version class'
group = 'WPILib'
outputs.file wpilibVersionFileOutput
inputs.file wpilibVersionFileInput
if (wpilibVersioning.releaseMode) {
outputs.upToDateWhen { false }
}
// We follow a simple set of checks to determine whether we should generate a new version file:
// 1. If the release type is not development, we generate a new version file
// 2. If there is no generated version number, we generate a new version file
// 3. If there is a generated build number, and the release type is development, then we will
// only generate if the publish task is run.
doLast {
def version = wpilibVersioning.version.get()
println "Writing version ${version} to $wpilibVersionFileOutput"
if (wpilibVersionFileOutput.exists()) {
wpilibVersionFileOutput.delete()
}
def read = wpilibVersionFileInput.text.replace('${wpilib_version}', version)
wpilibVersionFileOutput.write(read)
}
}
gradle.taskGraph.addTaskExecutionGraphListener { graph ->
def willPublish = graph.hasTask(publish)
if (willPublish) {
generateCppVersion.outputs.upToDateWhen { false }
}
}
def generateTask = createGenerateResourcesTask('main', 'RTNS', 'rtns', project)
project(':').libraryBuild.dependsOn build
tasks.withType(CppCompile) {
dependsOn generateTask
dependsOn generateCppVersion
}
model {
components {
// By default, a development executable will be generated. This is to help the case of
// testing specific functionality of the library.
"${nativeName}"(NativeExecutableSpec) {
baseName = 'roborioteamnumbersetter'
sources {
cpp {
source {
srcDirs 'src/main/native/cpp', "$buildDir/generated/main/cpp"
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/include'
}
}
if (OperatingSystem.current().isWindows()) {
rc {
source {
srcDirs 'src/main/native/win'
include '*.rc'
}
}
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
it.buildable = false
return
}
it.cppCompiler.define("LIBSSH_STATIC")
lib project: ':glass', library: 'glass', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
nativeUtils.useRequiredLibrary(it, 'imgui_static', 'libssh')
if (it.targetPlatform.operatingSystem.isWindows()) {
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
it.linker.args << 'ws2_32.lib' << 'advapi32.lib' << 'crypt32.lib' << 'user32.lib'
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
it.linker.args << '-framework' << 'Kerberos'
} else {
it.linker.args << '-lX11'
}
}
}
}
}
apply from: 'publish.gradle'
}

View File

@@ -0,0 +1,107 @@
apply plugin: 'maven-publish'
def baseArtifactId = 'roboRIOTeamNumberSetter'
def artifactGroupId = 'edu.wpi.first.tools'
def zipBaseName = '_GROUP_edu_wpi_first_tools_ID_roboRIOTeamNumberSetter_CLS'
def outputsFolder = file("$project.buildDir/outputs")
model {
tasks {
// Create the run task.
$.components.roborioteamnumbersetter.binaries.each { bin ->
if (bin.buildable && bin.name.toLowerCase().contains("debug")) {
Task run = project.tasks.create("run", Exec) {
commandLine bin.tasks.install.runScriptFile.get().asFile.toString()
}
run.dependsOn bin.tasks.install
}
}
}
publishing {
def roboRIOTeamNumberSetterTaskList = []
$.components.each { component ->
component.binaries.each { binary ->
if (binary in NativeExecutableBinarySpec && binary.component.name.contains("roborioteamnumbersetter")) {
if (binary.buildable && binary.name.contains("Release")) {
// We are now in the binary that we want.
// This is the default application path for the ZIP task.
def applicationPath = binary.executable.file
def icon = file("$project.projectDir/src/main/native/mac/rtns.icns")
// Create the macOS bundle.
def bundleTask = project.tasks.create("bundleroboRIOTeamNumberSetterOsxApp", Copy) {
description("Creates a macOS application bundle for roboRIO Team Number Setter")
from(file("$project.projectDir/Info.plist"))
into(file("$project.buildDir/outputs/bundles/roboRIOTeamNumberSetter.app/Contents"))
into("MacOS") { with copySpec { from binary.executable.file } }
into("Resources") { with copySpec { from icon } }
doLast {
if (project.hasProperty("developerID")) {
// Get path to binary.
exec {
workingDir rootDir
def args = [
"sh",
"-c",
"codesign --force --strict --deep " +
"--timestamp --options=runtime " +
"--verbose -s ${project.findProperty("developerID")} " +
"$project.buildDir/outputs/bundles/roboRIOTeamNumberSetter.app/"
]
commandLine args
}
}
}
}
// Reset the application path if we are creating a bundle.
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
applicationPath = file("$project.buildDir/outputs/bundles")
project.build.dependsOn bundleTask
}
// Create the ZIP.
def task = project.tasks.create("copyroboRIOTeamNumberSetterExecutable", Zip) {
description("Copies the roboRIOTeamNumberSetter executable to the outputs directory.")
destinationDirectory = outputsFolder
archiveBaseName = '_M_' + zipBaseName
duplicatesStrategy = 'exclude'
classifier = nativeUtils.getPublishClassifier(binary)
from(licenseFile) {
into '/'
}
from(applicationPath)
into(nativeUtils.getPlatformPath(binary))
}
if (binary.targetPlatform.operatingSystem.isMacOsX()) {
bundleTask.dependsOn binary.tasks.link
task.dependsOn(bundleTask)
}
task.dependsOn binary.tasks.link
roboRIOTeamNumberSetterTaskList.add(task)
project.build.dependsOn task
project.artifacts { task }
addTaskToCopyAllOutputs(task)
}
}
}
}
publications {
roborioteamnumbersetter(MavenPublication) {
roboRIOTeamNumberSetterTaskList.each { artifact it }
artifactId = baseArtifactId
groupId = artifactGroupId
version wpilibVersioning.version.get()
}
}
}
}

View File

@@ -0,0 +1,7 @@
/*
* Autogenerated file! Do not manually edit this file. This version is regenerated
* any time the publish task is run, or when this file is deleted.
*/
const char* GetWPILibVersion() {
return "${wpilib_version}";
}

View File

@@ -0,0 +1,265 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <memory>
#include <string_view>
#ifndef _WIN32
#include <arpa/inet.h>
#endif
#include <fmt/format.h>
#include <glass/MainMenuBar.h>
#include <glass/Context.h>
#include <glass/Storage.h>
#include <glass/Window.h>
#include <glass/WindowManager.h>
#include <glass/other/Log.h>
#include <imgui.h>
#include <libssh/libssh.h>
#include <wpi/Logger.h>
#include <wpi/fs.h>
#include <wpigui.h>
#include <unordered_map>
#include <mutex>
#include "wpi/SmallString.h"
#include "DeploySession.h"
#include "wpi/MulticastServiceResolver.h"
namespace gui = wpi::gui;
const char* GetWPILibVersion();
#define GLFWAPI extern "C"
GLFWAPI void glfwGetWindowSize(GLFWwindow* window, int* width, int* height);
#define GLFW_DONT_CARE -1
GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth,
int minheight, int maxwidth,
int maxheight);
GLFWAPI void glfwSetWindowSize(GLFWwindow* window, int width, int height);
struct TeamNumberRefHolder {
explicit TeamNumberRefHolder(glass::Storage& storage)
: teamNumber{storage.GetInt("TeamNumber", 0)} {}
int& teamNumber;
};
static std::unique_ptr<TeamNumberRefHolder> teamNumberRef;
static std::unordered_map<std::string, std::pair<unsigned int, std::string>>
foundDevices;
static wpi::Logger logger;
static sysid::DeploySession deploySession{logger};
static std::unique_ptr<wpi::MulticastServiceResolver> multicastResolver;
static glass::MainMenuBar gMainMenu;
static void FindDevices() {
WPI_EventHandle resolveEvent = multicastResolver->GetEventHandle();
bool timedOut = 0;
if (wpi::WaitForObject(resolveEvent, 0, &timedOut)) {
auto allData = multicastResolver->GetData();
for (auto&& data : allData) {
// search for MAC
auto macKey =
std::find_if(data.txt.begin(), data.txt.end(),
[](const auto& a) { return a.first == "MAC"; });
if (macKey != data.txt.end()) {
auto& mac = macKey->second;
foundDevices[mac] = std::make_pair(data.ipv4Address, data.hostName);
}
}
}
}
static int minWidth = 400;
static void DisplayGui() {
int& teamNumber = teamNumberRef->teamNumber;
FindDevices();
ImGui::GetStyle().WindowRounding = 0;
// fill entire OS window with this window
ImGui::SetNextWindowPos(ImVec2(0, 0));
int width, height;
glfwGetWindowSize(gui::GetSystemWindow(), &width, &height);
ImGui::SetNextWindowSize(ImVec2(width, height));
ImGui::Begin("Entries", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoCollapse);
ImGui::BeginMenuBar();
gMainMenu.WorkspaceMenu();
gui::EmitViewMenu();
bool about = false;
if (ImGui::BeginMenu("Info")) {
if (ImGui::MenuItem("About")) {
about = true;
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
if (about) {
ImGui::OpenPopup("About");
}
if (ImGui::BeginPopupModal("About")) {
ImGui::Text("roboRIO Team Number Setter");
ImGui::Separator();
ImGui::Text("v%s", GetWPILibVersion());
ImGui::Separator();
ImGui::Text("Has mDNS Implementation: %d",
static_cast<int>(multicastResolver->HasImplementation()));
ImGui::Separator();
ImGui::Text("Save location: %s", glass::GetStorageDir().c_str());
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (multicastResolver->HasImplementation()) {
ImGui::InputInt("Team Number", &teamNumber);
if (teamNumber < 0) {
teamNumber = 0;
}
int nameWidth = ImGui::CalcTextSize("roboRIO2-0000-FRC.local. ").x;
int macWidth = ImGui::CalcTextSize("88:88:88:88:88:88").x;
int ipAddressWidth = ImGui::CalcTextSize("255.255.255.255").x;
int setWidth = ImGui::CalcTextSize(" Set Team To 99999 ").x;
int blinkWidth = ImGui::CalcTextSize(" Blink ").x;
int rebootWidth = ImGui::CalcTextSize(" Reboot ").x;
minWidth = nameWidth + macWidth + ipAddressWidth + setWidth + blinkWidth +
rebootWidth + 100;
std::string setString = fmt::format("Set team to {}", teamNumber);
if (ImGui::BeginTable("Table", 6)) {
ImGui::TableSetupColumn(
"Name",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
nameWidth);
ImGui::TableSetupColumn(
"MAC Address",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
macWidth);
ImGui::TableSetupColumn(
"IP Address",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
ipAddressWidth);
ImGui::TableSetupColumn(
"Set",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
setWidth);
ImGui::TableSetupColumn(
"Blink",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
blinkWidth);
ImGui::TableSetupColumn(
"Reboot",
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthFixed,
rebootWidth);
ImGui::TableHeadersRow();
for (auto&& i : foundDevices) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s", i.second.second.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", i.first.c_str());
ImGui::TableNextColumn();
struct in_addr in;
in.s_addr = i.second.first;
ImGui::Text("%s", inet_ntoa(in));
ImGui::TableNextColumn();
std::future<int>* future = deploySession.GetFuture(i.first);
ImGui::PushID(i.first.c_str());
if (future) {
ImGui::Button("Deploying");
ImGui::TableNextColumn();
ImGui::TableNextColumn();
const auto fs = future->wait_for(std::chrono::seconds(0));
if (fs == std::future_status::ready) {
deploySession.DestroyFuture(i.first);
}
} else {
if (ImGui::Button(setString.c_str())) {
deploySession.ChangeTeamNumber(i.first, teamNumber, i.second.first);
}
ImGui::TableNextColumn();
if (ImGui::Button("Blink")) {
deploySession.Blink(i.first, i.second.first);
}
ImGui::TableNextColumn();
if (ImGui::Button("Reboot")) {
deploySession.Reboot(i.first, i.second.first);
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::Columns(6, "Devices");
// TODO make columns better
} else {
// Missing MDNS Implementation
ImGui::Text("mDNS Implementation is missing.");
#ifdef _WIN32
ImGui::Text("Windows 10 1809 or newer is required for this tool");
#else
ImGui::Text("avahi-client 3 and avahi-core 3 are required for this tool");
ImGui::Text(
"Install libavahi-client3 and libavahi-core3 from your package "
"manager");
#endif
}
ImGui::Columns();
ImGui::End();
glfwSetWindowSizeLimits(gui::GetSystemWindow(), minWidth, 200, GLFW_DONT_CARE,
GLFW_DONT_CARE);
if (width < minWidth) {
width = minWidth;
glfwSetWindowSize(gui::GetSystemWindow(), width, height);
}
}
void Application(std::string_view saveDir) {
gui::CreateContext();
glass::CreateContext();
glass::SetStorageName("roborioteamnumbersetter");
glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir()
: saveDir);
ssh_init();
teamNumberRef =
std::make_unique<TeamNumberRefHolder>(glass::GetStorageRoot());
multicastResolver =
std::make_unique<wpi::MulticastServiceResolver>("_ni._tcp");
multicastResolver->Start();
gui::AddLateExecute(DisplayGui);
gui::Initialize("roboRIO Team Number Setter", 600, 400);
gui::Main();
multicastResolver->Stop();
multicastResolver = nullptr;
glass::DestroyContext();
gui::DestroyContext();
}

View File

@@ -0,0 +1,186 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "DeploySession.h"
#include <memory>
#include <mutex>
#include <string_view>
#include <unordered_map>
#include <fmt/core.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/uv/Error.h>
#include <wpi/uv/GetAddrInfo.h>
#include <wpi/uv/Work.h>
#include <wpi/uv/util.h>
#include "SshSession.h"
using namespace sysid;
#ifdef ERROR
#undef ERROR
#endif
// Macros to make logging easier.
#define INFO(fmt, ...) WPI_INFO(m_logger, fmt, __VA_ARGS__)
#define DEBUG(fmt, ...) WPI_DEBUG(m_logger, fmt, __VA_ARGS__)
#define ERROR(fmt, ...) WPI_DEBUG(m_logger, fmt, __VA_ARGS__)
#define SUCCESS(fmt, ...) WPI_LOG(m_logger, kLogSuccess, fmt, __VA_ARGS__)
// roboRIO SSH constants.
static constexpr int kPort = 22;
static constexpr std::string_view kUsername = "admin";
static constexpr std::string_view kPassword = "";
std::unordered_map<std::string, std::future<int>> s_outstanding;
DeploySession::DeploySession(wpi::Logger& logger) : m_logger{logger} {}
template <typename T>
struct SafeDeleter {
explicit SafeDeleter(T d) : deleter(d) {}
~SafeDeleter() noexcept { deleter(); }
T deleter;
};
std::future<int>* DeploySession::GetFuture(const std::string& macAddress) {
auto itr = s_outstanding.find(macAddress);
if (itr == s_outstanding.end()) {
return nullptr;
}
return &itr->second;
}
void DeploySession::DestroyFuture(const std::string& macAddress) {
s_outstanding.erase(macAddress);
}
bool DeploySession::ChangeTeamNumber(const std::string& macAddress,
int teamNumber, unsigned int ipAddress) {
auto itr = s_outstanding.find(macAddress);
if (itr != s_outstanding.end()) {
return false;
}
std::future<int> future = std::async(
std::launch::async, [this, ipAddress, teamNumber, mac = macAddress]() {
// Convert to IP address.
wpi::SmallString<16> ip;
in_addr addr;
addr.s_addr = ipAddress;
wpi::uv::AddrToName(addr, &ip);
DEBUG("Trying to establish SSH connection to {}.", ip);
try {
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
session.Open();
DEBUG("SSH connection to {} was successful.", ip);
SUCCESS("{}", "roboRIO Connected!");
try {
session.Execute(fmt::format(
"/usr/local/natinst/bin/nirtcfg "
"--file=/etc/natinst/share/ni-rt.ini --set "
"section=systemsettings,token=host_name,value=roborio-{"
"}-FRC ; sync",
teamNumber));
} catch (const SshSession::SshException& e) {
ERROR("An exception occurred: {}", e.what());
throw e;
}
} catch (const SshSession::SshException& e) {
DEBUG("SSH connection to {} failed with {}.", ip, e.what());
throw e;
}
return 0;
});
s_outstanding[macAddress] = std::move(future);
return true;
}
bool DeploySession::Reboot(const std::string& macAddress,
unsigned int ipAddress) {
auto itr = s_outstanding.find(macAddress);
if (itr != s_outstanding.end()) {
return false;
}
std::future<int> future =
std::async(std::launch::async, [this, ipAddress, mac = macAddress]() {
// Convert to IP address.
wpi::SmallString<16> ip;
in_addr addr;
addr.s_addr = ipAddress;
wpi::uv::AddrToName(addr, &ip);
DEBUG("Trying to establish SSH connection to {}.", ip);
try {
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
session.Open();
DEBUG("SSH connection to {} was successful.", ip);
SUCCESS("{}", "roboRIO Connected!");
try {
session.Execute(fmt::format("sync ; shutdown -r now"));
} catch (const SshSession::SshException& e) {
ERROR("An exception occurred: {}", e.what());
throw e;
}
} catch (const SshSession::SshException& e) {
DEBUG("SSH connection to {} failed with {}.", ip, e.what());
throw e;
}
return 0;
});
s_outstanding[macAddress] = std::move(future);
return true;
}
bool DeploySession::Blink(const std::string& macAddress,
unsigned int ipAddress) {
auto itr = s_outstanding.find(macAddress);
if (itr != s_outstanding.end()) {
return false;
}
std::future<int> future =
std::async(std::launch::async, [this, ipAddress, mac = macAddress]() {
// Convert to IP address.
wpi::SmallString<16> ip;
in_addr addr;
addr.s_addr = ipAddress;
wpi::uv::AddrToName(addr, &ip);
DEBUG("Trying to establish SSH connection to {}.", ip);
try {
SshSession session{ip.str(), kPort, kUsername, kPassword, m_logger};
session.Open();
DEBUG("SSH connection to {} was successful.", ip);
SUCCESS("{}", "roboRIO Connected!");
try {
session.Execute(fmt::format(
"for i in 1 2 3 4 5 ; do ` echo 255 > "
"/sys/class/leds/nilrt:wifi:primary/brightness; sleep 0.5; "
"echo 0 > /sys/class/leds/nilrt:wifi:primary/brightness; sleep "
"0.5 ` ; done"));
} catch (const SshSession::SshException& e) {
ERROR("An exception occurred: {}", e.what());
throw e;
}
} catch (const SshSession::SshException& e) {
DEBUG("SSH connection to {} failed with {}.", ip, e.what());
throw e;
}
return 0;
});
s_outstanding[macAddress] = std::move(future);
return true;
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <atomic>
#include <future>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <wpi/Logger.h>
namespace sysid {
// Define an integer for a successful message in the log (shown in green on the
// GUI).
static constexpr unsigned int kLogSuccess = 31;
/**
* Represents a single deploy session.
*
* An instance of this class must be kept alive in memory until GetStatus()
* returns kDiscoveryFailure or kDone. Otherwise, the deploy will fail!
*/
class DeploySession {
public:
/** Represents the status of the deploy session. */
enum class Status { kInProgress, kDiscoveryFailure, kDone };
/**
* Constructs an instance of the deploy session.
*
* @param team The team number (or an IP address/hostname).
* @param drive Whether the drive program should be deployed to the roboRIO.
* If this is set to false, the mechanism project will be
* deployed.
* @param config The generation configuration file to be sent to the roboRIO.
* @param logger A reference to a logger where log messages should be sent.
*/
explicit DeploySession(wpi::Logger& logger);
/**
* Executes the deploy. This can be called from any thread.
*/
bool ChangeTeamNumber(const std::string& macAddress, int team,
unsigned int ipAddress);
bool Blink(const std::string& macAddress, unsigned int ipAddress);
bool Reboot(const std::string& macAddress, unsigned int ipAddress);
std::future<int>* GetFuture(const std::string& macAddress);
void DestroyFuture(const std::string& macAddress);
/**
* Returns the state of the deploy session.
*/
Status GetStatus() const;
private:
// Logger reference where log messages will be sent.
wpi::Logger& m_logger;
// The number of hostnames that have completed their resolution/connection
// attempts.
std::atomic_int m_visited = 0;
};
} // namespace sysid

View File

@@ -0,0 +1,143 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "SshSession.h"
#include <fcntl.h>
#include <libssh/libssh.h>
#include <libssh/sftp.h>
#include <stdint.h>
#include <sys/stat.h>
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <fmt/format.h>
#include <wpi/Logger.h>
using namespace sysid;
#define INFO(fmt, ...) WPI_INFO(m_logger, fmt, __VA_ARGS__)
SshSession::SshSession(std::string_view host, int port, std::string_view user,
std::string_view pass, wpi::Logger& logger)
: m_host{host},
m_port{port},
m_username{user},
m_password{pass},
m_logger{logger} {
// Create a new SSH session.
m_session = ssh_new();
if (!m_session) {
throw SshException("The SSH session could not be allocated.");
}
// Set the host, user, and port.
ssh_options_set(m_session, SSH_OPTIONS_HOST, m_host.c_str());
ssh_options_set(m_session, SSH_OPTIONS_USER, m_username.c_str());
ssh_options_set(m_session, SSH_OPTIONS_PORT, &m_port);
// Set timeout to 3 seconds.
int64_t timeout = 3L;
ssh_options_set(m_session, SSH_OPTIONS_TIMEOUT, &timeout);
// Set other miscellaneous options.
ssh_options_set(m_session, SSH_OPTIONS_STRICTHOSTKEYCHECK, "no");
}
SshSession::~SshSession() {
ssh_disconnect(m_session);
ssh_free(m_session);
}
void SshSession::Open() {
// Connect to the server.
int rc = ssh_connect(m_session);
if (rc != SSH_OK) {
throw SshException(ssh_get_error(m_session));
}
// Authenticate with password.
rc = ssh_userauth_password(m_session, nullptr, m_password.c_str());
if (rc != SSH_AUTH_SUCCESS) {
throw SshException(ssh_get_error(m_session));
}
}
void SshSession::Execute(std::string_view cmd) {
// Allocate a new channel.
ssh_channel channel = ssh_channel_new(m_session);
if (!channel) {
throw SshException(ssh_get_error(m_session));
}
// Open the channel.
int rc = ssh_channel_open_session(channel);
if (rc != SSH_OK) {
throw SshException(ssh_get_error(m_session));
}
// Execute the command.
std::string command{cmd};
rc = ssh_channel_request_exec(channel, command.c_str());
if (rc != SSH_OK) {
ssh_channel_close(channel);
ssh_channel_free(channel);
throw SshException(ssh_get_error(m_session));
}
INFO("{}", cmd);
// Log output.
char buf[512];
int read = ssh_channel_read(channel, buf, sizeof(buf), 0);
if (read != 0) {
INFO("{}", cmd);
}
// Close and free channel.
ssh_channel_close(channel);
ssh_channel_free(channel);
}
void SshSession::Put(std::string_view path, std::string_view contents) {
// Allocate the SFTP session.
sftp_session sftp = sftp_new(m_session);
if (!sftp) {
throw SshException(ssh_get_error(m_session));
}
// Initialize.
int rc = sftp_init(sftp);
if (rc != SSH_OK) {
sftp_free(sftp);
throw SshException(ssh_get_error(m_session));
}
// Copy.
sftp_file file =
sftp_open(sftp, path.data(), O_WRONLY | O_CREAT | O_TRUNC, S_IFMT);
if (!file) {
sftp_free(sftp);
throw SshException(ssh_get_error(m_session));
}
// Send 150K at a time.
static constexpr size_t kChunkSize = 150000;
for (size_t i = 0; i < contents.size(); i += kChunkSize) {
size_t len = (std::min)(kChunkSize, contents.size() - i);
size_t written = sftp_write(file, contents.data() + i, len);
if (written != len) {
sftp_close(file);
sftp_free(sftp);
throw SshException(ssh_get_error(m_session));
}
}
INFO("[SFTP] Deployed {}!", path);
// Close file, free memory.
sftp_close(file);
sftp_free(sftp);
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <libssh/libssh.h>
#include <stdexcept>
#include <string>
#include <string_view>
#include <wpi/Logger.h>
namespace sysid {
/**
* This class is a C++ implementation of the SshSessionController in
* wpilibsuite/deploy-utils. It handles connecting to an SSH server, running
* commands, and transferring files.
*/
class SshSession {
public:
/**
* This is the exception that will be thrown by any of the methods in this
* class if something goes wrong.
*/
class SshException : public std::runtime_error {
public:
explicit SshException(const char* msg) : runtime_error(msg) {}
};
/**
* Constructs a new session controller.
*
* @param host The hostname of the server to connect to.
* @param port The port that the sshd server is operating on.
* @param user The username to login as.
* @param pass The password for the given username.
* @param logger A reference to a logger to log messages to.
*/
SshSession(std::string_view host, int port, std::string_view user,
std::string_view pass, wpi::Logger& logger);
/**
* Destroys the controller object. This also disconnects the session from the
* server.
*/
~SshSession();
/**
* Opens the SSH connection to the given host.
*/
void Open();
/**
* Executes a command and logs the output (if there is any).
*
* @param cmd The command to execute on the server.
*/
void Execute(std::string_view cmd);
/**
* Puts a file on the server using SFTP.
*
* @param path The path to the file to put (on the server).
* @param contents The contents of the file.
*/
void Put(std::string_view path, std::string_view contents);
private:
ssh_session m_session;
std::string m_host;
int m_port;
std::string m_username;
std::string m_password;
wpi::Logger& m_logger;
};
} // namespace sysid

View File

@@ -0,0 +1,25 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <string_view>
void Application(std::string_view saveDir);
#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
int argc = __argc;
char** argv = __argv;
#else
int main(int argc, char** argv) {
#endif
std::string_view saveDir;
if (argc == 2) {
saveDir = argv[1];
}
Application(saveDir);
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@@ -0,0 +1 @@
IDI_ICON1 ICON "roborioteamnumbersetter.ico"

View File

@@ -32,6 +32,7 @@ include 'crossConnIntegrationTests'
include 'fieldImages'
include 'glass'
include 'outlineviewer'
include 'roborioteamnumbersetter'
include 'simulation:gz_msgs'
include 'simulation:frc_gazebo_plugins'
include 'simulation:halsim_gazebo'