From aced2e7da63acb171cb91017825497300674a8a6 Mon Sep 17 00:00:00 2001 From: Thad House Date: Tue, 30 Nov 2021 11:17:30 -0800 Subject: [PATCH] Add roboRIO Team Number Setter tool (#3744) --- roborioteamnumbersetter/.styleguide | 28 ++ roborioteamnumbersetter/Info.plist | 32 +++ roborioteamnumbersetter/build.gradle | 134 +++++++++ roborioteamnumbersetter/publish.gradle | 107 +++++++ .../src/main/generate/WPILibVersion.cpp.in | 7 + .../src/main/native/cpp/App.cpp | 265 ++++++++++++++++++ .../src/main/native/cpp/DeploySession.cpp | 186 ++++++++++++ .../src/main/native/cpp/DeploySession.h | 70 +++++ .../src/main/native/cpp/SshSession.cpp | 143 ++++++++++ .../src/main/native/cpp/SshSession.h | 81 ++++++ .../src/main/native/cpp/main.cpp | 25 ++ .../src/main/native/mac/rtns.icns | Bin 0 -> 30327 bytes .../src/main/native/resources/rtns-128.png | Bin 0 -> 3161 bytes .../src/main/native/resources/rtns-16.png | Bin 0 -> 153 bytes .../src/main/native/resources/rtns-256.png | Bin 0 -> 6219 bytes .../src/main/native/resources/rtns-32.png | Bin 0 -> 951 bytes .../src/main/native/resources/rtns-48.png | Bin 0 -> 974 bytes .../src/main/native/resources/rtns-512.png | Bin 0 -> 9552 bytes .../src/main/native/resources/rtns-64.png | Bin 0 -> 1438 bytes .../native/win/roborioteamnumbersetter.ico | Bin 0 -> 105894 bytes .../native/win/roborioteamnumbersetter.rc | 1 + settings.gradle | 1 + 22 files changed, 1080 insertions(+) create mode 100644 roborioteamnumbersetter/.styleguide create mode 100644 roborioteamnumbersetter/Info.plist create mode 100644 roborioteamnumbersetter/build.gradle create mode 100644 roborioteamnumbersetter/publish.gradle create mode 100644 roborioteamnumbersetter/src/main/generate/WPILibVersion.cpp.in create mode 100644 roborioteamnumbersetter/src/main/native/cpp/App.cpp create mode 100644 roborioteamnumbersetter/src/main/native/cpp/DeploySession.cpp create mode 100644 roborioteamnumbersetter/src/main/native/cpp/DeploySession.h create mode 100644 roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp create mode 100644 roborioteamnumbersetter/src/main/native/cpp/SshSession.h create mode 100644 roborioteamnumbersetter/src/main/native/cpp/main.cpp create mode 100644 roborioteamnumbersetter/src/main/native/mac/rtns.icns create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-128.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-16.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-256.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-32.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-48.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-512.png create mode 100644 roborioteamnumbersetter/src/main/native/resources/rtns-64.png create mode 100644 roborioteamnumbersetter/src/main/native/win/roborioteamnumbersetter.ico create mode 100644 roborioteamnumbersetter/src/main/native/win/roborioteamnumbersetter.rc diff --git a/roborioteamnumbersetter/.styleguide b/roborioteamnumbersetter/.styleguide new file mode 100644 index 0000000000..1d448980c2 --- /dev/null +++ b/roborioteamnumbersetter/.styleguide @@ -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 +} diff --git a/roborioteamnumbersetter/Info.plist b/roborioteamnumbersetter/Info.plist new file mode 100644 index 0000000000..d5fd7c2358 --- /dev/null +++ b/roborioteamnumbersetter/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleName + roboRIOTeamNumberSetter + CFBundleExecutable + roborioteamnumbersetter + CFBundleDisplayName + roboRIOTeamNumberSetter + CFBundleIdentifier + edu.wpi.first.tools.roboRIOTeamNumberSetter + CFBundleIconFile + ov.icns + CFBundlePackageType + APPL + CFBundleSupportedPlatforms + + MacOSX + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleShortVersionString + 2021 + CFBundleVersion + 2021 + LSMinimumSystemVersion + 10.11 + NSHighResolutionCapable + + + diff --git a/roborioteamnumbersetter/build.gradle b/roborioteamnumbersetter/build.gradle new file mode 100644 index 0000000000..2b1a22982a --- /dev/null +++ b/roborioteamnumbersetter/build.gradle @@ -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' +} diff --git a/roborioteamnumbersetter/publish.gradle b/roborioteamnumbersetter/publish.gradle new file mode 100644 index 0000000000..103be9f5e6 --- /dev/null +++ b/roborioteamnumbersetter/publish.gradle @@ -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() + } + } + } +} diff --git a/roborioteamnumbersetter/src/main/generate/WPILibVersion.cpp.in b/roborioteamnumbersetter/src/main/generate/WPILibVersion.cpp.in new file mode 100644 index 0000000000..b0a4490520 --- /dev/null +++ b/roborioteamnumbersetter/src/main/generate/WPILibVersion.cpp.in @@ -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}"; +} diff --git a/roborioteamnumbersetter/src/main/native/cpp/App.cpp b/roborioteamnumbersetter/src/main/native/cpp/App.cpp new file mode 100644 index 0000000000..97081460ba --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/App.cpp @@ -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 +#include + +#ifndef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 teamNumberRef; +static std::unordered_map> + foundDevices; +static wpi::Logger logger; +static sysid::DeploySession deploySession{logger}; +static std::unique_ptr 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(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* 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(glass::GetStorageRoot()); + + multicastResolver = + std::make_unique("_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(); +} diff --git a/roborioteamnumbersetter/src/main/native/cpp/DeploySession.cpp b/roborioteamnumbersetter/src/main/native/cpp/DeploySession.cpp new file mode 100644 index 0000000000..81c69c4b4d --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/DeploySession.cpp @@ -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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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> s_outstanding; + +DeploySession::DeploySession(wpi::Logger& logger) : m_logger{logger} {} + +template +struct SafeDeleter { + explicit SafeDeleter(T d) : deleter(d) {} + ~SafeDeleter() noexcept { deleter(); } + T deleter; +}; + +std::future* 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 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 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 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; +} diff --git a/roborioteamnumbersetter/src/main/native/cpp/DeploySession.h b/roborioteamnumbersetter/src/main/native/cpp/DeploySession.h new file mode 100644 index 0000000000..7cc5bc7853 --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/DeploySession.h @@ -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 +#include +#include +#include +#include +#include + +#include + +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* 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 diff --git a/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp b/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp new file mode 100644 index 0000000000..9c8920182b --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/SshSession.cpp @@ -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 +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +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); +} diff --git a/roborioteamnumbersetter/src/main/native/cpp/SshSession.h b/roborioteamnumbersetter/src/main/native/cpp/SshSession.h new file mode 100644 index 0000000000..2e97120f2a --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/SshSession.h @@ -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 + +#include +#include +#include + +#include + +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 diff --git a/roborioteamnumbersetter/src/main/native/cpp/main.cpp b/roborioteamnumbersetter/src/main/native/cpp/main.cpp new file mode 100644 index 0000000000..5f1261b00f --- /dev/null +++ b/roborioteamnumbersetter/src/main/native/cpp/main.cpp @@ -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 + +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; +} diff --git a/roborioteamnumbersetter/src/main/native/mac/rtns.icns b/roborioteamnumbersetter/src/main/native/mac/rtns.icns new file mode 100644 index 0000000000000000000000000000000000000000..4a1d0131b3a7e8996e5f2b4156bf81b696fc65cc GIT binary patch literal 30327 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 bj5Q8lFS&jHatOr*WS{;SklFv|6wZGD&`Tv1 literal 0 HcmV?d00001 diff --git a/roborioteamnumbersetter/src/main/native/resources/rtns-128.png b/roborioteamnumbersetter/src/main/native/resources/rtns-128.png new file mode 100644 index 0000000000000000000000000000000000000000..5c6ebdd61dbe2d362823c3af0199a27a22335290 GIT binary patch literal 3161 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 literal 0 HcmV?d00001 diff --git a/roborioteamnumbersetter/src/main/native/resources/rtns-16.png b/roborioteamnumbersetter/src/main/native/resources/rtns-16.png new file mode 100644 index 0000000000000000000000000000000000000000..b95cd6a645707de59f2ae744643ba8ecf6a85298 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPE^4e$wZ1=7hoeyqItCv4LX|8?Kp zR()?g_G>|V7b8%Du_VYZn8D%MjWi&~$kW9!gyVX0!U2{Z1t*4%nLH8_GejMDu3xwy wz$zlh9=3vGPNTmeGc$L`L^ie)JS7Ya6YII_)X&ap0qSP(boFyt=akR{0D)vK>;M1& literal 0 HcmV?d00001 diff --git a/roborioteamnumbersetter/src/main/native/resources/rtns-256.png b/roborioteamnumbersetter/src/main/native/resources/rtns-256.png new file mode 100644 index 0000000000000000000000000000000000000000..7d07f391435c1351e2ed7c3ea8cb5b4c6d7669d7 GIT binary patch literal 6219 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 literal 0 HcmV?d00001 diff --git a/roborioteamnumbersetter/src/main/native/resources/rtns-48.png b/roborioteamnumbersetter/src/main/native/resources/rtns-48.png new file mode 100644 index 0000000000000000000000000000000000000000..bf31571b29adbcc11bac2e37a60330a234c69fec GIT binary patch literal 974 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 R5v*yzFJHe@qw(6Vv>Ywhb zf4Hss?!M-y%gXQWtG~Oi{N}p)hs%ne?yJ7LuKMn_>W9aQp8-q$do2IyyY!DcP}k}o zuFHYsH@B5PU6y|Z>V+Yofp9kU86GRYdaM9i2XZah2y6<}){hfL7H!!l+ z-}&?A=l`qk{zY#70gT6WxBtBT_5a$te~YgDx$)uOk!OFyH~om({KI4Qcc&HK>yG|v zIQDDrqrWe{{|{LI{lJsIOAdZ30Va!{k|4ieAdMYxNeMCuut)5^eWpo5yzX>#?$*ro& zQ4dchVV?4fHAiE1?)!IZ`n0K6GV2?}=L4x%#kAabwm*;7Nhrx$bl(kh)o@t7m6zUh}80Z<iziMt)w6S)I=!9KYjPe#-x8l) zjB7eQJv-MJ%qn4ym>kR$ba_%_qRC6P443cUXG?EBl%d4yasEOB(`26jleC!!Ui6eK z;yJOciP__m^M#vVJWpBftPGeS&v7Y)Ve#kUVh`RUJOK|xfmRtU+NhzhRYIg!VAem* z&Ob}egfpFSS+mAxg$ZYCR%Yau%4OEtR=!IZpNjsOx*%h79DNjiP)3DXU6RCc7NLHo8Hs(qWN;`9~@a^EIRPX6$wq?2H z`#0UHzj|O*#i_2R+7oXxzc{eH{^=*t;0Dn$)2wQtOaGXgLRW`<6=DcbUGdhl;CKF4 zDWL;dQ@$A+gqfHp?EzQ>z^N#BWVJ zjoWRT=;$jg} TYW9W$l#M)H{an^LB{Ts5W{>2J literal 0 HcmV?d00001 diff --git a/roborioteamnumbersetter/src/main/native/win/roborioteamnumbersetter.ico b/roborioteamnumbersetter/src/main/native/win/roborioteamnumbersetter.ico new file mode 100644 index 0000000000000000000000000000000000000000..c488e68ea0eb4611d8bc866c1feae5b51b89f75a GIT binary patch literal 105894 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