mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa34aacf6e | ||
|
|
63512bbbb8 | ||
|
|
9227b2166e | ||
|
|
fbf92e9190 | ||
|
|
2108a61362 | ||
|
|
0a66479693 | ||
|
|
b510c17ef6 | ||
|
|
e7a7eb2e93 | ||
|
|
a465f2d8f0 | ||
|
|
a3364422fa | ||
|
|
df3242a40a | ||
|
|
00abb8c1e0 | ||
|
|
c886273fd7 | ||
|
|
53b5fd2ace | ||
|
|
56b758320f | ||
|
|
08f298e4cd | ||
|
|
6d0c5b19db | ||
|
|
0d22cf5ff7 | ||
|
|
32ec5b3f75 | ||
|
|
e5c4c6b1a7 | ||
|
|
099d048d9e | ||
|
|
4af84a1c12 | ||
|
|
ce3686b80d | ||
|
|
4b0eecaee0 | ||
|
|
edf4ded412 | ||
|
|
4c46b6aff9 | ||
|
|
490ca4a68a | ||
|
|
cbb5b0b802 | ||
|
|
bb7053d9ee | ||
|
|
9efed9a533 | ||
|
|
dbbfe1aed2 | ||
|
|
de65a135c3 | ||
|
|
3e9788cdff | ||
|
|
ecb072724d | ||
|
|
0d462a4561 | ||
|
|
ba37986561 | ||
|
|
25ab9cda92 | ||
|
|
2f6251d4a6 | ||
|
|
e9a7bed988 | ||
|
|
9cc14bbb43 | ||
|
|
8068369542 | ||
|
|
805c837a42 | ||
|
|
fd18577ba0 | ||
|
|
74dea9f05e | ||
|
|
9eef79d638 | ||
|
|
843574a810 | ||
|
|
226ef35212 | ||
|
|
b30664d630 | ||
|
|
804e5ce236 | ||
|
|
49af88f2bb | ||
|
|
d56314f866 | ||
|
|
43975ac7cc | ||
|
|
5483464158 | ||
|
|
785e7dd85c | ||
|
|
e57ded8c39 | ||
|
|
01f0394419 | ||
|
|
59be120982 | ||
|
|
37f065032f | ||
|
|
22a170bee7 |
@@ -156,7 +156,7 @@ SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
Standard: c++17
|
||||
Standard: c++20
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009-2022 FIRST and other WPILib contributors
|
||||
Copyright (c) 2009-2023 FIRST and other WPILib contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -16,7 +16,7 @@ We provide two base types of artifacts.
|
||||
|
||||
The first types are Java artifacts. These are usually published as `jar` files. Usually, the actual jar file is published with no classifier. The sources are published with the `-sources` classifier, and the javadocs are published with the `-javadoc` classifier.
|
||||
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respecively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifer combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
The second types are native artifacts. These are usually published as `zip` files (except for the `JNI` artifact types, which are `jar` files. See below for information on this). The `-sources` and `-headers` classifiers contain the sources and headers respectively for the library. Each artifact also contains a classifier for each platform we publish. This platform is in the format `{os}{arch}`. The platform artifact only contains the binaries for a specific platform. In addition, we provide a `-all` classifier. This classifier combines all of the platform artifacts into a single artifact. This is useful for tools that cannot determine what version to use during builds. However, we recommend using the platform specific classifier when possible. Note that the binary artifacts never contain the headers, you always need the `-headers` classifier to get those.
|
||||
|
||||
## Artifact Names
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class WPILIB_DLLEXPORT AprilTagFieldLayout {
|
||||
/**
|
||||
* Sets the origin for tag pose transformation.
|
||||
*
|
||||
* This tranforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* This transforms the Pose3ds returned by GetTagPose(int) to return the
|
||||
* correct pose relative to the provided origin.
|
||||
*
|
||||
* @param origin The new origin for tag transformations
|
||||
|
||||
@@ -143,7 +143,7 @@ public class CameraServerJNI {
|
||||
public static native void releaseSource(int source);
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
public static native void setCameraBrightness(int source, int brightness);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
namespace cs {
|
||||
|
||||
// The UnlimitedHandleResource class is a way to track handles. This version
|
||||
// allows an unlimted number of handles that are allocated sequentially. When
|
||||
// allows an unlimited number of handles that are allocated sequentially. When
|
||||
// possible, indices are reused to save memory usage and keep the array length
|
||||
// down.
|
||||
// However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -439,7 +439,7 @@ void ReleaseSource(CS_Source source, CS_Status* status) {
|
||||
}
|
||||
|
||||
//
|
||||
// Camera Source Common Property Fuctions
|
||||
// Camera Source Common Property Functions
|
||||
//
|
||||
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status) {
|
||||
|
||||
@@ -323,7 +323,7 @@ void CS_ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_source_prop_cfunc Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void CS_SetCameraBrightness(CS_Source source, int brightness,
|
||||
|
||||
@@ -262,7 +262,7 @@ void ReleaseSource(CS_Source source, CS_Status* status);
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Fuctions
|
||||
* @defgroup cscore_camera_property_func Camera Source Common Property Functions
|
||||
* @{
|
||||
*/
|
||||
void SetCameraBrightness(CS_Source source, int brightness, CS_Status* status);
|
||||
|
||||
@@ -79,7 +79,7 @@ using namespace cs;
|
||||
default:
|
||||
OBJCERROR(
|
||||
"Camera access explicitly blocked for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
self.isAuthorized = false;
|
||||
// TODO log
|
||||
break;
|
||||
@@ -524,7 +524,7 @@ static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) {
|
||||
if (!self.isAuthorized) {
|
||||
OBJCERROR(
|
||||
"Camera access not authorized for application. No cameras are "
|
||||
"accessable");
|
||||
"accessible");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ STDMETHODIMP SourceReaderCB::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
|
||||
return S_OK;
|
||||
if (SUCCEEDED(hrStatus)) {
|
||||
if (pSample) {
|
||||
// Prcoess sample
|
||||
// Process sample
|
||||
source->ProcessFrame(pSample, m_mode);
|
||||
// DO NOT release the frame
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class UsbCameraTest {
|
||||
static class ConnectVerbose {
|
||||
@Test
|
||||
void setConnectVerboseEnabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(1);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
@@ -38,7 +38,7 @@ class UsbCameraTest {
|
||||
|
||||
@Test
|
||||
void setConnectVerboseDisabledTest() {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistant Camera", getNonexistentCameraDev())) {
|
||||
try (UsbCamera camera = new UsbCamera("Nonexistent Camera", getNonexistentCameraDev())) {
|
||||
camera.setConnectVerbose(0);
|
||||
|
||||
CompletableFuture<String> result = new CompletableFuture<>();
|
||||
|
||||
@@ -466,7 +466,8 @@ static void ValueToCsv(wpi::raw_ostream& os, const Entry& entry,
|
||||
fmt::print(os, "{}", val);
|
||||
return;
|
||||
}
|
||||
} else if (entry.type == "int64") {
|
||||
} else if (entry.type == "int64" || entry.type == "int") {
|
||||
// support "int" for compatibility with old NT4 datalogs
|
||||
int64_t val;
|
||||
if (record.GetInteger(&val)) {
|
||||
fmt::print(os, "{}", val);
|
||||
|
||||
@@ -64,6 +64,7 @@ doxygen {
|
||||
cppIncludeRoots.add(it.absolutePath)
|
||||
}
|
||||
}
|
||||
cppIncludeRoots << '../ntcore/build/generated/main/native/include/'
|
||||
|
||||
if (project.hasProperty('docWarningsAsErrors')) {
|
||||
// C++20 shims
|
||||
@@ -238,7 +239,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
if (JavaVersion.current().isJava8Compatible() && project.hasProperty('docWarningsAsErrors')) {
|
||||
// Treat javadoc warnings as errors.
|
||||
//
|
||||
// The second argument '-quiet' is a hack. The one paramater
|
||||
// The second argument '-quiet' is a hack. The one parameter
|
||||
// addStringOption() doesn't work, so we add '-quiet', which is added
|
||||
// anyway by gradle. See https://github.com/gradle/gradle/issues/2354.
|
||||
//
|
||||
|
||||
@@ -151,7 +151,7 @@ html {
|
||||
--side-nav-arrow-opacity: 0;
|
||||
--side-nav-arrow-hover-opacity: 0.9;
|
||||
|
||||
/* height of an item in any tree / collapsable table */
|
||||
/* height of an item in any tree / collapsible table */
|
||||
--tree-item-height: 30px;
|
||||
|
||||
--darkmode-toggle-button-icon: "☀️";
|
||||
@@ -1615,7 +1615,7 @@ SOFTWARE.
|
||||
|
||||
html {
|
||||
/* side nav width. MUST be = `TREEVIEW_WIDTH`.
|
||||
* Make sure it is wide enought to contain the page title (logo + title + version)
|
||||
* Make sure it is wide enough to contain the page title (logo + title + version)
|
||||
*/
|
||||
--side-nav-fixed-width: 340px;
|
||||
--menu-display: none;
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FieldConfig {
|
||||
*
|
||||
* @param resourcePath The path to the resource file
|
||||
* @return The field configuration
|
||||
* @throws IOException Throws if the resoure could not be loaded
|
||||
* @throws IOException Throws if the resource could not be loaded
|
||||
*/
|
||||
public static FieldConfig loadFromResource(String resourcePath) throws IOException {
|
||||
try (InputStream stream = FieldConfig.class.getResourceAsStream(resourcePath);
|
||||
|
||||
@@ -400,6 +400,11 @@ void NetworkTablesModel::ValueSource::UpdateFromValue(
|
||||
}
|
||||
} else {
|
||||
valueChildren.clear();
|
||||
valueStr.clear();
|
||||
wpi::raw_string_ostream os{valueStr};
|
||||
os << '"';
|
||||
os.write_escaped(value.GetString());
|
||||
os << '"';
|
||||
}
|
||||
break;
|
||||
case NT_RAW:
|
||||
@@ -769,25 +774,32 @@ static bool StringToStringArray(std::string_view in,
|
||||
}
|
||||
in = wpi::trim(in);
|
||||
|
||||
wpi::SmallVector<std::string_view, 16> inSplit;
|
||||
wpi::SmallString<32> buf;
|
||||
|
||||
wpi::split(in, inSplit, ',', -1, false);
|
||||
for (auto val : inSplit) {
|
||||
val = wpi::trim(val);
|
||||
if (val.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (val.front() != '"' || val.back() != '"') {
|
||||
fmt::print(stderr,
|
||||
"GUI: NetworkTables: Could not understand value '{}'\n", val);
|
||||
while (!in.empty()) {
|
||||
if (in.front() != '"') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Expected '\"'");
|
||||
return false;
|
||||
}
|
||||
val.remove_prefix(1);
|
||||
val.remove_suffix(1);
|
||||
out->emplace_back(wpi::UnescapeCString(val, buf).first);
|
||||
in.remove_prefix(1);
|
||||
wpi::SmallString<128> buf;
|
||||
std::string_view val;
|
||||
std::tie(val, in) = wpi::UnescapeCString(in, buf);
|
||||
out->emplace_back(val);
|
||||
if (!in.empty()) {
|
||||
if (in.front() != '"') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Error escaping string");
|
||||
return false;
|
||||
}
|
||||
in.remove_prefix(1);
|
||||
in = wpi::ltrim(in);
|
||||
}
|
||||
if (!in.empty()) {
|
||||
if (in.front() != ',') {
|
||||
fmt::print(stderr, "GUI: NetworkTables: Expected ','");
|
||||
return false;
|
||||
}
|
||||
in.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -826,9 +838,8 @@ static void EmitEntryValueReadonly(const NetworkTablesModel::ValueSource& entry,
|
||||
break;
|
||||
}
|
||||
case NT_STRING: {
|
||||
// GetString() comes from a std::string, so it's null terminated
|
||||
ImGui::LabelText(typeStr ? typeStr : "string", "%s",
|
||||
val.GetString().data());
|
||||
entry.valueStr.c_str());
|
||||
break;
|
||||
}
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
@@ -938,13 +949,18 @@ static void EmitEntryValueEditable(NetworkTablesModel::Entry& entry,
|
||||
break;
|
||||
}
|
||||
case NT_STRING: {
|
||||
char* v = GetTextBuffer(val.GetString());
|
||||
char* v = GetTextBuffer(entry.valueStr);
|
||||
if (ImGui::InputText(typeStr ? typeStr : "string", v, kTextBufferSize,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
if (entry.publisher == 0) {
|
||||
entry.publisher = nt::Publish(entry.info.topic, NT_STRING, "string");
|
||||
if (v[0] == '"') {
|
||||
if (entry.publisher == 0) {
|
||||
entry.publisher =
|
||||
nt::Publish(entry.info.topic, NT_STRING, "string");
|
||||
}
|
||||
wpi::SmallString<128> buf;
|
||||
nt::SetString(entry.publisher,
|
||||
wpi::UnescapeCString(v + 1, buf).first);
|
||||
}
|
||||
nt::SetString(entry.publisher, v);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1045,58 +1061,97 @@ static void CreateTopicMenuItem(NetworkTablesModel* model,
|
||||
model->AddEntry(nt::GetTopic(model->GetInstance().GetHandle(), path));
|
||||
if (entry->publisher == 0) {
|
||||
entry->publisher = nt::Publish(entry->info.topic, type, typeStr);
|
||||
// publish a default value so it's editable
|
||||
switch (type) {
|
||||
case NT_BOOLEAN:
|
||||
nt::SetDefaultBoolean(entry->publisher, false);
|
||||
break;
|
||||
case NT_INTEGER:
|
||||
nt::SetDefaultInteger(entry->publisher, 0);
|
||||
break;
|
||||
case NT_FLOAT:
|
||||
nt::SetDefaultFloat(entry->publisher, 0.0);
|
||||
break;
|
||||
case NT_DOUBLE:
|
||||
nt::SetDefaultDouble(entry->publisher, 0.0);
|
||||
break;
|
||||
case NT_STRING:
|
||||
nt::SetDefaultString(entry->publisher, "");
|
||||
break;
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
nt::SetDefaultBooleanArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_INTEGER_ARRAY:
|
||||
nt::SetDefaultIntegerArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_FLOAT_ARRAY:
|
||||
nt::SetDefaultFloatArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_DOUBLE_ARRAY:
|
||||
nt::SetDefaultDoubleArray(entry->publisher, {});
|
||||
break;
|
||||
case NT_STRING_ARRAY:
|
||||
nt::SetDefaultStringArray(entry->publisher, {});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void glass::DisplayNetworkTablesAddMenu(NetworkTablesModel* model,
|
||||
std::string_view path,
|
||||
NetworkTablesFlags flags) {
|
||||
static char nameBuffer[kTextBufferSize];
|
||||
|
||||
if (ImGui::BeginMenu("Add new...")) {
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
nameBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
|
||||
kTextBufferSize);
|
||||
std::string fullNewPath;
|
||||
if (path == "/") {
|
||||
path = "";
|
||||
}
|
||||
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
|
||||
|
||||
ImGui::Text("Adding: %s", fullNewPath.c_str());
|
||||
ImGui::Separator();
|
||||
auto entry = model->GetEntry(fullNewPath);
|
||||
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
|
||||
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
|
||||
nameBuffer[0] != '\0') &&
|
||||
!exists;
|
||||
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
|
||||
enabled);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
static void EmitParentContextMenu(NetworkTablesModel* model,
|
||||
const std::string& path,
|
||||
NetworkTablesFlags flags) {
|
||||
static char nameBuffer[kTextBufferSize];
|
||||
if (ImGui::BeginPopupContextItem(path.c_str())) {
|
||||
ImGui::Text("%s", path.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::BeginMenu("Add new...")) {
|
||||
if (ImGui::IsWindowAppearing()) {
|
||||
nameBuffer[0] = '\0';
|
||||
}
|
||||
|
||||
ImGui::InputTextWithHint("New item name", "example", nameBuffer,
|
||||
kTextBufferSize);
|
||||
std::string fullNewPath;
|
||||
if (path == "/") {
|
||||
fullNewPath = path + nameBuffer;
|
||||
} else {
|
||||
fullNewPath = fmt::format("{}/{}", path, nameBuffer);
|
||||
}
|
||||
|
||||
ImGui::Text("Adding: %s", fullNewPath.c_str());
|
||||
ImGui::Separator();
|
||||
auto entry = model->GetEntry(fullNewPath);
|
||||
bool exists = entry && entry->info.type != NT_Type::NT_UNASSIGNED;
|
||||
bool enabled = (flags & NetworkTablesFlags_CreateNoncanonicalKeys ||
|
||||
nameBuffer[0] != '\0') &&
|
||||
!exists;
|
||||
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_STRING, "string", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER, "int", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT, "float", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE, "double", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN, "boolean", enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_STRING_ARRAY, "string[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_INTEGER_ARRAY, "int[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_FLOAT_ARRAY, "float[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_DOUBLE_ARRAY, "double[]",
|
||||
enabled);
|
||||
CreateTopicMenuItem(model, fullNewPath, NT_BOOLEAN_ARRAY, "boolean[]",
|
||||
enabled);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
DisplayNetworkTablesAddMenu(model, path, flags);
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -1280,7 +1335,6 @@ static void DisplayTable(NetworkTablesModel* model,
|
||||
}
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
// EmitParentContextMenu(model, "/", flags);
|
||||
if (flags & NetworkTablesFlags_TreeView) {
|
||||
switch (category) {
|
||||
case ShowPersistent:
|
||||
@@ -1511,6 +1565,7 @@ void NetworkTablesView::Display() {
|
||||
|
||||
void NetworkTablesView::Settings() {
|
||||
m_flags.DisplayMenu();
|
||||
DisplayNetworkTablesAddMenu(m_model, {}, m_flags.GetFlags());
|
||||
}
|
||||
|
||||
bool NetworkTablesView::HasSettings() {
|
||||
|
||||
@@ -194,6 +194,10 @@ void DisplayNetworkTables(
|
||||
NetworkTablesModel* model,
|
||||
NetworkTablesFlags flags = NetworkTablesFlags_Default);
|
||||
|
||||
void DisplayNetworkTablesAddMenu(
|
||||
NetworkTablesModel* model, std::string_view path = {},
|
||||
NetworkTablesFlags flags = NetworkTablesFlags_Default);
|
||||
|
||||
class NetworkTablesFlagsSettings {
|
||||
public:
|
||||
explicit NetworkTablesFlagsSettings(
|
||||
|
||||
@@ -32,7 +32,7 @@ public class SimDevice implements AutoCloseable {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. Returns null if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>null is returned if not in simulation.
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SimDeviceJNI extends JNIWrapper {
|
||||
* Creates a simulated device.
|
||||
*
|
||||
* <p>The device name must be unique. 0 is returned if the device name already exists. If multiple
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifer in
|
||||
* instances of the same device are desired, recommend appending the instance/unique identifier in
|
||||
* brackets to the base name, e.g. "device[1]".
|
||||
*
|
||||
* <p>0 is returned if not in simulation.
|
||||
|
||||
@@ -69,7 +69,7 @@ void setAnalogNumChannelsToActivate(int32_t channels);
|
||||
* number of active channels and the sample rate.
|
||||
*
|
||||
* When the number of channels changes, use the new value. Otherwise,
|
||||
* return the curent value.
|
||||
* return the current value.
|
||||
*
|
||||
* @return Value to write to the active channels field.
|
||||
*/
|
||||
|
||||
@@ -37,7 +37,7 @@ int32_t HAL_GetFPGAEncoder(HAL_FPGAEncoderHandle fpgaEncoderHandle,
|
||||
/**
|
||||
* Returns the period of the most recent pulse.
|
||||
* Returns the period of the most recent Encoder pulse in seconds.
|
||||
* This method compenstates for the decoding type.
|
||||
* This method compensates for the decoding type.
|
||||
*
|
||||
* @deprecated Use GetRate() in favor of this method. This returns unscaled
|
||||
* periods and GetRate() scales using value from SetDistancePerPulse().
|
||||
|
||||
@@ -253,12 +253,10 @@ const char* HAL_GetErrorMessage(int32_t code) {
|
||||
}
|
||||
}
|
||||
|
||||
static HAL_RuntimeType runtimeType = HAL_Runtime_RoboRIO;
|
||||
|
||||
HAL_RuntimeType HAL_GetRuntimeType(void) {
|
||||
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
|
||||
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
|
||||
return HAL_Runtime_RoboRIO2;
|
||||
}
|
||||
return HAL_Runtime_RoboRIO;
|
||||
return runtimeType;
|
||||
}
|
||||
|
||||
int32_t HAL_GetFPGAVersion(int32_t* status) {
|
||||
@@ -318,11 +316,8 @@ void InitializeRoboRioComments(void) {
|
||||
return;
|
||||
}
|
||||
start += searchString.size();
|
||||
size_t end = fileContents.find("\"", start);
|
||||
if (end == std::string_view::npos) {
|
||||
end = fileContents.size();
|
||||
}
|
||||
std::string_view escapedComments = wpi::slice(fileContents, start, end);
|
||||
std::string_view escapedComments =
|
||||
wpi::slice(fileContents, start, fileContents.size());
|
||||
wpi::SmallString<64> buf;
|
||||
auto [unescapedComments, rem] = wpi::UnescapeCString(escapedComments, buf);
|
||||
unescapedComments.copy(roboRioCommentsString,
|
||||
@@ -523,6 +518,13 @@ HAL_Bool HAL_Initialize(int32_t timeout, int32_t mode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
|
||||
if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
|
||||
runtimeType = HAL_Runtime_RoboRIO2;
|
||||
} else {
|
||||
runtimeType = HAL_Runtime_RoboRIO;
|
||||
}
|
||||
|
||||
InterruptManager::Initialize(global->getSystemInterface());
|
||||
|
||||
hal::InitializeDriverStation();
|
||||
|
||||
@@ -51,7 +51,7 @@ struct DeviceDescriptor
|
||||
// Bootloader version. Will not change for the life of the product, but additional
|
||||
// field upgrade features could be added in newer hardware.
|
||||
char bootloaderRev[MAX_STRING_LEN];
|
||||
// Manufacture Date. Could be a calender date or just the FRC season year.
|
||||
// Manufacture Date. Could be a calendar date or just the FRC season year.
|
||||
// Also helps troubleshooting "old ones" vs "new ones".
|
||||
char manufactureDate[MAX_STRING_LEN];
|
||||
// General status of the hardware. For example if the device is in bootloader
|
||||
|
||||
@@ -79,7 +79,7 @@ extern "C" {
|
||||
/**
|
||||
* Signals in message Compressor_Config.
|
||||
*
|
||||
* Configures compressor to use digitial/analog sensors
|
||||
* Configures compressor to use digital/analog sensors
|
||||
*
|
||||
* All signal values are as on the CAN bus.
|
||||
*/
|
||||
|
||||
@@ -40,7 +40,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes an object for peforming DMA transfers.
|
||||
* Initializes an object for performing DMA transfers.
|
||||
*
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created dma handle
|
||||
@@ -310,7 +310,7 @@ enum HAL_DMAReadStatus HAL_ReadDMADirect(void* dmaPointer,
|
||||
* timing out
|
||||
* @param[in] remainingOut the number of samples remaining in the queue
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the succes result of the sample read
|
||||
* @return the success result of the sample read
|
||||
*/
|
||||
enum HAL_DMAReadStatus HAL_ReadDMA(HAL_DMAHandle handle,
|
||||
HAL_DMASample* dmaSample,
|
||||
|
||||
@@ -28,7 +28,7 @@ extern "C" {
|
||||
* @param errorCode the error code
|
||||
* @param isLVCode true for a LV error code, false for a standard error code
|
||||
* @param details the details of the error
|
||||
* @param location the file location of the errror
|
||||
* @param location the file location of the error
|
||||
* @param callStack the callstack of the error
|
||||
* @param printMsg true to print the error message to stdout as well as to the
|
||||
* DS
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" {
|
||||
* Expected to be called internally, not by users.
|
||||
*
|
||||
* @param library the library path
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadOneExtension(const char* library);
|
||||
|
||||
@@ -39,7 +39,7 @@ int HAL_LoadOneExtension(const char* library);
|
||||
* Loads any extra halsim libraries provided in the HALSIM_EXTENSIONS
|
||||
* environment variable.
|
||||
*
|
||||
* @return the succes state of the initialization
|
||||
* @return the success state of the initialization
|
||||
*/
|
||||
int HAL_LoadExtensions(void);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ extern "C" {
|
||||
* Initializes a Power Distribution Panel.
|
||||
*
|
||||
* @param[in] moduleNumber the module number to initialize
|
||||
* @param[in] type the type of module to intialize
|
||||
* @param[in] type the type of module to initialize
|
||||
* @param[in] allocationLocation the location where the allocation is occurring
|
||||
* @param[out] status Error status variable. 0 on success.
|
||||
* @return the created PowerDistribution
|
||||
|
||||
@@ -47,7 +47,7 @@ extern "C" {
|
||||
*
|
||||
* The device name must be unique. 0 is returned if the device name already
|
||||
* exists. If multiple instances of the same device are desired, recommend
|
||||
* appending the instance/unique identifer in brackets to the base name,
|
||||
* appending the instance/unique identifier in brackets to the base name,
|
||||
* e.g. "device[1]".
|
||||
*
|
||||
* 0 is returned if not in simulation.
|
||||
@@ -663,7 +663,7 @@ class SimDevice {
|
||||
*
|
||||
* The device name must be unique. Returns null if the device name
|
||||
* already exists. If multiple instances of the same device are desired,
|
||||
* recommend appending the instance/unique identifer in brackets to the base
|
||||
* recommend appending the instance/unique identifier in brackets to the base
|
||||
* name, e.g. "device[1]".
|
||||
*
|
||||
* If not in simulation, results in an "empty" object that evaluates to false
|
||||
|
||||
@@ -63,8 +63,8 @@ int32_t ComputeDigitalMask(HAL_DigitalHandle handle, int32_t* status);
|
||||
|
||||
/**
|
||||
* Unsafe digital output set function
|
||||
* This function can be used to perform fast and determinstically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anyting other then
|
||||
* This function can be used to perform fast and deterministically set digital
|
||||
* outputs. This function holds the DIO lock, so calling anything other then
|
||||
* functions on the Proxy object passed as a parameter can deadlock your
|
||||
* program.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace hal {
|
||||
|
||||
/**
|
||||
* The UnlimitedHandleResource class is a way to track handles. This version
|
||||
* allows an unlimted number of handles that are allocated sequentially. When
|
||||
* allows an unlimited number of handles that are allocated sequentially. When
|
||||
* possible, indices are reused to save memory usage and keep the array length
|
||||
* down.
|
||||
* However, automatic array management has not been implemented, but might be in
|
||||
|
||||
@@ -201,7 +201,7 @@ int32_t HAL_GetREVPHSolenoids(HAL_REVPHHandle handle, int32_t* status) {
|
||||
|
||||
std::scoped_lock lock{pcm->lock};
|
||||
auto& data = SimREVPHData[pcm->module].solenoidOutput;
|
||||
uint8_t ret = 0;
|
||||
int32_t ret = 0;
|
||||
for (int i = 0; i < kNumREVPHChannels; i++) {
|
||||
ret |= (data[i] << i);
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
||||
@@ -17,12 +19,23 @@
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
void bench();
|
||||
void bench2();
|
||||
void stress();
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc == 2 && std::string_view{argv[1]} == "bench") {
|
||||
bench();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 2 && std::string_view{argv[1]} == "bench2") {
|
||||
bench2();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (argc == 2 && std::string_view{argv[1]} == "stress") {
|
||||
stress();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
auto myValue = nt::GetEntry(nt::GetDefaultInstance(), "MyValue");
|
||||
|
||||
nt::SetEntryValue(myValue, nt::Value::MakeString("Hello World"));
|
||||
@@ -106,3 +119,145 @@ void bench() {
|
||||
fmt::print("-- Flush --\n");
|
||||
PrintTimes(flushTimes);
|
||||
}
|
||||
|
||||
void bench2() {
|
||||
// set up instances
|
||||
auto client1 = nt::CreateInstance();
|
||||
auto client2 = nt::CreateInstance();
|
||||
auto server = nt::CreateInstance();
|
||||
|
||||
// connect client and server
|
||||
nt::StartServer(server, "bench2.json", "127.0.0.1", 10001, 10000);
|
||||
nt::StartClient4(client1, "client1");
|
||||
nt::StartClient3(client2, "client2");
|
||||
nt::SetServer(client1, "127.0.0.1", 10000);
|
||||
nt::SetServer(client2, "127.0.0.1", 10001);
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
||||
// add "typical" set of subscribers on client and server
|
||||
nt::SubscribeMultiple(client1, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(client2, {{std::string_view{}}});
|
||||
nt::SubscribeMultiple(server, {{std::string_view{}}});
|
||||
|
||||
// create 1000 entries
|
||||
std::array<NT_Entry, 1000> pubs;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
pubs[i] = nt::GetEntry(
|
||||
nt::GetTopic(server,
|
||||
fmt::format("/some/long/name/with/lots/of/slashes/{}", i)),
|
||||
NT_DOUBLE_ARRAY, "double[]");
|
||||
}
|
||||
|
||||
// warm up
|
||||
for (int i = 1; i <= 100; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
nt::FlushLocal(server);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
}
|
||||
|
||||
std::vector<int64_t> flushTimes;
|
||||
flushTimes.reserve(1001);
|
||||
|
||||
std::vector<int64_t> times;
|
||||
times.reserve(1001);
|
||||
|
||||
// benchmark
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
int64_t now = nt::Now();
|
||||
for (int i = 1; i <= 1000; ++i) {
|
||||
for (auto pub : pubs) {
|
||||
double vals[3] = {i * 0.01, i * 0.02, i * 0.03};
|
||||
nt::SetDoubleArray(pub, vals);
|
||||
}
|
||||
int64_t prev = now;
|
||||
now = nt::Now();
|
||||
times.emplace_back(now - prev);
|
||||
nt::FlushLocal(server);
|
||||
nt::Flush(server);
|
||||
flushTimes.emplace_back(nt::Now() - now);
|
||||
std::this_thread::sleep_for(0.02s);
|
||||
now = nt::Now();
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
|
||||
fmt::print("total time: {}us\n",
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
|
||||
.count());
|
||||
PrintTimes(times);
|
||||
fmt::print("-- Flush --\n");
|
||||
PrintTimes(flushTimes);
|
||||
}
|
||||
|
||||
static std::random_device r;
|
||||
static std::mt19937 gen(r());
|
||||
static std::uniform_real_distribution<double> dist;
|
||||
|
||||
void stress() {
|
||||
auto server = nt::CreateInstance();
|
||||
nt::StartServer(server, "stress.json", "127.0.0.1", 0, 10000);
|
||||
nt::SubscribeMultiple(server, {{std::string_view{}}});
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
for (int count = 0; count < 10; ++count) {
|
||||
std::thread{[] {
|
||||
auto client = nt::CreateInstance();
|
||||
nt::SubscribeMultiple(client, {{std::string_view{}}});
|
||||
for (int i = 0; i < 300; ++i) {
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// connect
|
||||
nt::StartClient4(client, "client");
|
||||
nt::SetServer(client, "127.0.0.1", 10000);
|
||||
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// disconnect
|
||||
nt::StopClient(client);
|
||||
}
|
||||
nt::DestroyInstance(client);
|
||||
}}.detach();
|
||||
|
||||
std::thread{[server, count] {
|
||||
for (int n = 0; n < 300; ++n) {
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.01s * dist(gen));
|
||||
|
||||
// create publishers
|
||||
NT_Publisher pub[30];
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
pub[i] =
|
||||
nt::Publish(nt::GetTopic(server, fmt::format("{}_{}", count, i)),
|
||||
NT_DOUBLE, "double", {});
|
||||
}
|
||||
|
||||
// publish values
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
// sleep a random amount of time between each value set
|
||||
std::this_thread::sleep_for(0.001s * dist(gen));
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
nt::SetDouble(pub[i], dist(gen));
|
||||
}
|
||||
nt::FlushLocal(server);
|
||||
}
|
||||
|
||||
// sleep a random amount of time
|
||||
std::this_thread::sleep_for(0.1s * dist(gen));
|
||||
|
||||
// remove publishers
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
nt::Unpublish(pub[i]);
|
||||
}
|
||||
}
|
||||
}}.detach();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(100s);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class NetworkTableListener implements AutoCloseable {
|
||||
* Create a time synchronization listener.
|
||||
*
|
||||
* @param inst instance
|
||||
* @param immediateNotify notify listener of current time synchonization value
|
||||
* @param immediateNotify notify listener of current time synchronization value
|
||||
* @param listener listener function
|
||||
* @return Listener
|
||||
*/
|
||||
|
||||
@@ -243,7 +243,7 @@ struct DataLoggerData {
|
||||
int Start(TopicData* topic, int64_t time) {
|
||||
return log.Start(fmt::format("{}{}", logPrefix,
|
||||
wpi::drop_front(topic->name, prefix.size())),
|
||||
topic->typeStr,
|
||||
topic->typeStr == "int" ? "int64" : topic->typeStr,
|
||||
DataLoggerEntry::MakeMetadata(topic->propertiesStr), time);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class NCImpl {
|
||||
void StopDSClient();
|
||||
|
||||
virtual void TcpConnected(uv::Tcp& tcp) = 0;
|
||||
virtual void ForceDisconnect(std::string_view reason) = 0;
|
||||
virtual void Disconnect(std::string_view reason);
|
||||
|
||||
// invariants
|
||||
@@ -99,6 +100,7 @@ class NCImpl3 : public NCImpl {
|
||||
|
||||
void HandleLocal();
|
||||
void TcpConnected(uv::Tcp& tcp) final;
|
||||
void ForceDisconnect(std::string_view reason) override;
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::shared_ptr<net3::UvStreamConnection3> m_wire;
|
||||
@@ -117,6 +119,7 @@ class NCImpl4 : public NCImpl {
|
||||
void HandleLocal();
|
||||
void TcpConnected(uv::Tcp& tcp) final;
|
||||
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
|
||||
void ForceDisconnect(std::string_view reason) override;
|
||||
void Disconnect(std::string_view reason) override;
|
||||
|
||||
std::function<void(int64_t serverTimeOffset, int64_t rtt2, bool valid)>
|
||||
@@ -155,7 +158,9 @@ void NCImpl::SetServers(
|
||||
[this, servers = std::move(serversCopy)](uv::Loop&) mutable {
|
||||
m_servers = std::move(servers);
|
||||
if (m_dsClientServer.first.empty()) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -167,14 +172,20 @@ void NCImpl::StartDSClient(unsigned int port) {
|
||||
}
|
||||
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT4 : port;
|
||||
m_dsClient = wpi::DsClient::Create(m_loop, m_logger);
|
||||
m_dsClient->setIp.connect([this](std::string_view ip) {
|
||||
m_dsClientServer.first = ip;
|
||||
m_parallelConnect->SetServers({{m_dsClientServer}});
|
||||
});
|
||||
m_dsClient->clearIp.connect([this] {
|
||||
m_dsClientServer.first.clear();
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
});
|
||||
if (m_dsClient) {
|
||||
m_dsClient->setIp.connect([this](std::string_view ip) {
|
||||
m_dsClientServer.first = ip;
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers({{m_dsClientServer}});
|
||||
}
|
||||
});
|
||||
m_dsClient->clearIp.connect([this] {
|
||||
m_dsClientServer.first.clear();
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->SetServers(m_servers);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,15 +202,20 @@ void NCImpl::Disconnect(std::string_view reason) {
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->Stop();
|
||||
}
|
||||
m_sendValuesTimer->Stop();
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Stop();
|
||||
}
|
||||
m_localStorage.ClearNetwork();
|
||||
m_localQueue.ClearQueue();
|
||||
m_connList.RemoveConnection(m_connHandle);
|
||||
m_connHandle = 0;
|
||||
|
||||
// start trying to connect again
|
||||
uv::Timer::SingleShot(m_loop, kReconnectRate,
|
||||
[this] { m_parallelConnect->Disconnected(); });
|
||||
uv::Timer::SingleShot(m_loop, kReconnectRate, [this] {
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Disconnected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
@@ -212,25 +228,31 @@ NCImpl3::NCImpl3(int inst, std::string_view id,
|
||||
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
|
||||
|
||||
m_sendValuesTimer = uv::Timer::Create(loop);
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
});
|
||||
}
|
||||
@@ -261,8 +283,10 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
auto clientImpl = std::make_shared<net3::ClientImpl3>(
|
||||
m_loop.Now().count(), m_inst, *wire, m_logger, [this](uint32_t repeatMs) {
|
||||
DEBUG4("Setting periodic timer to {}", repeatMs);
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
}
|
||||
});
|
||||
clientImpl->Start(
|
||||
m_id, [this, wire,
|
||||
@@ -276,7 +300,9 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
}
|
||||
|
||||
m_wire = std::move(wire);
|
||||
m_clientImpl = std::move(clientImpl);
|
||||
@@ -305,7 +331,7 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.closed.connect([this, &tcp] {
|
||||
DEBUG3("NT3 TCP connection closed");
|
||||
if (!tcp.IsLoopClosing()) {
|
||||
Disconnect(m_wire->GetDisconnectReason());
|
||||
Disconnect(m_wire ? m_wire->GetDisconnectReason() : "unknown");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -323,6 +349,12 @@ void NCImpl3::TcpConnected(uv::Tcp& tcp) {
|
||||
tcp.StartRead();
|
||||
}
|
||||
|
||||
void NCImpl3::ForceDisconnect(std::string_view reason) {
|
||||
if (m_wire) {
|
||||
m_wire->Disconnect(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl3::Disconnect(std::string_view reason) {
|
||||
INFO("DISCONNECTED NT3 connection: {}", reason);
|
||||
m_clientImpl.reset();
|
||||
@@ -343,34 +375,42 @@ NCImpl4::NCImpl4(
|
||||
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
|
||||
|
||||
m_readLocalTimer = uv::Timer::Create(loop);
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendControl(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendControl(m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
}
|
||||
|
||||
m_sendValuesTimer = uv::Timer::Create(loop);
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->timeout.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
if (m_clientImpl) {
|
||||
HandleLocal();
|
||||
m_clientImpl->SendValues(m_loop.Now().count(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
});
|
||||
}
|
||||
@@ -418,7 +458,9 @@ void NCImpl4::TcpConnected(uv::Tcp& tcp) {
|
||||
}
|
||||
|
||||
void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
if (m_parallelConnect) {
|
||||
m_parallelConnect->Succeeded(tcp);
|
||||
}
|
||||
|
||||
ConnectionInfo connInfo;
|
||||
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip, &connInfo.remote_port);
|
||||
@@ -432,8 +474,10 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
m_loop.Now().count(), m_inst, *m_wire, m_logger, m_timeSyncUpdated,
|
||||
[this](uint32_t repeatMs) {
|
||||
DEBUG4("Setting periodic timer to {}", repeatMs);
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
if (m_sendValuesTimer) {
|
||||
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
|
||||
uv::Timer::Time{repeatMs});
|
||||
}
|
||||
});
|
||||
m_clientImpl->SetLocal(&m_localStorage);
|
||||
m_localStorage.StartNetwork(&m_localQueue);
|
||||
@@ -451,13 +495,24 @@ void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
|
||||
});
|
||||
ws.binary.connect([this](std::span<const uint8_t> data, bool) {
|
||||
if (m_clientImpl) {
|
||||
m_clientImpl->ProcessIncomingBinary(data);
|
||||
m_clientImpl->ProcessIncomingBinary(m_loop.Now().count(), data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NCImpl4::ForceDisconnect(std::string_view reason) {
|
||||
if (m_wire) {
|
||||
m_wire->Disconnect(reason);
|
||||
}
|
||||
}
|
||||
|
||||
void NCImpl4::Disconnect(std::string_view reason) {
|
||||
INFO("DISCONNECTED NT4 connection: {}", reason);
|
||||
std::string realReason;
|
||||
if (m_wire) {
|
||||
realReason = m_wire->GetDisconnectReason();
|
||||
}
|
||||
INFO("DISCONNECTED NT4 connection: {}",
|
||||
realReason.empty() ? reason : realReason);
|
||||
m_clientImpl.reset();
|
||||
m_wire.reset();
|
||||
NCImpl::Disconnect(reason);
|
||||
@@ -494,7 +549,7 @@ void NetworkClient::SetServers(
|
||||
|
||||
void NetworkClient::Disconnect() {
|
||||
m_impl->m_loopRunner.ExecAsync(
|
||||
[this](auto&) { m_impl->Disconnect("requested by application"); });
|
||||
[this](auto&) { m_impl->ForceDisconnect("requested by application"); });
|
||||
}
|
||||
|
||||
void NetworkClient::StartDSClient(unsigned int port) {
|
||||
@@ -542,7 +597,7 @@ void NetworkClient3::SetServers(
|
||||
|
||||
void NetworkClient3::Disconnect() {
|
||||
m_impl->m_loopRunner.ExecAsync(
|
||||
[this](auto&) { m_impl->Disconnect("requested by application"); });
|
||||
[this](auto&) { m_impl->ForceDisconnect("requested by application"); });
|
||||
}
|
||||
|
||||
void NetworkClient3::StartDSClient(unsigned int port) {
|
||||
|
||||
@@ -255,8 +255,9 @@ void ServerConnection4::ProcessWsUpgrade() {
|
||||
m_info.remote_id = dedupName;
|
||||
m_server.AddConnection(this, m_info);
|
||||
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
|
||||
auto realReason = m_wire->GetDisconnectReason();
|
||||
INFO("DISCONNECTED NT4 client '{}' (from {}): {}", m_info.remote_id,
|
||||
m_connInfo, reason);
|
||||
m_connInfo, realReason.empty() ? reason : realReason);
|
||||
ConnectionClosed();
|
||||
});
|
||||
m_websocket->text.connect([this](std::string_view data, bool) {
|
||||
@@ -419,36 +420,46 @@ void NSImpl::Init() {
|
||||
|
||||
// set up timers
|
||||
m_readLocalTimer = uv::Timer::Create(m_loop);
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
HandleLocal();
|
||||
m_serverImpl.SendControl(m_loop.Now().count());
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
if (m_readLocalTimer) {
|
||||
m_readLocalTimer->timeout.connect([this] {
|
||||
HandleLocal();
|
||||
m_serverImpl.SendControl(m_loop.Now().count());
|
||||
});
|
||||
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
|
||||
}
|
||||
|
||||
m_savePersistentTimer = uv::Timer::Create(m_loop);
|
||||
m_savePersistentTimer->timeout.connect([this] {
|
||||
if (m_serverImpl.PersistentChanged()) {
|
||||
uv::QueueWork(
|
||||
m_loop,
|
||||
[this, fn = m_persistentFilename,
|
||||
data = m_serverImpl.DumpPersistent()] { SavePersistent(fn, data); },
|
||||
nullptr);
|
||||
}
|
||||
});
|
||||
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
|
||||
if (m_savePersistentTimer) {
|
||||
m_savePersistentTimer->timeout.connect([this] {
|
||||
if (m_serverImpl.PersistentChanged()) {
|
||||
uv::QueueWork(
|
||||
m_loop,
|
||||
[this, fn = m_persistentFilename,
|
||||
data = m_serverImpl.DumpPersistent()] {
|
||||
SavePersistent(fn, data);
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
});
|
||||
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
|
||||
}
|
||||
|
||||
// set up flush async
|
||||
m_flush = uv::Async<>::Create(m_loop);
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
for (auto&& conn : m_connections) {
|
||||
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
if (m_flush) {
|
||||
m_flush->wakeup.connect([this] {
|
||||
HandleLocal();
|
||||
for (auto&& conn : m_connections) {
|
||||
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
|
||||
}
|
||||
});
|
||||
}
|
||||
m_flushAtomic = m_flush.get();
|
||||
|
||||
m_flushLocal = uv::Async<>::Create(m_loop);
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
if (m_flushLocal) {
|
||||
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
|
||||
}
|
||||
m_flushLocalAtomic = m_flushLocal.get();
|
||||
|
||||
INFO("Listening on NT3 port {}, NT4 port {}", m_port3, m_port4);
|
||||
|
||||
@@ -32,7 +32,7 @@ static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -53,7 +53,7 @@ class CImpl : public ServerMessageHandler {
|
||||
timeSyncUpdated,
|
||||
std::function<void(uint32_t repeatMs)> setPeriodic);
|
||||
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
bool SendControl(uint64_t curTimeMs);
|
||||
void SendValues(uint64_t curTimeMs, bool flush);
|
||||
@@ -91,6 +91,7 @@ class CImpl : public ServerMessageHandler {
|
||||
// timestamp handling
|
||||
static constexpr uint32_t kPingIntervalMs = 3000;
|
||||
uint64_t m_nextPingTimeMs{0};
|
||||
uint64_t m_pongTimeMs{0};
|
||||
uint32_t m_rtt2Us{UINT32_MAX};
|
||||
bool m_haveTimeOffset{false};
|
||||
int64_t m_serverTimeOffsetUs{0};
|
||||
@@ -125,7 +126,8 @@ CImpl::CImpl(
|
||||
m_setPeriodic(m_periodMs);
|
||||
}
|
||||
|
||||
void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
void CImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
for (;;) {
|
||||
if (data.empty()) {
|
||||
break;
|
||||
@@ -150,6 +152,7 @@ void CImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
}
|
||||
DEBUG4("RTT ping response time {} value {}", value.time(),
|
||||
value.GetInteger());
|
||||
m_pongTimeMs = curTimeMs;
|
||||
int64_t now = wpi::Now();
|
||||
int64_t rtt2 = (now - value.GetInteger()) / 2;
|
||||
if (rtt2 < m_rtt2Us) {
|
||||
@@ -207,6 +210,12 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
|
||||
// start a timestamp RTT ping if it's time to do one
|
||||
if (curTimeMs >= m_nextPingTimeMs) {
|
||||
// if we didn't receive a response to our last ping, disconnect
|
||||
if (m_nextPingTimeMs != 0 && m_pongTimeMs == 0) {
|
||||
m_wire.Disconnect("timed out");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return false;
|
||||
}
|
||||
@@ -215,6 +224,7 @@ bool CImpl::SendControl(uint64_t curTimeMs) {
|
||||
WireEncodeBinary(m_wire.SendBinary().Add(), -1, 0, Value::MakeInteger(now));
|
||||
// drift isn't critical here, so just go from current time
|
||||
m_nextPingTimeMs = curTimeMs + kPingIntervalMs;
|
||||
m_pongTimeMs = 0;
|
||||
}
|
||||
|
||||
if (!m_outgoing.empty()) {
|
||||
@@ -313,7 +323,9 @@ void CImpl::SendInitialValues() {
|
||||
|
||||
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
|
||||
if (!m_wire.Ready()) {
|
||||
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return false;
|
||||
@@ -463,8 +475,9 @@ void ClientImpl::ProcessIncomingText(std::string_view data) {
|
||||
WireDecodeText(data, *m_impl, m_impl->m_logger);
|
||||
}
|
||||
|
||||
void ClientImpl::ProcessIncomingBinary(std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(data);
|
||||
void ClientImpl::ProcessIncomingBinary(uint64_t curTimeMs,
|
||||
std::span<const uint8_t> data) {
|
||||
m_impl->ProcessIncomingBinary(curTimeMs, data);
|
||||
}
|
||||
|
||||
void ClientImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
|
||||
|
||||
@@ -40,7 +40,7 @@ class ClientImpl {
|
||||
~ClientImpl();
|
||||
|
||||
void ProcessIncomingText(std::string_view data);
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data);
|
||||
void ProcessIncomingBinary(uint64_t curTimeMs, std::span<const uint8_t> data);
|
||||
void HandleLocal(std::vector<ClientMessage>&& msgs);
|
||||
|
||||
void SendControl(uint64_t curTimeMs);
|
||||
|
||||
@@ -50,7 +50,7 @@ static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -78,12 +78,10 @@ class SImpl;
|
||||
|
||||
class ClientData {
|
||||
public:
|
||||
ClientData(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ClientData(std::string_view name, std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: m_originalName{originalName},
|
||||
m_name{name},
|
||||
: m_name{name},
|
||||
m_connInfo{connInfo},
|
||||
m_local{local},
|
||||
m_setPeriodic{std::move(setPeriodic)},
|
||||
@@ -114,12 +112,10 @@ class ClientData {
|
||||
std::string_view name, bool special,
|
||||
wpi::SmallVectorImpl<SubscriberData*>& buf);
|
||||
|
||||
std::string_view GetOriginalName() const { return m_originalName; }
|
||||
std::string_view GetName() const { return m_name; }
|
||||
int GetId() const { return m_id; }
|
||||
|
||||
protected:
|
||||
std::string m_originalName;
|
||||
std::string m_name;
|
||||
std::string m_connInfo;
|
||||
bool m_local; // local to machine
|
||||
@@ -143,12 +139,10 @@ class ClientData {
|
||||
|
||||
class ClientData4Base : public ClientData, protected ClientMessageHandler {
|
||||
public:
|
||||
ClientData4Base(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local,
|
||||
ClientData4Base(std::string_view name, std::string_view connInfo, bool local,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server,
|
||||
int id, wpi::Logger& logger)
|
||||
: ClientData{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger} {}
|
||||
: ClientData{name, connInfo, local, setPeriodic, server, id, logger} {}
|
||||
|
||||
protected:
|
||||
// ClientMessageHandler interface
|
||||
@@ -170,8 +164,7 @@ class ClientData4Base : public ClientData, protected ClientMessageHandler {
|
||||
class ClientDataLocal final : public ClientData4Base {
|
||||
public:
|
||||
ClientDataLocal(SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{"", "", "", true, [](uint32_t) {}, server, id, logger} {
|
||||
}
|
||||
: ClientData4Base{"", "", true, [](uint32_t) {}, server, id, logger} {}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final {}
|
||||
void ProcessIncomingBinary(std::span<const uint8_t> data) final {}
|
||||
@@ -189,12 +182,10 @@ class ClientDataLocal final : public ClientData4Base {
|
||||
|
||||
class ClientData4 final : public ClientData4Base {
|
||||
public:
|
||||
ClientData4(std::string_view originalName, std::string_view name,
|
||||
std::string_view connInfo, bool local, WireConnection& wire,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData4Base{originalName, name, connInfo, local,
|
||||
setPeriodic, server, id, logger},
|
||||
ClientData4(std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic,
|
||||
SImpl& server, int id, wpi::Logger& logger)
|
||||
: ClientData4Base{name, connInfo, local, setPeriodic, server, id, logger},
|
||||
m_wire{wire} {}
|
||||
|
||||
void ProcessIncomingText(std::string_view data) final;
|
||||
@@ -214,6 +205,7 @@ class ClientData4 final : public ClientData4Base {
|
||||
|
||||
private:
|
||||
std::vector<ServerMessage> m_outgoing;
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
|
||||
bool WriteBinary(int64_t id, int64_t time, const Value& value) {
|
||||
return WireEncodeBinary(SendBinary().Add(), id, time, value);
|
||||
@@ -246,7 +238,7 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireConnection3& wire, ServerImpl::Connected3Func connected,
|
||||
ServerImpl::SetPeriodicFunc setPeriodic, SImpl& server, int id,
|
||||
wpi::Logger& logger)
|
||||
: ClientData{"", "", connInfo, local, setPeriodic, server, id, logger},
|
||||
: ClientData{"", connInfo, local, setPeriodic, server, id, logger},
|
||||
m_connected{std::move(connected)},
|
||||
m_wire{wire},
|
||||
m_decoder{*this} {}
|
||||
@@ -291,6 +283,7 @@ class ClientData3 final : public ClientData, private net3::MessageHandler3 {
|
||||
net3::WireDecoder3 m_decoder;
|
||||
|
||||
std::vector<net3::Message3> m_outgoing;
|
||||
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
|
||||
int64_t m_nextPubUid{1};
|
||||
|
||||
struct TopicData3 {
|
||||
@@ -650,6 +643,11 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
|
||||
}
|
||||
|
||||
// see if this immediately subscribes to any topics
|
||||
// for transmit efficiency, we want to batch announcements and values, so
|
||||
// send announcements in first loop and remember what we want to send in
|
||||
// second loop.
|
||||
std::vector<TopicData*> dataToSend;
|
||||
dataToSend.reserve(m_server.m_topics.size());
|
||||
for (auto&& topic : m_server.m_topics) {
|
||||
bool removed = false;
|
||||
if (replace) {
|
||||
@@ -687,11 +685,15 @@ void ClientData4Base::ClientSubscribe(int64_t subuid,
|
||||
// send last value
|
||||
if (added && !sub->options.topicsOnly && !wasSubscribedValue &&
|
||||
topic->lastValue) {
|
||||
DEBUG4("send last value for {} to client {}", topic->name, m_id);
|
||||
SendValue(topic.get(), topic->lastValue, kSendAll);
|
||||
dataToSend.emplace_back(topic.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto topic : dataToSend) {
|
||||
DEBUG4("send last value for {} to client {}", topic->name, m_id);
|
||||
SendValue(topic, topic->lastValue, kSendAll);
|
||||
}
|
||||
|
||||
// update meta data
|
||||
UpdateMetaClientSub();
|
||||
|
||||
@@ -857,24 +859,23 @@ void ClientData4::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
if (auto m = std::get_if<ServerValueMsg>(&msg.contents)) {
|
||||
if (m->topic == topic->id) {
|
||||
// replace, or append if not present
|
||||
auto [it, added] =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
if (auto m =
|
||||
std::get_if<ServerValueMsg>(&m_outgoing[it->second].contents)) {
|
||||
if (m->topic == topic->id) { // should always be true
|
||||
m->value = value;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_outgoing.emplace_back(
|
||||
ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
}
|
||||
m_outgoing.emplace_back(ServerMessage{ServerValueMsg{topic->id, value}});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -943,7 +944,9 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
if (!m_wire.Ready()) {
|
||||
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return;
|
||||
@@ -957,6 +960,7 @@ void ClientData4::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -989,6 +993,7 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
mode = ClientData::kSendImmNoFlush; // always send local immediately
|
||||
}
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
bool added = false;
|
||||
|
||||
switch (mode) {
|
||||
case ClientData::kSendDisabled: // do nothing
|
||||
@@ -1009,24 +1014,26 @@ void ClientData3::SendValue(TopicData* topic, const Value& value,
|
||||
}
|
||||
break;
|
||||
case ClientData::kSendNormal: {
|
||||
// scan outgoing and replace, or append if not present
|
||||
bool found = false;
|
||||
for (auto&& msg : m_outgoing) {
|
||||
// replace, or append if not present
|
||||
wpi::DenseMap<NT_Topic, size_t>::iterator it;
|
||||
std::tie(it, added) =
|
||||
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
|
||||
if (!added && it->second < m_outgoing.size()) {
|
||||
auto& msg = m_outgoing[it->second];
|
||||
if (msg.Is(net3::Message3::kEntryUpdate) ||
|
||||
msg.Is(net3::Message3::kEntryAssign)) {
|
||||
if (msg.id() == topic->id) {
|
||||
if (msg.id() == topic->id) { // should always be true
|
||||
msg.SetValue(value);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case ClientData::kSendAll: // append to outgoing
|
||||
if (!added) {
|
||||
m_outgoingValueMap[topic->id] = m_outgoing.size();
|
||||
}
|
||||
++topic3->seqNum;
|
||||
if (topic3->sentAssign) {
|
||||
m_outgoing.emplace_back(net3::Message3::EntryUpdate(
|
||||
@@ -1114,7 +1121,9 @@ void ClientData3::SendOutgoing(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
if (!m_wire.Ready()) {
|
||||
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return;
|
||||
@@ -1125,6 +1134,7 @@ void ClientData3::SendOutgoing(uint64_t curTimeMs) {
|
||||
net3::WireEncode(out.stream(), msg);
|
||||
}
|
||||
m_outgoing.resize(0);
|
||||
m_outgoingValueMap.clear();
|
||||
m_lastSendMs = curTimeMs;
|
||||
}
|
||||
|
||||
@@ -1283,7 +1293,7 @@ void ClientData3::EntryAssign(std::string_view name, unsigned int id,
|
||||
auto topic = m_server.CreateTopic(this, name, typeStr, properties);
|
||||
TopicData3* topic3 = GetTopic3(topic);
|
||||
if (topic3->published || topic3->sentAssign) {
|
||||
WARNING("ignorning client {} duplicate publish of '{}'", m_id, name);
|
||||
WARNING("ignoring client {} duplicate publish of '{}'", m_id, name);
|
||||
return;
|
||||
}
|
||||
++topic3->seqNum;
|
||||
@@ -1503,39 +1513,29 @@ SImpl::SImpl(wpi::Logger& logger) : m_logger{logger} {
|
||||
std::pair<std::string, int> SImpl::AddClient(
|
||||
std::string_view name, std::string_view connInfo, bool local,
|
||||
WireConnection& wire, ServerImpl::SetPeriodicFunc setPeriodic) {
|
||||
// strip anything after @ in the name
|
||||
name = wpi::split(name, '@').first;
|
||||
if (name.empty()) {
|
||||
name = "NT4";
|
||||
}
|
||||
size_t index = m_clients.size();
|
||||
// find an empty slot and check for duplicates
|
||||
// find an empty slot
|
||||
// just do a linear search as number of clients is typically small (<10)
|
||||
int duplicateName = 0;
|
||||
for (size_t i = 0, end = index; i < end; ++i) {
|
||||
auto& clientData = m_clients[i];
|
||||
if (clientData && clientData->GetOriginalName() == name) {
|
||||
++duplicateName;
|
||||
} else if (!clientData && index == end) {
|
||||
if (!m_clients[i]) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index == m_clients.size()) {
|
||||
m_clients.emplace_back();
|
||||
}
|
||||
|
||||
// if duplicate name, de-duplicate
|
||||
std::string dedupName;
|
||||
if (duplicateName > 0) {
|
||||
dedupName = fmt::format("{}@{}", name, duplicateName);
|
||||
} else {
|
||||
dedupName = name;
|
||||
}
|
||||
// ensure name is unique by suffixing index
|
||||
std::string dedupName = fmt::format("{}@{}", name, index);
|
||||
|
||||
auto& clientData = m_clients[index];
|
||||
clientData = std::make_unique<ClientData4>(name, dedupName, connInfo, local,
|
||||
wire, std::move(setPeriodic),
|
||||
*this, index, m_logger);
|
||||
clientData = std::make_unique<ClientData4>(dedupName, connInfo, local, wire,
|
||||
std::move(setPeriodic), *this,
|
||||
index, m_logger);
|
||||
|
||||
// create client meta topics
|
||||
clientData->m_metaPub =
|
||||
@@ -2289,9 +2289,10 @@ void ServerImpl::SendControl(uint64_t curTimeMs) {
|
||||
}
|
||||
|
||||
void ServerImpl::SendValues(int clientId, uint64_t curTimeMs) {
|
||||
auto client = m_impl->m_clients[clientId].get();
|
||||
client->SendOutgoing(curTimeMs);
|
||||
client->Flush();
|
||||
if (auto client = m_impl->m_clients[clientId].get()) {
|
||||
client->SendOutgoing(curTimeMs);
|
||||
client->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerImpl::HandleLocal(std::span<const ClientMessage> msgs) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <span>
|
||||
|
||||
#include <wpi/SpanExtras.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpinet/WebSocket.h>
|
||||
|
||||
using namespace nt;
|
||||
@@ -25,6 +26,12 @@ WebSocketConnection::~WebSocketConnection() {
|
||||
for (auto&& buf : m_buf_pool) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
for (auto&& buf : m_text_buffers) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
for (auto&& buf : m_binary_buffers) {
|
||||
buf.Deallocate();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketConnection::Flush() {
|
||||
@@ -61,9 +68,11 @@ void WebSocketConnection::Flush() {
|
||||
m_binary_buffers.clear();
|
||||
m_text_pos = 0;
|
||||
m_binary_pos = 0;
|
||||
m_lastFlushTime = wpi::Now();
|
||||
}
|
||||
|
||||
void WebSocketConnection::Disconnect(std::string_view reason) {
|
||||
m_reason = reason;
|
||||
m_ws.Close(1005, reason);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <wpi/SmallVector.h>
|
||||
@@ -32,8 +34,12 @@ class WebSocketConnection final
|
||||
|
||||
void Flush() final;
|
||||
|
||||
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
|
||||
|
||||
void Disconnect(std::string_view reason) final;
|
||||
|
||||
std::string_view GetDisconnectReason() const { return m_reason; }
|
||||
|
||||
private:
|
||||
void StartSendText() final;
|
||||
void FinishSendText() final;
|
||||
@@ -64,6 +70,8 @@ class WebSocketConnection final
|
||||
size_t m_binary_pos = 0;
|
||||
bool m_in_text = false;
|
||||
int m_sendsActive = 0;
|
||||
std::string m_reason;
|
||||
uint64_t m_lastFlushTime = 0;
|
||||
};
|
||||
|
||||
} // namespace nt::net
|
||||
|
||||
@@ -30,6 +30,8 @@ class WireConnection {
|
||||
|
||||
virtual void Flush() = 0;
|
||||
|
||||
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
|
||||
|
||||
virtual void Disconnect(std::string_view reason) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <wpi/DenseMap.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/timestamp.h>
|
||||
|
||||
#include "Handle.h"
|
||||
#include "Log.h"
|
||||
@@ -33,7 +34,7 @@ static constexpr uint32_t kMinPeriodMs = 5;
|
||||
|
||||
// maximum amount of time the wire can be not ready to send another
|
||||
// transmission before we close the connection
|
||||
static constexpr uint32_t kWireMaxNotReadyMs = 1000;
|
||||
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -232,7 +233,7 @@ void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
|
||||
|
||||
auto out = m_wire.Send();
|
||||
|
||||
// send keep-alives
|
||||
// send keep-alive
|
||||
if (curTimeMs >= m_nextKeepAliveTimeMs) {
|
||||
if (!CheckNetworkReady(curTimeMs)) {
|
||||
return;
|
||||
@@ -303,7 +304,9 @@ void CImpl::SendValue(Writer& out, Entry* entry, const Value& value) {
|
||||
|
||||
bool CImpl::CheckNetworkReady(uint64_t curTimeMs) {
|
||||
if (!m_wire.Ready()) {
|
||||
if (m_lastSendMs != 0 && curTimeMs > (m_lastSendMs + kWireMaxNotReadyMs)) {
|
||||
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
|
||||
uint64_t now = wpi::Now();
|
||||
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
|
||||
m_wire.Disconnect("transmit stalled");
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "UvStreamConnection3.h"
|
||||
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpinet/uv/Stream.h>
|
||||
|
||||
using namespace nt;
|
||||
@@ -33,6 +34,7 @@ void UvStreamConnection3::Flush() {
|
||||
});
|
||||
m_buffers.clear();
|
||||
m_os.reset();
|
||||
m_lastFlushTime = wpi::Now();
|
||||
}
|
||||
|
||||
void UvStreamConnection3::Disconnect(std::string_view reason) {
|
||||
|
||||
@@ -38,6 +38,8 @@ class UvStreamConnection3 final
|
||||
|
||||
void Flush() final;
|
||||
|
||||
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
|
||||
|
||||
void Disconnect(std::string_view reason) final;
|
||||
|
||||
std::string_view GetDisconnectReason() const { return m_reason; }
|
||||
@@ -54,6 +56,7 @@ class UvStreamConnection3 final
|
||||
std::vector<wpi::uv::Buffer> m_buf_pool;
|
||||
wpi::raw_uv_ostream m_os;
|
||||
std::string m_reason;
|
||||
uint64_t m_lastFlushTime = 0;
|
||||
int m_sendsActive = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ class WireConnection3 {
|
||||
|
||||
virtual void Flush() = 0;
|
||||
|
||||
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
|
||||
|
||||
virtual void Disconnect(std::string_view reason) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -32,6 +32,8 @@ class MockWireConnection : public WireConnection {
|
||||
|
||||
MOCK_METHOD(void, Flush, (), (override));
|
||||
|
||||
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
|
||||
|
||||
protected:
|
||||
|
||||
@@ -51,7 +51,7 @@ TEST_F(ServerImplTest, AddClient) {
|
||||
MockSetPeriodicFunc setPeriodic;
|
||||
auto [name, id] = server.AddClient("test", "connInfo", false, wire,
|
||||
setPeriodic.AsStdFunction());
|
||||
EXPECT_EQ(name, "test");
|
||||
EXPECT_EQ(name, "test@1");
|
||||
EXPECT_NE(id, -1);
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ TEST_F(ServerImplTest, AddDuplicateClient) {
|
||||
setPeriodic1.AsStdFunction());
|
||||
auto [name2, id2] = server.AddClient("test", "connInfo2", false, wire2,
|
||||
setPeriodic2.AsStdFunction());
|
||||
EXPECT_EQ(name1, "test");
|
||||
EXPECT_EQ(name1, "test@1");
|
||||
EXPECT_NE(id1, -1);
|
||||
EXPECT_EQ(name2, "test@1");
|
||||
EXPECT_EQ(name2, "test@2");
|
||||
EXPECT_NE(id1, id2);
|
||||
EXPECT_NE(id2, -1);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ class MockWireConnection3 : public WireConnection3 {
|
||||
|
||||
MOCK_METHOD(void, Flush, (), (override));
|
||||
|
||||
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
|
||||
|
||||
protected:
|
||||
|
||||
@@ -119,6 +119,7 @@ static void DisplayGui() {
|
||||
gui::EmitViewMenu();
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
gFlagsSettings.DisplayMenu();
|
||||
glass::DisplayNetworkTablesAddMenu(gModel.get());
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ int main(int argc, char* argv[]) {
|
||||
std::perror("readlink");
|
||||
return 1;
|
||||
} else if (readlink_len == PATH_MAX) {
|
||||
std::printf("Truncation occured\n");
|
||||
std::printf("Truncation occurred\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ DESTINATION_TEST_RESULTS=none
|
||||
LOCAL_TEST_RESULTS=none
|
||||
|
||||
|
||||
# Begin searching for options from the second paramater on
|
||||
# Begin searching for options from the second parameter on
|
||||
PARAM_ARGS=${@:2}
|
||||
|
||||
if [[ "$1" = java ]]; then
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# This file is intended to be run in the DEFAULT_TEST_DIR on the roboRIO.
|
||||
# Do not attempt to run this file on your local system.
|
||||
# There is one file (delploy-and-run-test-on-robot.sh) that is designed to
|
||||
# deploy this file allong with the compiled tests for you.
|
||||
# deploy this file along with the compiled tests for you.
|
||||
|
||||
# Configurable variables
|
||||
source config.sh
|
||||
@@ -24,9 +24,9 @@ Where:
|
||||
-h Show this help text
|
||||
-d The directory where the tests have been placed.
|
||||
This is done to prevent overwriting an already running program.
|
||||
This scrip will automatically move the test into the ${DEFAULT_TEST_DIR}
|
||||
This script will automatically move the test into the ${DEFAULT_TEST_DIR}
|
||||
directory.
|
||||
Default: Assumes the test is in the same directory as this scrip.
|
||||
Default: Assumes the test is in the same directory as this script.
|
||||
-A Do not use the default arguments for the given language.
|
||||
arg The arguments to be passed to test."
|
||||
|
||||
@@ -42,7 +42,7 @@ fi
|
||||
LANGUAGE=none
|
||||
TEST_FILE=none
|
||||
TEST_DIR="$DEFAULT_TEST_DIR"
|
||||
# Begin searching for options from the second paramater on
|
||||
# Begin searching for options from the second parameter on
|
||||
PARAM_ARGS=${@:2}
|
||||
# Where the test arguments start
|
||||
TEST_RUN_ARGS=${@:2}
|
||||
@@ -125,7 +125,7 @@ elif [[ $TEST_DIR != "$DEFAULT_TEST_DIR" ]]; then
|
||||
mv "${TEST_DIR}/${TEST_FILE}" "${DEFAULT_TEST_DIR}"
|
||||
fi
|
||||
|
||||
# Make sure the excecutable file has permission to run
|
||||
# Make sure the executable file has permission to run
|
||||
|
||||
# Get the serial number and FPGADeviceCode for this rio
|
||||
export serialnum=`/sbin/fw_printenv -n serial#`
|
||||
|
||||
@@ -26,7 +26,11 @@ public abstract class CommandBase implements Sendable, Command {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified requirements to the command.
|
||||
* Adds the specified subsystems to the requirements of the command. The scheduler will prevent
|
||||
* two commands that require the same subsystem from being scheduled simultaneously.
|
||||
*
|
||||
* <p>Note that the scheduler determines the requirements of a command when it is scheduled, so
|
||||
* this method should normally be called from the command's constructor.
|
||||
*
|
||||
* @param requirements the requirements to add
|
||||
*/
|
||||
|
||||
@@ -626,7 +626,6 @@ public final class CommandScheduler implements NTSendable, AutoCloseable {
|
||||
*
|
||||
* @param command The command to check
|
||||
* @return true if composed
|
||||
* @throws IllegalArgumentException if the given commands have already been composed.
|
||||
*/
|
||||
public boolean isComposed(Command command) {
|
||||
return getComposedCommands().contains(command);
|
||||
|
||||
@@ -45,7 +45,7 @@ public class RamseteCommand extends CommandBase {
|
||||
private final PIDController m_leftController;
|
||||
private final PIDController m_rightController;
|
||||
private final BiConsumer<Double, Double> m_output;
|
||||
private DifferentialDriveWheelSpeeds m_prevSpeeds;
|
||||
private DifferentialDriveWheelSpeeds m_prevSpeeds = new DifferentialDriveWheelSpeeds();
|
||||
private double m_prevTime;
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,6 +50,14 @@ public interface Subsystem {
|
||||
CommandScheduler.getInstance().setDefaultCommand(this, defaultCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the default command for the subsystem. This will not cancel the default command if it
|
||||
* is currently running.
|
||||
*/
|
||||
default void removeDefaultCommand() {
|
||||
CommandScheduler.getInstance().removeDefaultCommand(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default command for this subsystem. Returns null if no default command is currently
|
||||
* associated with the subsystem.
|
||||
|
||||
@@ -9,8 +9,7 @@ import edu.wpi.first.util.sendable.SendableRegistry;
|
||||
import edu.wpi.first.wpilibj.Timer;
|
||||
|
||||
/**
|
||||
* A command that does nothing but takes a specified amount of time to finish. Useful for
|
||||
* CommandGroups. Can also be subclassed to make a command with an internal timer.
|
||||
* A command that does nothing but takes a specified amount of time to finish.
|
||||
*
|
||||
* <p>This class is provided by the NewCommands VendorDep
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,10 @@ void Subsystem::SetDefaultCommand(CommandPtr&& defaultCommand) {
|
||||
std::move(defaultCommand));
|
||||
}
|
||||
|
||||
void Subsystem::RemoveDefaultCommand() {
|
||||
CommandScheduler::GetInstance().RemoveDefaultCommand(this);
|
||||
}
|
||||
|
||||
Command* Subsystem::GetDefaultCommand() const {
|
||||
return CommandScheduler::GetInstance().GetDefaultCommand(this);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@ class CommandBase : public Command,
|
||||
/**
|
||||
* Adds the specified Subsystem requirements to the command.
|
||||
*
|
||||
* The scheduler will prevent two commands that require the same subsystem
|
||||
* from being scheduled simultaneously.
|
||||
*
|
||||
* Note that the scheduler determines the requirements of a command when it
|
||||
* is scheduled, so this method should normally be called from the command's
|
||||
* constructor.
|
||||
*
|
||||
* @param requirements the Subsystem requirements to add
|
||||
*/
|
||||
void AddRequirements(std::initializer_list<Subsystem*> requirements);
|
||||
@@ -35,6 +42,13 @@ class CommandBase : public Command,
|
||||
/**
|
||||
* Adds the specified Subsystem requirements to the command.
|
||||
*
|
||||
* The scheduler will prevent two commands that require the same subsystem
|
||||
* from being scheduled simultaneously.
|
||||
*
|
||||
* Note that the scheduler determines the requirements of a command when it
|
||||
* is scheduled, so this method should normally be called from the command's
|
||||
* constructor.
|
||||
*
|
||||
* @param requirements the Subsystem requirements to add
|
||||
*/
|
||||
void AddRequirements(std::span<Subsystem* const> requirements);
|
||||
@@ -42,6 +56,13 @@ class CommandBase : public Command,
|
||||
/**
|
||||
* Adds the specified Subsystem requirements to the command.
|
||||
*
|
||||
* The scheduler will prevent two commands that require the same subsystem
|
||||
* from being scheduled simultaneously.
|
||||
*
|
||||
* Note that the scheduler determines the requirements of a command when it
|
||||
* is scheduled, so this method should normally be called from the command's
|
||||
* constructor.
|
||||
*
|
||||
* @param requirements the Subsystem requirements to add
|
||||
*/
|
||||
void AddRequirements(wpi::SmallSet<Subsystem*, 4> requirements);
|
||||
@@ -49,6 +70,13 @@ class CommandBase : public Command,
|
||||
/**
|
||||
* Adds the specified Subsystem requirement to the command.
|
||||
*
|
||||
* The scheduler will prevent two commands that require the same subsystem
|
||||
* from being scheduled simultaneously.
|
||||
*
|
||||
* Note that the scheduler determines the requirements of a command when it
|
||||
* is scheduled, so this method should normally be called from the command's
|
||||
* constructor.
|
||||
*
|
||||
* @param requirement the Subsystem requirement to add
|
||||
*/
|
||||
void AddRequirements(Subsystem* requirement);
|
||||
|
||||
@@ -83,6 +83,12 @@ class Subsystem {
|
||||
*/
|
||||
void SetDefaultCommand(CommandPtr&& defaultCommand);
|
||||
|
||||
/**
|
||||
* Removes the default command for the subsystem. This will not cancel the
|
||||
* default command if it is currently running.
|
||||
*/
|
||||
void RemoveDefaultCommand();
|
||||
|
||||
/**
|
||||
* Gets the default command for this subsystem. Returns null if no default
|
||||
* command is currently associated with the subsystem.
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
namespace frc2 {
|
||||
/**
|
||||
* A command that does nothing but takes a specified amount of time to finish.
|
||||
* Useful for CommandGroups. Can also be subclassed to make a command with an
|
||||
* internal timer.
|
||||
*
|
||||
* This class is provided by the NewCommands VendorDep
|
||||
*/
|
||||
|
||||
@@ -285,7 +285,7 @@ bool ADIS16448_IMU::SwitchToAutoSPI() {
|
||||
m_spi->SetAutoTransmitData({{GLOB_CMD}}, 27);
|
||||
// Configure auto stall time
|
||||
m_spi->ConfigureAutoStall(HAL_SPI_kMXP, 100, 1000, 255);
|
||||
// Kick off DMA SPI (Note: Device configration impossible after SPI DMA is
|
||||
// Kick off DMA SPI (Note: Device configuration impossible after SPI DMA is
|
||||
// activated)
|
||||
m_spi->StartAutoTrigger(*m_auto_interrupt, true, false);
|
||||
// Check to see if the acquire thread is running. If not, kick one off.
|
||||
|
||||
@@ -279,7 +279,7 @@ bool ADIS16470_IMU::SwitchToAutoSPI() {
|
||||
}
|
||||
// Configure auto stall time
|
||||
m_spi->ConfigureAutoStall(HAL_SPI_kOnboardCS0, 5, 1000, 1);
|
||||
// Kick off DMA SPI (Note: Device configration impossible after SPI DMA is
|
||||
// Kick off DMA SPI (Note: Device configuration impossible after SPI DMA is
|
||||
// activated) DR High = Data good (data capture should be triggered on the
|
||||
// rising edge)
|
||||
m_spi->StartAutoTrigger(*m_auto_interrupt, true, false);
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace {
|
||||
|
||||
struct Thread final : public wpi::SafeThread {
|
||||
Thread(std::string_view dir, std::string_view filename, double period);
|
||||
~Thread() override;
|
||||
|
||||
void Main() final;
|
||||
|
||||
@@ -94,6 +95,10 @@ Thread::Thread(std::string_view dir, std::string_view filename, double period)
|
||||
StartNTLog();
|
||||
}
|
||||
|
||||
Thread::~Thread() {
|
||||
StopNTLog();
|
||||
}
|
||||
|
||||
void Thread::Main() {
|
||||
// based on free disk space, scan for "old" FRC_*.wpilog files and remove
|
||||
{
|
||||
@@ -181,7 +186,7 @@ void Thread::Main() {
|
||||
} else {
|
||||
dsAttachCount = 0;
|
||||
}
|
||||
if (dsAttachCount > 50) { // 1 second
|
||||
if (dsAttachCount > 300) { // 6 seconds
|
||||
std::time_t now = std::time(nullptr);
|
||||
auto tm = std::gmtime(&now);
|
||||
if (tm->tm_year > 100) {
|
||||
@@ -202,7 +207,7 @@ void Thread::Main() {
|
||||
} else {
|
||||
fmsAttachCount = 0;
|
||||
}
|
||||
if (fmsAttachCount > 100) { // 2 seconds
|
||||
if (fmsAttachCount > 250) { // 5 seconds
|
||||
// match info comes through TCP, so we need to double-check we've
|
||||
// actually received it
|
||||
auto matchType = DriverStation::GetMatchType();
|
||||
|
||||
@@ -484,6 +484,12 @@ bool DriverStation::IsTest() {
|
||||
return controlWord.test;
|
||||
}
|
||||
|
||||
bool DriverStation::IsTestEnabled() {
|
||||
HAL_ControlWord controlWord;
|
||||
HAL_GetControlWord(&controlWord);
|
||||
return controlWord.test && controlWord.enabled;
|
||||
}
|
||||
|
||||
bool DriverStation::IsDSAttached() {
|
||||
HAL_ControlWord controlWord;
|
||||
HAL_GetControlWord(&controlWord);
|
||||
|
||||
@@ -95,7 +95,7 @@ void IterativeRobotBase::SetNetworkTablesFlushEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void IterativeRobotBase::EnableLiveWindowInTest(bool testLW) {
|
||||
if (IsTest()) {
|
||||
if (IsTestEnabled()) {
|
||||
throw FRC_MakeError(err::IncompatibleMode,
|
||||
"Can't configure test mode while in test mode!");
|
||||
}
|
||||
|
||||
@@ -161,9 +161,14 @@ void PneumaticHub::EnableCompressorAnalog(
|
||||
"maxPressure must be between 0 and 120 PSI, got {}",
|
||||
maxPressure);
|
||||
}
|
||||
int32_t status = 0;
|
||||
|
||||
// Send the voltage as it would be if the 5V rail was at exactly 5V.
|
||||
// The firmware will compensate for the real 5V rail voltage, which
|
||||
// can fluctuate somewhat over time.
|
||||
units::volt_t minAnalogVoltage = PSIToVolts(minPressure, 5_V);
|
||||
units::volt_t maxAnalogVoltage = PSIToVolts(maxPressure, 5_V);
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_SetREVPHClosedLoopControlAnalog(m_handle, minAnalogVoltage.value(),
|
||||
maxAnalogVoltage.value(), &status);
|
||||
FRC_ReportError(status, "Module {}", m_module);
|
||||
@@ -186,9 +191,14 @@ void PneumaticHub::EnableCompressorHybrid(
|
||||
"maxPressure must be between 0 and 120 PSI, got {}",
|
||||
maxPressure);
|
||||
}
|
||||
int32_t status = 0;
|
||||
|
||||
// Send the voltage as it would be if the 5V rail was at exactly 5V.
|
||||
// The firmware will compensate for the real 5V rail voltage, which
|
||||
// can fluctuate somewhat over time.
|
||||
units::volt_t minAnalogVoltage = PSIToVolts(minPressure, 5_V);
|
||||
units::volt_t maxAnalogVoltage = PSIToVolts(maxPressure, 5_V);
|
||||
|
||||
int32_t status = 0;
|
||||
HAL_SetREVPHClosedLoopControlHybrid(m_handle, minAnalogVoltage.value(),
|
||||
maxAnalogVoltage.value(), &status);
|
||||
FRC_ReportError(status, "Module {}", m_module);
|
||||
|
||||
@@ -20,12 +20,16 @@ struct ShuffleboardInstance::Impl {
|
||||
bool tabsChanged = false;
|
||||
std::shared_ptr<nt::NetworkTable> rootTable;
|
||||
std::shared_ptr<nt::NetworkTable> rootMetaTable;
|
||||
nt::StringPublisher selectedTabPub;
|
||||
};
|
||||
|
||||
ShuffleboardInstance::ShuffleboardInstance(nt::NetworkTableInstance ntInstance)
|
||||
: m_impl(new Impl) {
|
||||
m_impl->rootTable = ntInstance.GetTable(Shuffleboard::kBaseTableName);
|
||||
m_impl->rootMetaTable = m_impl->rootTable->GetSubTable(".metadata");
|
||||
m_impl->selectedTabPub =
|
||||
m_impl->rootMetaTable->GetStringTopic("Selected")
|
||||
.Publish(nt::PubSubOptions{.keepDuplicates = true});
|
||||
HAL_Report(HALUsageReporting::kResourceType_Shuffleboard, 0);
|
||||
}
|
||||
|
||||
@@ -75,9 +79,9 @@ void ShuffleboardInstance::DisableActuatorWidgets() {
|
||||
}
|
||||
|
||||
void ShuffleboardInstance::SelectTab(int index) {
|
||||
m_impl->rootMetaTable->GetEntry("Selected").SetDouble(index);
|
||||
m_impl->selectedTabPub.Set(std::to_string(index));
|
||||
}
|
||||
|
||||
void ShuffleboardInstance::SelectTab(std::string_view title) {
|
||||
m_impl->rootMetaTable->GetEntry("Selected").SetString(title);
|
||||
m_impl->selectedTabPub.Set(title);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
|
||||
using namespace frc::sim;
|
||||
|
||||
DutyCycleEncoderSim::DutyCycleEncoderSim(const frc::DutyCycleEncoder& encoder) {
|
||||
frc::sim::SimDeviceSim deviceSim{"DutyCycle:DutyCycleEncoder",
|
||||
encoder.GetSourceChannel()};
|
||||
DutyCycleEncoderSim::DutyCycleEncoderSim(const frc::DutyCycleEncoder& encoder)
|
||||
: DutyCycleEncoderSim{encoder.GetSourceChannel()} {}
|
||||
|
||||
DutyCycleEncoderSim::DutyCycleEncoderSim(int channel) {
|
||||
frc::sim::SimDeviceSim deviceSim{"DutyCycle:DutyCycleEncoder", channel};
|
||||
m_simPosition = deviceSim.GetDouble("position");
|
||||
m_simDistancePerRotation = deviceSim.GetDouble("distance_per_rot");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <hal/FRCUsageReporting.h>
|
||||
#include <hal/HALBase.h>
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpimath/MathShared.h>
|
||||
|
||||
#include "WPILibVersion.h"
|
||||
@@ -138,6 +139,10 @@ class WPILibMathShared : public wpi::math::MathShared {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
units::second_t GetTimestamp() override {
|
||||
return units::second_t{wpi::Now() * 1.0e-6};
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -205,6 +210,10 @@ bool RobotBase::IsTest() const {
|
||||
return DriverStation::IsTest();
|
||||
}
|
||||
|
||||
bool RobotBase::IsTestEnabled() const {
|
||||
return DriverStation::IsTestEnabled();
|
||||
}
|
||||
|
||||
std::thread::id RobotBase::GetThreadId() {
|
||||
return m_threadId;
|
||||
}
|
||||
@@ -220,7 +229,7 @@ RobotBase::RobotBase() {
|
||||
SetupMathShared();
|
||||
|
||||
auto inst = nt::NetworkTableInstance::GetDefault();
|
||||
// subscribe to "" to force persistent values to progagate to local
|
||||
// subscribe to "" to force persistent values to propagate to local
|
||||
nt::SubscribeMultiple(inst.GetHandle(), {{std::string_view{}}});
|
||||
#ifdef __FRC_ROBORIO__
|
||||
inst.StartServer("/home/lvuser/networktables.json");
|
||||
|
||||
@@ -30,7 +30,7 @@ class AnalogTrigger;
|
||||
* range defined by the limits.
|
||||
*
|
||||
* The RisingPulse and FallingPulse outputs detect an instantaneous transition
|
||||
* from above the upper limit to below the lower limit, and vise versa. These
|
||||
* from above the upper limit to below the lower limit, and vice versa. These
|
||||
* pulses represent a rollover condition of a sensor and can be routed to an up
|
||||
* / down counter or to interrupts. Because the outputs generate a pulse, they
|
||||
* cannot be read directly. To help ensure that a rollover condition is not
|
||||
|
||||
@@ -209,6 +209,14 @@ class DriverStation final {
|
||||
*/
|
||||
static bool IsTest();
|
||||
|
||||
/**
|
||||
* Check if the DS is commanding Test mode and if it has enabled the robot.
|
||||
*
|
||||
* @return True if the robot is being commanded to be in Test mode and
|
||||
* enabled.
|
||||
*/
|
||||
static bool IsTestEnabled();
|
||||
|
||||
/**
|
||||
* Check if the DS is attached.
|
||||
*
|
||||
|
||||
@@ -149,7 +149,7 @@ class PWM : public wpi::Sendable, public wpi::SendableHelper<PWM> {
|
||||
virtual double GetSpeed() const;
|
||||
|
||||
/**
|
||||
* Temporarily disables the PWM output. The next set call will reenable
|
||||
* Temporarily disables the PWM output. The next set call will re-enable
|
||||
* the output.
|
||||
*/
|
||||
virtual void SetDisabled();
|
||||
|
||||
@@ -46,9 +46,10 @@ class PneumaticHub : public PneumaticsBase {
|
||||
* and will turn off when the pressure reaches {@code maxPressure}.
|
||||
*
|
||||
* @param minPressure The minimum pressure. The compressor will turn on when
|
||||
* the pressure drops below this value.
|
||||
* the pressure drops below this value. Range 0 - 120 PSI.
|
||||
* @param maxPressure The maximum pressure. The compressor will turn off when
|
||||
* the pressure reaches this value.
|
||||
* the pressure reaches this value. Range 0 - 120 PSI. Must be larger then
|
||||
* minPressure.
|
||||
*/
|
||||
void EnableCompressorAnalog(
|
||||
units::pounds_per_square_inch_t minPressure,
|
||||
@@ -74,10 +75,11 @@ class PneumaticHub : public PneumaticsBase {
|
||||
*
|
||||
* @param minPressure The minimum pressure. The compressor will turn on when
|
||||
* the pressure drops below this value and the pressure switch indicates that
|
||||
* the system is not full.
|
||||
* the system is not full. Range 0 - 120 PSI.
|
||||
* @param maxPressure The maximum pressure. The compressor will turn off when
|
||||
* the pressure reaches this value or the pressure switch is disconnected or
|
||||
* indicates that the system is full.
|
||||
* indicates that the system is full. Range 0 - 120 PSI. Must be larger then
|
||||
* minPressure.
|
||||
*/
|
||||
void EnableCompressorHybrid(
|
||||
units::pounds_per_square_inch_t minPressure,
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace frc {
|
||||
/**
|
||||
* The Resource class is a convenient way to track allocated resources.
|
||||
*
|
||||
* It tracks them as indicies in the range [0 .. elements - 1]. E.g. the library
|
||||
* It tracks them as indices in the range [0 .. elements - 1]. E.g. the library
|
||||
* uses this to track hardware channel allocation.
|
||||
*
|
||||
* The Resource class does not allocate the hardware channels or other
|
||||
@@ -46,7 +46,7 @@ class Resource {
|
||||
* Allocate storage for a new instance of Resource.
|
||||
*
|
||||
* Allocate a bool array of values that will get initialized to indicate that
|
||||
* no resources have been allocated yet. The indicies of the resources are
|
||||
* no resources have been allocated yet. The indices of the resources are
|
||||
* [0 .. elements - 1].
|
||||
*/
|
||||
explicit Resource(uint32_t size);
|
||||
|
||||
@@ -129,14 +129,14 @@ class RobotBase {
|
||||
/**
|
||||
* Determine if the Robot is currently enabled.
|
||||
*
|
||||
* @return True if the Robot is currently enabled by the field controls.
|
||||
* @return True if the Robot is currently enabled by the Driver Station.
|
||||
*/
|
||||
bool IsEnabled() const;
|
||||
|
||||
/**
|
||||
* Determine if the Robot is currently disabled.
|
||||
*
|
||||
* @return True if the Robot is currently disabled by the field controls.
|
||||
* @return True if the Robot is currently disabled by the Driver Station.
|
||||
*/
|
||||
bool IsDisabled() const;
|
||||
|
||||
@@ -144,7 +144,7 @@ class RobotBase {
|
||||
* Determine if the robot is currently in Autonomous mode.
|
||||
*
|
||||
* @return True if the robot is currently operating Autonomously as determined
|
||||
* by the field controls.
|
||||
* by the Driver Station.
|
||||
*/
|
||||
bool IsAutonomous() const;
|
||||
|
||||
@@ -152,7 +152,7 @@ class RobotBase {
|
||||
* Determine if the robot is currently in Autonomous mode and enabled.
|
||||
*
|
||||
* @return True if the robot us currently operating Autonomously while enabled
|
||||
* as determined by the field controls.
|
||||
* as determined by the Driver Station.
|
||||
*/
|
||||
bool IsAutonomousEnabled() const;
|
||||
|
||||
@@ -160,7 +160,7 @@ class RobotBase {
|
||||
* Determine if the robot is currently in Operator Control mode.
|
||||
*
|
||||
* @return True if the robot is currently operating in Tele-Op mode as
|
||||
* determined by the field controls.
|
||||
* determined by the Driver Station.
|
||||
*/
|
||||
bool IsTeleop() const;
|
||||
|
||||
@@ -168,18 +168,26 @@ class RobotBase {
|
||||
* Determine if the robot is current in Operator Control mode and enabled.
|
||||
*
|
||||
* @return True if the robot is currently operating in Tele-Op mode while
|
||||
* wnabled as determined by the field-controls.
|
||||
* enabled as determined by the Driver Station.
|
||||
*/
|
||||
bool IsTeleopEnabled() const;
|
||||
|
||||
/**
|
||||
* Determine if the robot is currently in Test mode.
|
||||
*
|
||||
* @return True if the robot is currently running tests as determined by the
|
||||
* field controls.
|
||||
* @return True if the robot is currently running in Test mode as determined
|
||||
* by the Driver Station.
|
||||
*/
|
||||
bool IsTest() const;
|
||||
|
||||
/**
|
||||
* Determine if the robot is current in Test mode and enabled.
|
||||
*
|
||||
* @return True if the robot is currently operating in Test mode while
|
||||
* enabled as determined by the Driver Station.
|
||||
*/
|
||||
bool IsTestEnabled() const;
|
||||
|
||||
/**
|
||||
* Gets the ID of the main robot thread.
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace frc {
|
||||
* Driver for the RS-232 serial port on the roboRIO.
|
||||
*
|
||||
* The current implementation uses the VISA formatted I/O mode. This means that
|
||||
* all traffic goes through the fomatted buffers. This allows the intermingled
|
||||
* all traffic goes through the formatted buffers. This allows the intermingled
|
||||
* use of Printf(), Scanf(), and the raw buffer accessors Read() and Write().
|
||||
*
|
||||
* More information can be found in the NI-VISA User Manual here:
|
||||
|
||||
@@ -283,9 +283,9 @@ class XboxController : public GenericHID {
|
||||
BooleanEvent Y(EventLoop* loop) const;
|
||||
|
||||
/**
|
||||
* Whether the Y button was released since the last check.
|
||||
* Read the value of the back button on the controller.
|
||||
*
|
||||
* @return Whether the button was released since the last check.
|
||||
* @return The state of the button.
|
||||
*/
|
||||
bool GetBackButton() const;
|
||||
|
||||
|
||||
@@ -26,7 +26,14 @@ class DutyCycleEncoderSim {
|
||||
explicit DutyCycleEncoderSim(const DutyCycleEncoder& encoder);
|
||||
|
||||
/**
|
||||
* Set the position tin turns.
|
||||
* Constructs from a digital input channel.
|
||||
*
|
||||
* @param channel digital input channel
|
||||
*/
|
||||
explicit DutyCycleEncoderSim(int channel);
|
||||
|
||||
/**
|
||||
* Set the position in turns.
|
||||
*
|
||||
* @param turns The position.
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,10 @@ namespace frc {
|
||||
template <class T>
|
||||
class SendableChooser : public SendableChooserBase {
|
||||
wpi::StringMap<T> m_choices;
|
||||
static_assert(std::is_copy_constructible_v<T>,
|
||||
"T must be copy-constructible!");
|
||||
static_assert(std::is_default_constructible_v<T>,
|
||||
"T must be default-constructible!");
|
||||
|
||||
template <class U>
|
||||
static U _unwrap_smart_ptr(const U& value);
|
||||
|
||||
52
wpilibc/src/test/native/cpp/NotifierTest.cpp
Normal file
52
wpilibc/src/test/native/cpp/NotifierTest.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 <atomic>
|
||||
|
||||
#include "frc/Notifier.h"
|
||||
#include "frc/simulation/SimHooks.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace frc;
|
||||
|
||||
namespace {
|
||||
|
||||
class NotifierTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
sim::PauseTiming();
|
||||
sim::RestartTiming();
|
||||
}
|
||||
|
||||
void TearDown() override { sim::ResumeTiming(); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(NotifierTest, StartPeriodicAndStop) {
|
||||
std::atomic<uint32_t> counter{0};
|
||||
|
||||
Notifier notifier{[&] { ++counter; }};
|
||||
notifier.StartPeriodic(1_s);
|
||||
|
||||
sim::StepTiming(10.5_s);
|
||||
|
||||
notifier.Stop();
|
||||
EXPECT_EQ(10u, counter);
|
||||
|
||||
sim::StepTiming(3_s);
|
||||
|
||||
EXPECT_EQ(10u, counter);
|
||||
}
|
||||
|
||||
TEST_F(NotifierTest, StartSingle) {
|
||||
std::atomic<uint32_t> counter{0};
|
||||
|
||||
Notifier notifier{[&] { ++counter; }};
|
||||
notifier.StartSingle(1_s);
|
||||
|
||||
sim::StepTiming(10.5_s);
|
||||
|
||||
EXPECT_EQ(1u, counter);
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
using namespace frc;
|
||||
|
||||
inline constexpr auto kPeriod = 20_ms;
|
||||
|
||||
namespace {
|
||||
class TimedRobotTest : public ::testing::TestWithParam<bool> {
|
||||
protected:
|
||||
@@ -45,6 +47,8 @@ class MockRobot : public TimedRobot {
|
||||
std::atomic<uint32_t> m_teleopPeriodicCount{0};
|
||||
std::atomic<uint32_t> m_testPeriodicCount{0};
|
||||
|
||||
MockRobot() : TimedRobot{kPeriod} {}
|
||||
|
||||
void RobotInit() override { m_robotInitCount++; }
|
||||
|
||||
void SimulationInit() override { m_simulationInitCount++; }
|
||||
@@ -107,7 +111,7 @@ TEST_F(TimedRobotTest, DisabledMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -128,7 +132,7 @@ TEST_F(TimedRobotTest, DisabledMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -183,7 +187,7 @@ TEST_F(TimedRobotTest, AutonomousMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -204,7 +208,7 @@ TEST_F(TimedRobotTest, AutonomousMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -259,7 +263,7 @@ TEST_F(TimedRobotTest, TeleopMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -280,7 +284,7 @@ TEST_F(TimedRobotTest, TeleopMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -339,7 +343,7 @@ TEST_P(TimedRobotTest, TestMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -363,7 +367,7 @@ TEST_P(TimedRobotTest, TestMode) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_robotInitCount);
|
||||
EXPECT_EQ(1u, robot.m_simulationInitCount);
|
||||
@@ -436,7 +440,7 @@ TEST_F(TimedRobotTest, ModeChange) {
|
||||
EXPECT_EQ(0u, robot.m_teleopExitCount);
|
||||
EXPECT_EQ(0u, robot.m_testExitCount);
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(0u, robot.m_autonomousInitCount);
|
||||
@@ -454,7 +458,7 @@ TEST_F(TimedRobotTest, ModeChange) {
|
||||
frc::sim::DriverStationSim::SetTest(false);
|
||||
frc::sim::DriverStationSim::NotifyNewData();
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_autonomousInitCount);
|
||||
@@ -472,7 +476,7 @@ TEST_F(TimedRobotTest, ModeChange) {
|
||||
frc::sim::DriverStationSim::SetTest(false);
|
||||
frc::sim::DriverStationSim::NotifyNewData();
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_autonomousInitCount);
|
||||
@@ -490,7 +494,7 @@ TEST_F(TimedRobotTest, ModeChange) {
|
||||
frc::sim::DriverStationSim::SetTest(true);
|
||||
frc::sim::DriverStationSim::NotifyNewData();
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_autonomousInitCount);
|
||||
@@ -508,7 +512,7 @@ TEST_F(TimedRobotTest, ModeChange) {
|
||||
frc::sim::DriverStationSim::SetTest(false);
|
||||
frc::sim::DriverStationSim::NotifyNewData();
|
||||
|
||||
frc::sim::StepTiming(20_ms);
|
||||
frc::sim::StepTiming(kPeriod);
|
||||
|
||||
EXPECT_EQ(2u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_autonomousInitCount);
|
||||
@@ -528,7 +532,7 @@ TEST_F(TimedRobotTest, AddPeriodic) {
|
||||
MockRobot robot;
|
||||
|
||||
std::atomic<uint32_t> callbackCount{0};
|
||||
robot.AddPeriodic([&] { callbackCount++; }, 10_ms);
|
||||
robot.AddPeriodic([&] { callbackCount++; }, kPeriod / 2.0);
|
||||
|
||||
std::thread robotThread{[&] { robot.StartCompetition(); }};
|
||||
|
||||
@@ -540,13 +544,13 @@ TEST_F(TimedRobotTest, AddPeriodic) {
|
||||
EXPECT_EQ(0u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(0u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(10_ms);
|
||||
frc::sim::StepTiming(kPeriod / 2.0);
|
||||
|
||||
EXPECT_EQ(0u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(0u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(1u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(10_ms);
|
||||
frc::sim::StepTiming(kPeriod / 2.0);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_disabledPeriodicCount);
|
||||
@@ -560,14 +564,14 @@ TEST_F(TimedRobotTest, AddPeriodicWithOffset) {
|
||||
MockRobot robot;
|
||||
|
||||
std::atomic<uint32_t> callbackCount{0};
|
||||
robot.AddPeriodic([&] { callbackCount++; }, 10_ms, 5_ms);
|
||||
robot.AddPeriodic([&] { callbackCount++; }, kPeriod / 2.0, kPeriod / 4.0);
|
||||
|
||||
// Expirations in this test (ms)
|
||||
//
|
||||
// Robot | Callback
|
||||
// ================
|
||||
// 20 | 15
|
||||
// 40 | 25
|
||||
// p | 0.75p
|
||||
// 2p | 1.25p
|
||||
|
||||
std::thread robotThread{[&] { robot.StartCompetition(); }};
|
||||
|
||||
@@ -579,25 +583,25 @@ TEST_F(TimedRobotTest, AddPeriodicWithOffset) {
|
||||
EXPECT_EQ(0u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(0u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(7.5_ms);
|
||||
frc::sim::StepTiming(kPeriod * 3.0 / 8.0);
|
||||
|
||||
EXPECT_EQ(0u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(0u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(0u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(7.5_ms);
|
||||
frc::sim::StepTiming(kPeriod * 3.0 / 8.0);
|
||||
|
||||
EXPECT_EQ(0u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(0u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(1u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(5_ms);
|
||||
frc::sim::StepTiming(kPeriod / 4.0);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_disabledPeriodicCount);
|
||||
EXPECT_EQ(1u, callbackCount);
|
||||
|
||||
frc::sim::StepTiming(5_ms);
|
||||
frc::sim::StepTiming(kPeriod / 4.0);
|
||||
|
||||
EXPECT_EQ(1u, robot.m_disabledInitCount);
|
||||
EXPECT_EQ(1u, robot.m_disabledPeriodicCount);
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <networktables/NetworkTableInstance.h>
|
||||
#include <networktables/NetworkTableListener.h>
|
||||
#include <networktables/StringTopic.h>
|
||||
|
||||
#include "frc/shuffleboard/ShuffleboardInstance.h"
|
||||
#include "gtest/gtest.h"
|
||||
@@ -106,3 +108,22 @@ TEST(ShuffleboardInstanceTest, NestedActuatorWidgetsAreDisabled) {
|
||||
EXPECT_FALSE(controllable)
|
||||
<< "The nested actuator widget should have been disabled";
|
||||
}
|
||||
|
||||
TEST(ShuffleboardInstanceTest, DuplicateSelectTabs) {
|
||||
NTWrapper ntInst;
|
||||
frc::detail::ShuffleboardInstance shuffleboardInst{ntInst.inst};
|
||||
std::atomic_int counter = 0;
|
||||
auto listener = nt::NetworkTableListener::CreateListener(
|
||||
ntInst.inst.GetStringTopic("/Shuffleboard/.metadata/Selected"),
|
||||
nt::EventFlags::kValueAll | nt::EventFlags::kImmediate,
|
||||
[&counter](auto& event) { counter++; });
|
||||
|
||||
// There shouldn't be anything there
|
||||
EXPECT_EQ(0, counter);
|
||||
|
||||
shuffleboardInst.SelectTab("tab1");
|
||||
shuffleboardInst.SelectTab("tab1");
|
||||
EXPECT_TRUE(ntInst.inst.WaitForListenerQueue(0.005))
|
||||
<< "Listener queue timed out!";
|
||||
EXPECT_EQ(2, counter);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ TEST(ElevatorSimTest, MinMax) {
|
||||
|
||||
TEST(ElevatorSimTest, Stability) {
|
||||
frc::sim::ElevatorSim sim{
|
||||
frc::DCMotor::Vex775Pro(4), 100, 4_kg, 0.5_in, 0_m, 10_m, true};
|
||||
frc::DCMotor::Vex775Pro(4), 100, 4_kg, 0.5_in, 0_m, 10_m, false};
|
||||
|
||||
sim.SetState(frc::Vectord<2>{0.0, 0.0});
|
||||
sim.SetInput(frc::Vectord<1>{12.0});
|
||||
|
||||
@@ -2,150 +2,30 @@
|
||||
// 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 <numbers>
|
||||
#include "Robot.h"
|
||||
|
||||
#include <frc/Encoder.h>
|
||||
#include <frc/Joystick.h>
|
||||
#include <frc/Preferences.h>
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
#include <frc/controller/PIDController.h>
|
||||
#include <frc/motorcontrol/PWMSparkMax.h>
|
||||
#include <frc/simulation/BatterySim.h>
|
||||
#include <frc/simulation/EncoderSim.h>
|
||||
#include <frc/simulation/RoboRioSim.h>
|
||||
#include <frc/simulation/SingleJointedArmSim.h>
|
||||
#include <frc/smartdashboard/Mechanism2d.h>
|
||||
#include <frc/smartdashboard/MechanismLigament2d.h>
|
||||
#include <frc/smartdashboard/MechanismRoot2d.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
#include <frc/system/plant/LinearSystemId.h>
|
||||
#include <frc/util/Color.h>
|
||||
#include <frc/util/Color8Bit.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/moment_of_inertia.h>
|
||||
void Robot::SimulationPeriodic() {
|
||||
m_arm.SimulationPeriodic();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a sample program to demonstrate how to use a state-space controller
|
||||
* to control an arm.
|
||||
*/
|
||||
class Robot : public frc::TimedRobot {
|
||||
static constexpr int kMotorPort = 0;
|
||||
static constexpr int kEncoderAChannel = 0;
|
||||
static constexpr int kEncoderBChannel = 1;
|
||||
static constexpr int kJoystickPort = 0;
|
||||
void Robot::TeleopInit() {
|
||||
m_arm.LoadPreferences();
|
||||
}
|
||||
|
||||
static constexpr std::string_view kArmPositionKey = "ArmPosition";
|
||||
static constexpr std::string_view kArmPKey = "ArmP";
|
||||
|
||||
// The P gain for the PID controller that drives this arm.
|
||||
double kArmKp = 50.0;
|
||||
|
||||
units::degree_t armPosition = 75.0_deg;
|
||||
|
||||
// distance per pulse = (angle per revolution) / (pulses per revolution)
|
||||
// = (2 * PI rads) / (4096 pulses)
|
||||
static constexpr double kArmEncoderDistPerPulse =
|
||||
2.0 * std::numbers::pi / 4096.0;
|
||||
|
||||
// The arm gearbox represents a gearbox containing two Vex 775pro motors.
|
||||
frc::DCMotor m_armGearbox = frc::DCMotor::Vex775Pro(2);
|
||||
|
||||
// Standard classes for controlling our arm
|
||||
frc2::PIDController m_controller{kArmKp, 0, 0};
|
||||
frc::Encoder m_encoder{kEncoderAChannel, kEncoderBChannel};
|
||||
frc::PWMSparkMax m_motor{kMotorPort};
|
||||
frc::Joystick m_joystick{kJoystickPort};
|
||||
|
||||
// Simulation classes help us simulate what's going on, including gravity.
|
||||
// This sim represents an arm with 2 775s, a 600:1 reduction, a mass of 5kg,
|
||||
// 30in overall arm length, range of motion in [-75, 255] degrees, and noise
|
||||
// with a standard deviation of 1 encoder tick.
|
||||
frc::sim::SingleJointedArmSim m_armSim{
|
||||
m_armGearbox,
|
||||
200.0,
|
||||
frc::sim::SingleJointedArmSim::EstimateMOI(30_in, 8_kg),
|
||||
30_in,
|
||||
-75_deg,
|
||||
255_deg,
|
||||
true,
|
||||
{kArmEncoderDistPerPulse}};
|
||||
frc::sim::EncoderSim m_encoderSim{m_encoder};
|
||||
|
||||
// Create a Mechanism2d display of an Arm
|
||||
frc::Mechanism2d m_mech2d{60, 60};
|
||||
frc::MechanismRoot2d* m_armBase = m_mech2d.GetRoot("ArmBase", 30, 30);
|
||||
frc::MechanismLigament2d* m_armTower =
|
||||
m_armBase->Append<frc::MechanismLigament2d>(
|
||||
"Arm Tower", 30, -90_deg, 6, frc::Color8Bit{frc::Color::kBlue});
|
||||
frc::MechanismLigament2d* m_arm = m_armBase->Append<frc::MechanismLigament2d>(
|
||||
"Arm", 30, m_armSim.GetAngle(), 6, frc::Color8Bit{frc::Color::kYellow});
|
||||
|
||||
public:
|
||||
void RobotInit() override {
|
||||
m_encoder.SetDistancePerPulse(kArmEncoderDistPerPulse);
|
||||
|
||||
// Put Mechanism 2d to SmartDashboard
|
||||
frc::SmartDashboard::PutData("Arm Sim", &m_mech2d);
|
||||
|
||||
// Set the Arm position setpoint and P constant to Preferences if the keys
|
||||
// don't already exist
|
||||
if (!frc::Preferences::ContainsKey(kArmPositionKey)) {
|
||||
frc::Preferences::SetDouble(kArmPositionKey, armPosition.value());
|
||||
}
|
||||
if (!frc::Preferences::ContainsKey(kArmPKey)) {
|
||||
frc::Preferences::SetDouble(kArmPKey, kArmKp);
|
||||
}
|
||||
void Robot::TeleopPeriodic() {
|
||||
if (m_joystick.GetTrigger()) {
|
||||
// Here, we run PID control like normal.
|
||||
m_arm.ReachSetpoint();
|
||||
} else {
|
||||
// Otherwise, we disable the motor.
|
||||
m_arm.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SimulationPeriodic() override {
|
||||
// In this method, we update our simulation of what our arm is doing
|
||||
// First, we set our "inputs" (voltages)
|
||||
m_armSim.SetInput(frc::Vectord<1>{m_motor.Get() *
|
||||
frc::RobotController::GetInputVoltage()});
|
||||
|
||||
// Next, we update it. The standard loop time is 20ms.
|
||||
m_armSim.Update(20_ms);
|
||||
|
||||
// Finally, we set our simulated encoder's readings and simulated battery
|
||||
// voltage
|
||||
m_encoderSim.SetDistance(m_armSim.GetAngle().value());
|
||||
// SimBattery estimates loaded battery voltages
|
||||
frc::sim::RoboRioSim::SetVInVoltage(
|
||||
frc::sim::BatterySim::Calculate({m_armSim.GetCurrentDraw()}));
|
||||
|
||||
// Update the Mechanism Arm angle based on the simulated arm angle
|
||||
m_arm->SetAngle(m_armSim.GetAngle());
|
||||
}
|
||||
|
||||
void TeleopInit() override {
|
||||
// Read Preferences for Arm setpoint and kP on entering Teleop
|
||||
armPosition = units::degree_t{
|
||||
frc::Preferences::GetDouble(kArmPositionKey, armPosition.value())};
|
||||
if (kArmKp != frc::Preferences::GetDouble(kArmPKey, kArmKp)) {
|
||||
kArmKp = frc::Preferences::GetDouble(kArmPKey, kArmKp);
|
||||
m_controller.SetP(kArmKp);
|
||||
}
|
||||
}
|
||||
|
||||
void TeleopPeriodic() override {
|
||||
if (m_joystick.GetTrigger()) {
|
||||
// Here, we run PID control like normal, with a setpoint read from
|
||||
// preferences in degrees.
|
||||
double pidOutput = m_controller.Calculate(
|
||||
m_encoder.GetDistance(), (units::radian_t{armPosition}.value()));
|
||||
m_motor.SetVoltage(units::volt_t{pidOutput});
|
||||
} else {
|
||||
// Otherwise, we disable the motor.
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void DisabledInit() override {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
};
|
||||
void Robot::DisabledInit() {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_arm.Stop();
|
||||
}
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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 "subsystems/Arm.h"
|
||||
|
||||
#include <frc/Preferences.h>
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/StateSpaceUtil.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
|
||||
Arm::Arm() {
|
||||
m_encoder.SetDistancePerPulse(kArmEncoderDistPerPulse);
|
||||
|
||||
// Put Mechanism 2d to SmartDashboard
|
||||
frc::SmartDashboard::PutData("Arm Sim", &m_mech2d);
|
||||
|
||||
// Set the Arm position setpoint and P constant to Preferences if the keys
|
||||
// don't already exist
|
||||
frc::Preferences::InitDouble(kArmPositionKey, m_armSetpoint.value());
|
||||
frc::Preferences::InitDouble(kArmPKey, m_armKp);
|
||||
}
|
||||
|
||||
void Arm::SimulationPeriodic() {
|
||||
// In this method, we update our simulation of what our arm is doing
|
||||
// First, we set our "inputs" (voltages)
|
||||
m_armSim.SetInput(
|
||||
frc::Vectord<1>{m_motor.Get() * frc::RobotController::GetInputVoltage()});
|
||||
|
||||
// Next, we update it. The standard loop time is 20ms.
|
||||
m_armSim.Update(20_ms);
|
||||
|
||||
// Finally, we set our simulated encoder's readings and simulated battery
|
||||
// voltage
|
||||
m_encoderSim.SetDistance(m_armSim.GetAngle().value());
|
||||
// SimBattery estimates loaded battery voltages
|
||||
frc::sim::RoboRioSim::SetVInVoltage(
|
||||
frc::sim::BatterySim::Calculate({m_armSim.GetCurrentDraw()}));
|
||||
|
||||
// Update the Mechanism Arm angle based on the simulated arm angle
|
||||
m_arm->SetAngle(m_armSim.GetAngle());
|
||||
}
|
||||
|
||||
void Arm::LoadPreferences() {
|
||||
// Read Preferences for Arm setpoint and kP on entering Teleop
|
||||
m_armSetpoint = units::degree_t{
|
||||
frc::Preferences::GetDouble(kArmPositionKey, m_armSetpoint.value())};
|
||||
if (m_armKp != frc::Preferences::GetDouble(kArmPKey, m_armKp)) {
|
||||
m_armKp = frc::Preferences::GetDouble(kArmPKey, m_armKp);
|
||||
m_controller.SetP(m_armKp);
|
||||
}
|
||||
}
|
||||
|
||||
void Arm::ReachSetpoint() {
|
||||
// Here, we run PID control like normal, with a setpoint read from
|
||||
// preferences in degrees.
|
||||
double pidOutput = m_controller.Calculate(
|
||||
m_encoder.GetDistance(), (units::radian_t{m_armSetpoint}.value()));
|
||||
m_motor.SetVoltage(units::volt_t{pidOutput});
|
||||
}
|
||||
|
||||
void Arm::Stop() {
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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 <numbers>
|
||||
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/mass.h>
|
||||
#include <units/time.h>
|
||||
#include <units/velocity.h>
|
||||
#include <units/voltage.h>
|
||||
|
||||
/**
|
||||
* The Constants header provides a convenient place for teams to hold robot-wide
|
||||
* numerical or bool constants. This should not be used for any other purpose.
|
||||
*
|
||||
* It is generally a good idea to place constants into subsystem- or
|
||||
* command-specific namespaces within this header, which can then be used where
|
||||
* they are needed.
|
||||
*/
|
||||
|
||||
static constexpr int kMotorPort = 0;
|
||||
static constexpr int kEncoderAChannel = 0;
|
||||
static constexpr int kEncoderBChannel = 1;
|
||||
static constexpr int kJoystickPort = 0;
|
||||
|
||||
static constexpr std::string_view kArmPositionKey = "ArmPosition";
|
||||
static constexpr std::string_view kArmPKey = "ArmP";
|
||||
|
||||
static constexpr double kDefaultArmKp = 50.0;
|
||||
static constexpr units::degree_t kDefaultArmSetpoint = 75.0_deg;
|
||||
|
||||
static constexpr units::radian_t kMinAngle = -75.0_deg;
|
||||
static constexpr units::radian_t kMaxAngle = 255.0_deg;
|
||||
|
||||
static constexpr double kArmReduction = 200.0;
|
||||
static constexpr units::kilogram_t kArmMass = 8.0_kg;
|
||||
static constexpr units::meter_t kArmLength = 30.0_in;
|
||||
|
||||
// distance per pulse = (angle per revolution) / (pulses per revolution)
|
||||
// = (2 * PI rads) / (4096 pulses)
|
||||
static constexpr double kArmEncoderDistPerPulse =
|
||||
2.0 * std::numbers::pi / 4096.0;
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 <frc/Joystick.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
|
||||
#include "subsystems/Arm.h"
|
||||
|
||||
/**
|
||||
* This is a sample program to demonstrate the use of arm simulation.
|
||||
*/
|
||||
class Robot : public frc::TimedRobot {
|
||||
public:
|
||||
void RobotInit() override {}
|
||||
void SimulationPeriodic() override;
|
||||
void TeleopInit() override;
|
||||
void TeleopPeriodic() override;
|
||||
void DisabledInit() override;
|
||||
|
||||
private:
|
||||
frc::Joystick m_joystick{kJoystickPort};
|
||||
Arm m_arm;
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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 <frc/Encoder.h>
|
||||
#include <frc/controller/ArmFeedforward.h>
|
||||
#include <frc/controller/PIDController.h>
|
||||
#include <frc/motorcontrol/PWMSparkMax.h>
|
||||
#include <frc/simulation/BatterySim.h>
|
||||
#include <frc/simulation/EncoderSim.h>
|
||||
#include <frc/simulation/PWMSim.h>
|
||||
#include <frc/simulation/RoboRioSim.h>
|
||||
#include <frc/simulation/SingleJointedArmSim.h>
|
||||
#include <frc/smartdashboard/Mechanism2d.h>
|
||||
#include <frc/smartdashboard/MechanismLigament2d.h>
|
||||
#include <frc/smartdashboard/MechanismRoot2d.h>
|
||||
#include <units/length.h>
|
||||
|
||||
#include "Constants.h"
|
||||
|
||||
class Arm {
|
||||
public:
|
||||
Arm();
|
||||
void SimulationPeriodic();
|
||||
void LoadPreferences();
|
||||
void ReachSetpoint();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
// The P gain for the PID controller that drives this arm.
|
||||
double m_armKp = kDefaultArmKp;
|
||||
units::degree_t m_armSetpoint = kDefaultArmSetpoint;
|
||||
|
||||
// The arm gearbox represents a gearbox containing two Vex 775pro motors.
|
||||
frc::DCMotor m_armGearbox = frc::DCMotor::Vex775Pro(2);
|
||||
|
||||
// Standard classes for controlling our arm
|
||||
frc2::PIDController m_controller{m_armKp, 0, 0};
|
||||
frc::Encoder m_encoder{kEncoderAChannel, kEncoderBChannel};
|
||||
frc::PWMSparkMax m_motor{kMotorPort};
|
||||
|
||||
// Simulation classes help us simulate what's going on, including gravity.
|
||||
// This sim represents an arm with 2 775s, a 600:1 reduction, a mass of 5kg,
|
||||
// 30in overall arm length, range of motion in [-75, 255] degrees, and noise
|
||||
// with a standard deviation of 1 encoder tick.
|
||||
frc::sim::SingleJointedArmSim m_armSim{
|
||||
m_armGearbox,
|
||||
kArmReduction,
|
||||
frc::sim::SingleJointedArmSim::EstimateMOI(kArmLength, kArmMass),
|
||||
kArmLength,
|
||||
kMinAngle,
|
||||
kMaxAngle,
|
||||
true,
|
||||
{kArmEncoderDistPerPulse}};
|
||||
frc::sim::EncoderSim m_encoderSim{m_encoder};
|
||||
|
||||
// Create a Mechanism2d display of an Arm
|
||||
frc::Mechanism2d m_mech2d{60, 60};
|
||||
frc::MechanismRoot2d* m_armBase = m_mech2d.GetRoot("ArmBase", 30, 30);
|
||||
frc::MechanismLigament2d* m_armTower =
|
||||
m_armBase->Append<frc::MechanismLigament2d>(
|
||||
"Arm Tower", 30, -90_deg, 6, frc::Color8Bit{frc::Color::kBlue});
|
||||
frc::MechanismLigament2d* m_arm = m_armBase->Append<frc::MechanismLigament2d>(
|
||||
"Arm", 30, m_armSim.GetAngle(), 6, frc::Color8Bit{frc::Color::kYellow});
|
||||
};
|
||||
@@ -2,152 +2,35 @@
|
||||
// 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 <numbers>
|
||||
#include "Robot.h"
|
||||
|
||||
#include <frc/Encoder.h>
|
||||
#include <frc/Joystick.h>
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/StateSpaceUtil.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
#include <frc/controller/ElevatorFeedforward.h>
|
||||
#include <frc/controller/PIDController.h>
|
||||
#include <frc/controller/ProfiledPIDController.h>
|
||||
#include <frc/motorcontrol/PWMSparkMax.h>
|
||||
#include <frc/simulation/BatterySim.h>
|
||||
#include <frc/simulation/ElevatorSim.h>
|
||||
#include <frc/simulation/EncoderSim.h>
|
||||
#include <frc/simulation/RoboRioSim.h>
|
||||
#include <frc/smartdashboard/Mechanism2d.h>
|
||||
#include <frc/smartdashboard/MechanismLigament2d.h>
|
||||
#include <frc/smartdashboard/MechanismRoot2d.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
#include <frc/system/plant/LinearSystemId.h>
|
||||
#include <frc/util/Color.h>
|
||||
#include <frc/util/Color8Bit.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/moment_of_inertia.h>
|
||||
#include "Constants.h"
|
||||
|
||||
/**
|
||||
* This is a sample program to demonstrate the use of elevator simulation.
|
||||
*/
|
||||
class Robot : public frc::TimedRobot {
|
||||
static constexpr int kMotorPort = 0;
|
||||
static constexpr int kEncoderAChannel = 0;
|
||||
static constexpr int kEncoderBChannel = 1;
|
||||
static constexpr int kJoystickPort = 0;
|
||||
void Robot::RobotPeriodic() {
|
||||
// Update the telemetry, including mechanism visualization, regardless of
|
||||
// mode.
|
||||
m_elevator.UpdateTelemetry();
|
||||
}
|
||||
|
||||
static constexpr double kElevatorKp = 5.0;
|
||||
static constexpr double kElevatorKi = 0.0;
|
||||
static constexpr double kElevatorKd = 0.0;
|
||||
void Robot::SimulationPeriodic() {
|
||||
// Update the simulation model.
|
||||
m_elevator.SimulationPeriodic();
|
||||
}
|
||||
|
||||
static constexpr units::volt_t kElevatorkS = 0.0_V;
|
||||
static constexpr units::volt_t kElevatorkG = 0.0_V;
|
||||
static constexpr auto kElevatorkV = 0.762_V / 1_mps;
|
||||
static constexpr auto kElevatorkA = 0.762_V / 1_mps_sq;
|
||||
|
||||
static constexpr double kElevatorGearing = 10.0;
|
||||
static constexpr units::meter_t kElevatorDrumRadius = 2_in;
|
||||
static constexpr units::kilogram_t kCarriageMass = 4.0_kg;
|
||||
|
||||
static constexpr units::meter_t kSetpoint = 30_in;
|
||||
static constexpr units::meter_t kMinElevatorHeight = 2_in;
|
||||
static constexpr units::meter_t kMaxElevatorHeight = 50_in;
|
||||
|
||||
// distance per pulse = (distance per revolution) / (pulses per revolution)
|
||||
// = (Pi * D) / ppr
|
||||
static constexpr double kArmEncoderDistPerPulse =
|
||||
2.0 * std::numbers::pi * kElevatorDrumRadius.value() / 4096.0;
|
||||
|
||||
// This gearbox represents a gearbox containing 4 Vex 775pro motors.
|
||||
frc::DCMotor m_elevatorGearbox = frc::DCMotor::Vex775Pro(4);
|
||||
|
||||
// Standard classes for controlling our elevator
|
||||
frc::TrapezoidProfile<units::meters>::Constraints m_constraints{2.45_mps,
|
||||
2.45_mps_sq};
|
||||
frc::ProfiledPIDController<units::meters> m_controller{
|
||||
kElevatorKp, kElevatorKi, kElevatorKd, m_constraints};
|
||||
|
||||
frc::ElevatorFeedforward m_feedforward{kElevatorkS, kElevatorkG, kElevatorkV,
|
||||
kElevatorkA};
|
||||
frc::Encoder m_encoder{kEncoderAChannel, kEncoderBChannel};
|
||||
frc::PWMSparkMax m_motor{kMotorPort};
|
||||
frc::Joystick m_joystick{kJoystickPort};
|
||||
|
||||
// Simulation classes help us simulate what's going on, including gravity.
|
||||
frc::sim::ElevatorSim m_elevatorSim{m_elevatorGearbox,
|
||||
kElevatorGearing,
|
||||
kCarriageMass,
|
||||
kElevatorDrumRadius,
|
||||
kMinElevatorHeight,
|
||||
kMaxElevatorHeight,
|
||||
true,
|
||||
{0.01}};
|
||||
frc::sim::EncoderSim m_encoderSim{m_encoder};
|
||||
|
||||
// Create a Mechanism2d display of an elevator
|
||||
frc::Mechanism2d m_mech2d{20, 50};
|
||||
frc::MechanismRoot2d* m_elevatorRoot =
|
||||
m_mech2d.GetRoot("Elevator Root", 10, 0);
|
||||
frc::MechanismLigament2d* m_elevatorMech2d =
|
||||
m_elevatorRoot->Append<frc::MechanismLigament2d>(
|
||||
"Elevator", units::inch_t{m_elevatorSim.GetPosition()}.value(),
|
||||
90_deg);
|
||||
|
||||
public:
|
||||
void RobotInit() override {
|
||||
m_encoder.SetDistancePerPulse(kArmEncoderDistPerPulse);
|
||||
|
||||
// Put Mechanism 2d to SmartDashboard
|
||||
// To view the Elevator Sim in the simulator, select Network Tables ->
|
||||
// SmartDashboard -> Elevator Sim
|
||||
frc::SmartDashboard::PutData("Elevator Sim", &m_mech2d);
|
||||
void Robot::TeleopPeriodic() {
|
||||
if (m_joystick.GetTrigger()) {
|
||||
// Here, we set the constant setpoint of 0.75 meters.
|
||||
m_elevator.ReachGoal(Constants::kSetpoint);
|
||||
} else {
|
||||
// Otherwise, we update the setpoint to 0.
|
||||
m_elevator.ReachGoal(0.0_m);
|
||||
}
|
||||
}
|
||||
|
||||
void SimulationPeriodic() override {
|
||||
// In this method, we update our simulation of what our elevator is doing
|
||||
// First, we set our "inputs" (voltages)
|
||||
m_elevatorSim.SetInput(frc::Vectord<1>{
|
||||
m_motor.Get() * frc::RobotController::GetInputVoltage()});
|
||||
|
||||
// Next, we update it. The standard loop time is 20ms.
|
||||
m_elevatorSim.Update(20_ms);
|
||||
|
||||
// Finally, we set our simulated encoder's readings and simulated battery
|
||||
// voltage
|
||||
m_encoderSim.SetDistance(m_elevatorSim.GetPosition().value());
|
||||
// SimBattery estimates loaded battery voltages
|
||||
frc::sim::RoboRioSim::SetVInVoltage(
|
||||
frc::sim::BatterySim::Calculate({m_elevatorSim.GetCurrentDraw()}));
|
||||
|
||||
// Update the Elevator length based on the simulated elevator height
|
||||
m_elevatorMech2d->SetLength(
|
||||
units::inch_t{m_elevatorSim.GetPosition()}.value());
|
||||
}
|
||||
|
||||
void TeleopPeriodic() override {
|
||||
if (m_joystick.GetTrigger()) {
|
||||
// Here, we set the constant setpoint of 30in.
|
||||
m_controller.SetGoal(kSetpoint);
|
||||
} else {
|
||||
// Otherwise, we update the setpoint to 0.
|
||||
m_controller.SetGoal(0.0_m);
|
||||
}
|
||||
// With the setpoint value we run PID control like normal
|
||||
double pidOutput =
|
||||
m_controller.Calculate(units::meter_t{m_encoder.GetDistance()});
|
||||
units::volt_t feedforwardOutput =
|
||||
m_feedforward.Calculate(m_controller.GetSetpoint().velocity);
|
||||
m_motor.SetVoltage(units::volt_t{pidOutput} + feedforwardOutput);
|
||||
}
|
||||
// To view the Elevator in the simulator, select Network Tables ->
|
||||
// SmartDashboard -> Elevator Sim
|
||||
|
||||
void DisabledInit() override {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
};
|
||||
void Robot::DisabledInit() {
|
||||
// This just makes sure that our simulation code knows that the motor's off.
|
||||
m_elevator.Stop();
|
||||
}
|
||||
|
||||
#ifndef RUNNING_FRC_TESTS
|
||||
int main() {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 "subsystems/Elevator.h"
|
||||
|
||||
#include <frc/RobotController.h>
|
||||
#include <frc/StateSpaceUtil.h>
|
||||
#include <frc/smartdashboard/SmartDashboard.h>
|
||||
|
||||
Elevator::Elevator() {
|
||||
m_encoder.SetDistancePerPulse(Constants::kArmEncoderDistPerPulse);
|
||||
|
||||
// Put Mechanism 2d to SmartDashboard
|
||||
// To view the Elevator visualization, select Network Tables -> SmartDashboard
|
||||
// -> Elevator Sim
|
||||
frc::SmartDashboard::PutData("Elevator Sim", &m_mech2d);
|
||||
}
|
||||
|
||||
void Elevator::SimulationPeriodic() {
|
||||
// In this method, we update our simulation of what our elevator is doing
|
||||
// First, we set our "inputs" (voltages)
|
||||
m_elevatorSim.SetInput(frc::Vectord<1>{
|
||||
m_motorSim.GetSpeed() * frc::RobotController::GetInputVoltage()});
|
||||
|
||||
// Next, we update it. The standard loop time is 20ms.
|
||||
m_elevatorSim.Update(20_ms);
|
||||
|
||||
// Finally, we set our simulated encoder's readings and simulated battery
|
||||
// voltage
|
||||
m_encoderSim.SetDistance(m_elevatorSim.GetPosition().value());
|
||||
// SimBattery estimates loaded battery voltages
|
||||
frc::sim::RoboRioSim::SetVInVoltage(
|
||||
frc::sim::BatterySim::Calculate({m_elevatorSim.GetCurrentDraw()}));
|
||||
}
|
||||
|
||||
void Elevator::UpdateTelemetry() {
|
||||
// Update the Elevator length based on the simulated elevator height
|
||||
m_elevatorMech2d->SetLength(m_encoder.GetDistance());
|
||||
}
|
||||
|
||||
void Elevator::ReachGoal(units::meter_t goal) {
|
||||
m_controller.SetGoal(goal);
|
||||
// With the setpoint value we run PID control like normal
|
||||
double pidOutput =
|
||||
m_controller.Calculate(units::meter_t{m_encoder.GetDistance()});
|
||||
units::volt_t feedforwardOutput =
|
||||
m_feedforward.Calculate(m_controller.GetSetpoint().velocity);
|
||||
m_motor.SetVoltage(units::volt_t{pidOutput} + feedforwardOutput);
|
||||
}
|
||||
|
||||
void Elevator::Stop() {
|
||||
m_controller.SetGoal(0.0_m);
|
||||
m_motor.Set(0.0);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 <numbers>
|
||||
|
||||
#include <units/acceleration.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
#include <units/mass.h>
|
||||
#include <units/time.h>
|
||||
#include <units/velocity.h>
|
||||
#include <units/voltage.h>
|
||||
|
||||
/**
|
||||
* The Constants header provides a convenient place for teams to hold robot-wide
|
||||
* numerical or bool constants. This should not be used for any other purpose.
|
||||
*
|
||||
* It is generally a good idea to place constants into subsystem- or
|
||||
* command-specific namespaces within this header, which can then be used where
|
||||
* they are needed.
|
||||
*/
|
||||
|
||||
namespace Constants {
|
||||
|
||||
static constexpr int kMotorPort = 0;
|
||||
static constexpr int kEncoderAChannel = 0;
|
||||
static constexpr int kEncoderBChannel = 1;
|
||||
static constexpr int kJoystickPort = 0;
|
||||
|
||||
static constexpr double kElevatorKp = 5.0;
|
||||
static constexpr double kElevatorKi = 0.0;
|
||||
static constexpr double kElevatorKd = 0.0;
|
||||
|
||||
static constexpr units::volt_t kElevatorkS = 0.0_V;
|
||||
static constexpr units::volt_t kElevatorkG = 0.762_V;
|
||||
static constexpr auto kElevatorkV = 0.762_V / 1_mps;
|
||||
static constexpr auto kElevatorkA = 0.0_V / 1_mps_sq;
|
||||
|
||||
static constexpr double kElevatorGearing = 10.0;
|
||||
static constexpr units::meter_t kElevatorDrumRadius = 2_in;
|
||||
static constexpr units::kilogram_t kCarriageMass = 4.0_kg;
|
||||
|
||||
static constexpr units::meter_t kSetpoint = 75_cm;
|
||||
static constexpr units::meter_t kMinElevatorHeight = 0_cm;
|
||||
static constexpr units::meter_t kMaxElevatorHeight = 1.25_m;
|
||||
|
||||
// distance per pulse = (distance per revolution) / (pulses per revolution)
|
||||
// = (Pi * D) / ppr
|
||||
static constexpr double kArmEncoderDistPerPulse =
|
||||
2.0 * std::numbers::pi * kElevatorDrumRadius.value() / 4096.0;
|
||||
|
||||
} // namespace Constants
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 <frc/Joystick.h>
|
||||
#include <frc/TimedRobot.h>
|
||||
|
||||
#include "subsystems/Elevator.h"
|
||||
|
||||
/**
|
||||
* This is a sample program to demonstrate the use of elevator simulation.
|
||||
*/
|
||||
class Robot : public frc::TimedRobot {
|
||||
public:
|
||||
void RobotInit() override {}
|
||||
void RobotPeriodic() override;
|
||||
void SimulationPeriodic() override;
|
||||
void TeleopPeriodic() override;
|
||||
void DisabledInit() override;
|
||||
|
||||
private:
|
||||
frc::Joystick m_joystick{Constants::kJoystickPort};
|
||||
Elevator m_elevator;
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 <frc/Encoder.h>
|
||||
#include <frc/controller/ElevatorFeedforward.h>
|
||||
#include <frc/controller/PIDController.h>
|
||||
#include <frc/controller/ProfiledPIDController.h>
|
||||
#include <frc/motorcontrol/PWMSparkMax.h>
|
||||
#include <frc/simulation/BatterySim.h>
|
||||
#include <frc/simulation/ElevatorSim.h>
|
||||
#include <frc/simulation/EncoderSim.h>
|
||||
#include <frc/simulation/PWMSim.h>
|
||||
#include <frc/simulation/RoboRioSim.h>
|
||||
#include <frc/smartdashboard/Mechanism2d.h>
|
||||
#include <frc/smartdashboard/MechanismLigament2d.h>
|
||||
#include <frc/smartdashboard/MechanismRoot2d.h>
|
||||
#include <units/length.h>
|
||||
|
||||
#include "Constants.h"
|
||||
|
||||
class Elevator {
|
||||
public:
|
||||
Elevator();
|
||||
void SimulationPeriodic();
|
||||
void UpdateTelemetry();
|
||||
void ReachGoal(units::meter_t goal);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
// This gearbox represents a gearbox containing 4 Vex 775pro motors.
|
||||
frc::DCMotor m_elevatorGearbox = frc::DCMotor::Vex775Pro(4);
|
||||
|
||||
// Standard classes for controlling our elevator
|
||||
frc::TrapezoidProfile<units::meters>::Constraints m_constraints{2.45_mps,
|
||||
2.45_mps_sq};
|
||||
frc::ProfiledPIDController<units::meters> m_controller{
|
||||
Constants::kElevatorKp, Constants::kElevatorKi, Constants::kElevatorKd,
|
||||
m_constraints};
|
||||
|
||||
frc::ElevatorFeedforward m_feedforward{
|
||||
Constants::kElevatorkS, Constants::kElevatorkG, Constants::kElevatorkV,
|
||||
Constants::kElevatorkA};
|
||||
frc::Encoder m_encoder{Constants::kEncoderAChannel,
|
||||
Constants::kEncoderBChannel};
|
||||
frc::PWMSparkMax m_motor{Constants::kMotorPort};
|
||||
frc::sim::PWMSim m_motorSim{m_motor};
|
||||
|
||||
// Simulation classes help us simulate what's going on, including gravity.
|
||||
frc::sim::ElevatorSim m_elevatorSim{m_elevatorGearbox,
|
||||
Constants::kElevatorGearing,
|
||||
Constants::kCarriageMass,
|
||||
Constants::kElevatorDrumRadius,
|
||||
Constants::kMinElevatorHeight,
|
||||
Constants::kMaxElevatorHeight,
|
||||
true,
|
||||
{0.01}};
|
||||
frc::sim::EncoderSim m_encoderSim{m_encoder};
|
||||
|
||||
// Create a Mechanism2d display of an elevator
|
||||
frc::Mechanism2d m_mech2d{20, 50};
|
||||
frc::MechanismRoot2d* m_elevatorRoot =
|
||||
m_mech2d.GetRoot("Elevator Root", 10, 0);
|
||||
frc::MechanismLigament2d* m_elevatorMech2d =
|
||||
m_elevatorRoot->Append<frc::MechanismLigament2d>(
|
||||
"Elevator", m_elevatorSim.GetPosition().value(), 90_deg);
|
||||
};
|
||||
@@ -21,7 +21,7 @@ class Robot : public frc::TimedRobot {
|
||||
}
|
||||
|
||||
void TeleopPeriodic() override {
|
||||
/* Use the joystick X axis for forward movement, Y axis for lateral
|
||||
/* Use the joystick Y axis for forward movement, X axis for lateral
|
||||
* movement, and Z axis for rotation.
|
||||
*/
|
||||
m_robotDrive.DriveCartesian(-m_stick.GetY(), -m_stick.GetX(),
|
||||
|
||||
@@ -25,7 +25,7 @@ class Robot : public frc::TimedRobot {
|
||||
|
||||
static constexpr int kPotChannel = 1;
|
||||
static constexpr int kMotorChannel = 7;
|
||||
static constexpr int kJoystickChannel = 0;
|
||||
static constexpr int kJoystickChannel = 3;
|
||||
|
||||
// The elevator can move 1.5 meters from top to bottom
|
||||
static constexpr units::meter_t kFullHeight = 1.5_m;
|
||||
|
||||
@@ -41,7 +41,7 @@ class RobotContainer {
|
||||
frc2::Command* GetAutonomousCommand();
|
||||
|
||||
private:
|
||||
// Assumes a gamepad plugged into channnel 0
|
||||
// Assumes a gamepad plugged into channel 0
|
||||
frc::Joystick m_controller{0};
|
||||
frc::SendableChooser<frc2::Command*> m_chooser;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user